@todesktop/runtime
v2.0.0
Published
The [ToDesktop](https://todesktop.com) runtime package which takes care of auto-updating, crash reporting, and more. For more information, see https://todesktop.com/cli.
Downloads
40,299
Readme
@todesktop/runtime
The ToDesktop runtime package which takes care of auto-updating, crash reporting, and more. For more information, see https://todesktop.com/cli.
Important
If you are using this library with Electron 11 or earlier then please use version
0.6.5
.If you are using Electron 12 or later then you can use version
1.0.0
or later.
Table of contents
- Installation
- Minimal Usage
- Default Behaviour
- Simulate Updates
- Smoke Test
- API
- Events
- Recipes
- Debugging
- More documentation
- Changelog
Installation
Via npm:
npm install @todesktop/runtime
Or Yarn:
yarn add @todesktop/runtime
Minimal usage
In your main (background process) script, require the package and call the init
function. The key is to call it right at the beginning. We'll do the rest.
const { app, BrowserWindow } = require("electron");
const todesktop = require("@todesktop/runtime");
todesktop.init();
function createWindow() {
// Create the browser window.
let win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
});
// and load the index.html of the app.
win.loadFile("index.html");
}
app.whenReady().then(createWindow);
Check out todesktop-quick-start to see a minimal example project.
Also, see the full API below to learn more about the other methods and events.
Default behaviour
By default, we check for updates when the app launches and every 10 minutes thereafter.
When an update is found, we show a notification explaining that an update has been downloaded and once they exit/restart, it'll be installed. Your user doesn't have to accept any prompt or anything like that. Only one notification is shown, max.
This default behaviour can be augmented using the API below, e.g. if you want to offer your users a button to check for updates, a prompt to restart and install the update, etc.
Disabling the default auto-update behaviour
It's also possible to disable some or all of the default behaviour.
What happens when an update is found can be disabled using the update-downloaded
event (learn more below).
Everything (the notification, the auto-checks on launch and on interval) can be disabled by passing autoUpdater: false
to the .init
method (learn more below).
Simulate updates
Passing --runtime-simulate-updates
as a flag with an associated value will run @todesktop/runtime
in simulation mode:
--runtime-simulate-updates=update-available
simulates the situation where an auto-update exists and will be downloaded.--runtime-simulate-updates=update-not-available
simulates the situation where an auto update is not available.
Internally, this replaces the autoUpdater
API implementation with an alternative that simulates the above auto-update workflows. This is useful if you're running your application in local development and want to test @todesktop/runtime
's auto-update behaviour.
Smoke test
Setting TODESKTOP_SMOKE_TEST
in your environment to true
will run @todesktop/runtime
in simulation mode.
When an application is run in Smoke Test mode the auto-update behavior is forcibly enabled. To successfully pass a Smoke Test your application should not expect any user interaction like showing some dialog on app start.
You can check whether an application is run in Smoke Test mode using todesktop.isSmokeTestMode
property. See an example.
API
.init(options)
.autoUpdater.checkForUpdates(options)
.autoUpdater.on(eventName, callback)
.autoUpdater.restartAndInstall(options)
.isSmokeTestMode
.runtime.isInstalledUsingWindowsMSI
.init(options)
This initializes ToDesktop. You must call this at the top of your main script. If the app isn't packaged, this won't do much. .autoUpdater.checkForUpdates
will never check or find an update, for example.
Returns nothing.
options
(Optional)
Type: object
Default: { autoUpdater: true }
options.autoUpdater
(Optional)
Type: boolean
Default: true
If false
, this will disable all of the default auto-update behaviour. I.e. it will not auto-check on launch, on interval, or show a notification when an update is found. The events will always be fired.
If you do this, everything is in your hands. You must manually call .autoUpdater.checkForUpdates
or else your users will never get an update.
options.autoCheckInterval
(Optional)
Type: number
Default: 10 * 60 * 1000
(10 min)
The interval at which ToDesktop should check for updates.
options.shouldAutoCheckOnLaunch
(Optional)
Type: boolean
Default: true
Whether the app should check for auto-updates on launch.
options.customLogger
(Optional)
Default: undefined
To debug, you can pass a custom logger object. You can pass electron-log (recommended), winston, or another logger with the following interface: { error(), info(), warn() }
. Include a debug()
method to get even more information.
Example using electron-log:
const electronLog = require("electron-log");
const todesktop = require("@todesktop/runtime");
electronLog.transports.file.level = "debug";
todesktop.init({
customLogger: electronLog,
});
options.updateReadyAction
(Optional)
Default: { showInstallAndRestartPrompt: "never", showNotification: "always" }
Configures whether runtime should notify a user when a new update is downloaded.
options.updateReadyAction.showInstallAndRestartPrompt
(Optional)
showInstallAndRestartPrompt
asks a user whether the app should be updated
right now or on the next launch. Values:
"never"
- never show the prompt"always"
- always show the prompt"whenInForeground"
- show the prompt only when the app is in foreground
You can also choose to supply a Function
instead of a string
value. If a function is supplied, it will override the system prompt that gets shown once a new update is ready to be installed. For flexibility, this function can return either an object or another function.
Object return signature
When following the object return pattern, the return object should contain MessageBoxOptions
for displaying the prompt.
You can also make use of the supplied context
argument to ensure that the prompt displays contextually-relevant information:
context
contains update information:disableUpdateReadyAction: boolean;
sources: Source[];
updateInfo: TDUpdateInfo | null;
appIsInForeground: boolean;
const todesktop = require("@todesktop/runtime");
todesktop.init({
updateReadyAction: {
showInstallAndRestartPrompt: async (context) => {
if (!context.appIsInForeground) return;
return {
message: "Update Available",
detail: `Version ${context.updateInfo.version} is ready to be installed.`,
installOnNextLaunchButton: "Install on next launch",
restartAndInstallButton: "Install now and restart",
};
},
},
});
Function return signature
The function pattern allows for more granular control of the prompt. By returning a function, we also supply you with an additional api
argument which you can use to manually show and react to the prompt:
api
provides functions for correct handling of the returned function:showInstallAndRestartPrompt(options: Electron.MessageBoxOptions): Promise<Electron.MessageBoxReturnValue>
restartAndInstall({ isSilent }?: { isSilent?: boolean }): void
const todesktop = require("@todesktop/runtime");
todesktop.init({
updateReadyAction: {
showInstallAndRestartPrompt: async (context) => {
if (!context.appIsInForeground) return;
return async (api) => {
const { response } = await api.showInstallAndRestartPrompt({
message: "Update Available",
detail: `Version ${context.updateInfo.version} is ready to be installed.`,
buttons: ["Install on next launch", `Install now and restart`],
});
if (response === 1) {
api.restartAndInstall();
}
};
},
},
});
options.updateReadyAction.showNotification
(Optional)
showNotification
shows a notification that an update is ready to be
installed. Values:
"never"
- never show the notification"always"
- always show the notification"whenInBackground"
- show the notification only when the app is in background.
You can also choose to supply a Function
instead of a string
value. If a function is supplied, it will override the notification that gets shown once a new update is ready to be installed. For flexibility, this function can return either an object or another function.
Object return signature
When following the object return pattern, the return object should contain NotificationConstructorOptions
for displaying the notification.
You can also make use of the supplied context
argument to ensure that the notification displays contextually-relevant information:
context
contains update information:disableUpdateReadyAction: boolean;
sources: Source[];
updateInfo: TDUpdateInfo | null;
appIsInForeground: boolean;
const todesktop = require("@todesktop/runtime");
todesktop.init({
updateReadyAction: {
showNotification: (context) => {
if (context.appIsInForeground) return;
return {
title: "A new update is ready to install",
body: `Version ${context.updateInfo.version} has been downloaded and will be automatically installed on exit`,
};
},
},
});
Function return signature
The function pattern allows for more granular control of the notification. By returning a function, we also supply you with an additional api
argument which you can use to manually show and react to the notification:
api
provides functions for correct handling of the returned function:showNotification(options: NotificationConstructorOptions): Notification
const todesktop = require("@todesktop/runtime");
todesktop.init({
updateReadyAction: {
showNotification: (context) => {
if (context.appIsInForeground) return;
return (api) => {
const notification = api.showNotification({
title: "A new update is ready to install",
body: `Version ${context.updateInfo.version} has been downloaded and will be automatically installed on exit`,
actions: [{ type: "button", text: "Close" }],
});
notification.on("action", () => {
// optionally respond to action...
});
};
},
},
});
.autoUpdater.checkForUpdates(options)
This performs a check for updates and downloads it. Only call this after the application is ready.
It returns a Promise which resolves to an object.
If there is no update available, the result will be { updateInfo: null }
.
If there is an update available, the result will look like this:
{
updateInfo: {
releaseDate: "2011-10-05T14:48:00.000Z",
version: "2.0.1",
}
}
This method can reject with an error, e.g. if the network is unstable.
options
(Optional)
Type: object
Default: { source: "programmatic-call", disableUpdateReadyAction: false }
options.source
(Optional)
Type: string
Default: "programmatic-call"
The source/trigger of the update check. The update-downloaded
event passes this back to you so you can identify the source(s) of the update check.
options.disableUpdateReadyAction
(Optional)
Type: boolean
Default: false
Whether to disable the configured updateReadyAction
behaviour. If disabled, the updateReadyAction
prompt or notification will not display in response to invoking .autoUpdater.checkForUpdates
.
Example
try {
const result = await todesktop.autoUpdater.checkForUpdates();
if (result.updateInfo) {
console.log("Update found:", result.updateInfo.version);
todesktop.autoUpdater.restartAndInstall();
}
} catch (e) {
console.log("Update check failed:", e);
}
.autoUpdater.on(eventName, callback)
This subscribes to an event. The callback will be called when the event occurs.
.autoUpdater
is an instance of EventEmitter2 so it supports all of the same methods; e.g. .once
, .off
, and so on.
eventName
Type: string
callback
Type: Function
.autoUpdater.restartAndInstall(options)
This restarts the application and installs the new update after one has been found.
Returns nothing.
NOTE: this method will close all application windows first and only emit before-quit-for-update
event on app after that. This is different from the normal quit event sequence. However, this is not the case if the method is called from a Squirrel.Windows application.
options
(Optional)
Type: object
options.isSilent
(Optional)
Type: boolean
Default: false
Runs the installer in silent mode. No installer UI will be shown to the user. Ignored on macOS as update installs are silent by default.
isSmokeTestMode
Type: boolean
Returns true
if the app is run in Smoke Test mode.
runtime.isInstalledUsingWindowsMSI
Type: Function
Returns true
if the app is installed using MSI.
Events
before-quit-for-update
This is the same as Electron's before-quit-for-update
auto-updater event.
checking-for-update
Emitted when checking if an update has started.
update-available
Emitted when there is an available update. Callback contains the following object:
{
releaseDate: "2011-10-05T14:48:00.000Z",
version: "2.1.3"
}
update-not-available
Emitted when there is no available update. Callback contains the following object:
{
releaseDate: "2011-10-05T14:48:00.000Z",
version: "2.1.3"
}
download-progress
Emitted on progress. Callback contains the following object:
progress
bytesPerSecond
percent
total
transferred
update-downloaded
Emitted when there is an update available and downloaded. The update has already been downloaded.
Your callback is called with an object like this:
{
disableUpdateReadyAction: false,
sources: ["auto-check-on-interval"],
updateInfo: {
releaseDate: "2011-10-05T14:48:00.000Z",
version: "2.1.3"
}
}
disableUpdateReadyAction
is a boolean that signals whether the configured updateReadyAction
was disabled.
sources
(an array of strings) are the sources/triggers of the update check. The default built-in sources are auto-check-on-launch
, auto-check-on-interval
, and programmatic-call
. You can pass a custom source to .autoUpdater.checkForUpdates
. NOTE: sources
is an array because multiple checks could be triggered around the same time. They do not cancel an existing update check but piggyback onto it instead.
By default, we show a notification explaining that an update has been downloaded and once they exit/restart, it'll be installed. Your user doesn't have to accept any prompt or anything like that. Only one notification is shown, max.
To disable this, you can return false
(or a Promise which resolves to promise
). If your callback errors or it returns a Promise that doesn't resolve within 500 milliseconds, the default behaviour will proceed.
Example
todesktop.autoUpdater.on("update-downloaded", (event) => {
console.log("Update found:", event.updateInfo.version);
if (event.sources.includes("my-custom-source")) {
return false;
todesktop.autoUpdater.restartAndInstall();
}
// Else: the default behaviour (notification) will happen
});
Recipes
How do I show a message on my UI to indicate that an update is available?
By default, ToDesktop runtime will show a notification that an update is ready to be installed. However, it is possible that the user may have notifications turned off for your app. For this reason, it's a good idea to notify your users of an update directly on the UI of your app.
ToDesktop runtime is only available in the main process of Electron. So we need to use IPC to tell the UI that an update is available, let's do that:
const todesktop = require("@todesktop/runtime");
const { ipcMain } = require("electron");
todesktop.init();
const win = new BrowserWindow(/* Options here */);
// When we receive an update-downloaded event then
// we forward that event to our UI using IPC
todesktop.autoUpdater.on("update-downloaded", (event) => {
win.webContents.send("update-downloaded", event);
});
// Listen for the UI to tell us that it wants to
// do a restart and install of the app
ipcMain.on("restart-and-install", () => {
todesktop.autoUpdater.restartAndInstall();
});
In our UI we want to show a div when an update is ready to install. Here's the HTML:
<div class="update-available" style="display:none;">
Version <span class="app-version"></span> is ready to Install.
<a href="#" class="click-to-install">
Click here to restart the app and install the update </a
>.
</div>
Let's hook it all up. We use IPC on the renderer thread to update our UI:
const { ipcRenderer } = require("electron");
// First let's hook up our click-to-install link
const installLink = document.querySelector(".click-to-install");
installLink.addEventListener("click", (e) => {
e.preventDefault();
ipcRenderer.send("restart-and-install");
});
// Now let's listen for updates and show the
// update-available div on our UI
const updateAvailableDiv = document.querySelector(".update-available");
const appVersionSpan = document.querySelector(".app-version");
ipcRenderer.on("update-downloaded", (e, { updateInfo }) => {
// Update the version text
appVersionSpan.innerText = updateInfo.version;
// Show the update-available div
updateAvailableDiv.style.display = "block";
});
How do I force the application to restart when an update has been downloaded?
First up, make sure that this is something that you want to do. For example, a user might be in the middle of typing a message on your UI when you restart the app.
If you still want to go ahead then here is a simple recipe to do just that.
todesktop.autoUpdater.on("update-downloaded", () => {
// Immediately update the app and restart it
todesktop.autoUpdater.restartAndInstall();
// Return false so that a notification is not displayed
return false;
});
How do I access debug logs in a packaged app
Sometimes you will want to see messages that are logged to the console while a packaged app is running. Here is how you do this on Mac and Windows:
On Windows
Open the Command Prompt and cd
into the app's folder.
NOTE: If testing auto-update then make sure you use the Command Prompt because other shell environments like bash will prevent the updater from working correctly.
cd C:\Users\YourUserName\AppData\Local\Programs\YourAppName
You can optionally set the DEBUG environment variable to enable logging from @todesktop/runtime.
set DEBUG=@todesktop/runtime
Then run the app:
"{YourAppName}.exe"
On Mac
Open the terminal and run the app with the DEBUG environment variable set:
DEBUG=@todesktop/runtime open "/Applications/YourAppName.app"
To see the logs, run the following command in the terminal:
tail -f ~/Library/Logs/YourAppName/main.log
How do I prevent code from running in a Smoke Test
Actions which require a user interaction may break Smoke Test, especially dialogs from electron dialog API. Use the following code to disable such a behavior in a Smoke Test mode:
if (!todesktop.isSmokeTestMode) {
const result = dialog.showMessageBoxSync({
type: "question",
buttons: ["Move to Applications", "Not Now"],
defaultId: 0,
cancelId: 1,
title: "Move to Applications?",
message:
"The application should be run from the Applications folder. Would you like to move it now?",
});
if (result === 0) {
moveToApplications();
}
}
Debugging
See the customLogger option
.
Alternatively, set the DEBUG
environment variable and the logs will be sent to the main process logs; DEBUG=@todesktop/runtime
. See the recipe above for how to do this with a packaged app.
More documentation
For documentation on ToDesktop CLI See https://www.npmjs.com/package/@todesktop/cli
Changelog
v2.0.0
- Upgrade to Electron Updater v6.3.9 (addresses CVE-2024-39698).
- Upgrade to Del v6.1.1.
- Upgrade to EventEmitter2 v6.4.9.
- Upgrade to Semver v7.6.3.
v1.6.4
- Add support for
todesktop.runtime.isInstalledUsingWindowsMSI
function.