2024-08-21 02:01:29 +02:00
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
2024-08-06 23:25:30 +02:00
import { isDev } from "@/util/isdev" ;
2024-08-06 20:05:26 +02:00
import * as electron from "electron" ;
import { autoUpdater } from "electron-updater" ;
import * as services from "../frontend/app/store/services" ;
import { fireAndForget } from "../frontend/util/util" ;
export let updater : Updater ;
export class Updater {
2024-08-08 20:55:44 +02:00
autoCheckInterval : NodeJS.Timeout | null ;
intervalms : number ;
autoCheckEnabled : boolean ;
2024-08-06 20:05:26 +02:00
availableUpdateReleaseName : string | null ;
availableUpdateReleaseNotes : string | null ;
2024-08-07 00:13:59 +02:00
private _status : UpdaterStatus ;
2024-08-06 20:05:26 +02:00
lastUpdateCheck : Date ;
2024-09-04 04:16:04 +02:00
constructor ( settings : SettingsType ) {
this . intervalms = settings [ "autoupdate:intervalms" ] ;
this . autoCheckEnabled = settings [ "autoupdate:enabled" ] ;
2024-08-08 20:55:44 +02:00
this . _status = "up-to-date" ;
this . lastUpdateCheck = new Date ( 0 ) ;
this . autoCheckInterval = null ;
this . availableUpdateReleaseName = null ;
2024-09-04 04:16:04 +02:00
autoUpdater . autoInstallOnAppQuit = settings [ "autoupdate:installonquit" ] ;
2024-08-08 20:55:44 +02:00
2024-08-06 20:05:26 +02:00
autoUpdater . removeAllListeners ( ) ;
autoUpdater . on ( "error" , ( err ) = > {
console . log ( "updater error" ) ;
console . log ( err ) ;
this . status = "error" ;
} ) ;
autoUpdater . on ( "checking-for-update" , ( ) = > {
console . log ( "checking-for-update" ) ;
this . status = "checking" ;
} ) ;
autoUpdater . on ( "update-available" , ( ) = > {
console . log ( "update-available; downloading..." ) ;
} ) ;
autoUpdater . on ( "update-not-available" , ( ) = > {
console . log ( "update-not-available" ) ;
} ) ;
autoUpdater . on ( "update-downloaded" , ( event ) = > {
console . log ( "update-downloaded" , [ event ] ) ;
this . availableUpdateReleaseName = event . releaseName ;
this . availableUpdateReleaseNotes = event . releaseNotes as string | null ;
// Display the update banner and create a system notification
this . status = "ready" ;
const updateNotification = new electron . Notification ( {
title : "Wave Terminal" ,
body : "A new version of Wave Terminal is ready to install." ,
} ) ;
updateNotification . on ( "click" , ( ) = > {
2024-08-07 01:19:55 +02:00
fireAndForget ( ( ) = > this . promptToInstallUpdate ( ) ) ;
2024-08-06 20:05:26 +02:00
} ) ;
updateNotification . show ( ) ;
} ) ;
}
2024-08-08 20:55:44 +02:00
/ * *
* The status of the Updater .
* /
2024-08-06 20:05:26 +02:00
get status ( ) : UpdaterStatus {
return this . _status ;
}
private set status ( value : UpdaterStatus ) {
this . _status = value ;
electron . BrowserWindow . getAllWindows ( ) . forEach ( ( window ) = > {
window . webContents . send ( "app-update-status" , value ) ;
} ) ;
}
/ * *
2024-08-08 20:55:44 +02:00
* Check for updates and start the background update check , if configured .
2024-08-06 20:05:26 +02:00
* /
2024-08-08 20:55:44 +02:00
async start() {
if ( this . autoCheckEnabled ) {
console . log ( "starting updater" ) ;
this . autoCheckInterval = setInterval ( ( ) = > {
fireAndForget ( ( ) = > this . checkForUpdates ( false ) ) ;
} , 600000 ) ; // intervals are unreliable when an app is suspended so we will check every 10 mins if the interval has passed.
await this . checkForUpdates ( false ) ;
}
}
/ * *
* Stop the background update check , if configured .
* /
stop() {
console . log ( "stopping updater" ) ;
if ( this . autoCheckInterval ) {
clearInterval ( this . autoCheckInterval ) ;
this . autoCheckInterval = null ;
}
2024-08-06 20:05:26 +02:00
}
/ * *
2024-08-07 01:09:24 +02:00
* Checks if the configured interval time has passed since the last update check , and if so , checks for updates using the ` autoUpdater ` object
2024-08-06 20:05:26 +02:00
* @param userInput Whether the user is requesting this . If so , an alert will report the result of the check .
* /
async checkForUpdates ( userInput : boolean ) {
const now = new Date ( ) ;
2024-08-07 01:08:49 +02:00
// Run an update check always if the user requests it, otherwise only if there's an active update check interval and enough time has elapsed.
2024-08-06 20:05:26 +02:00
if (
2024-08-07 00:13:59 +02:00
userInput ||
2024-08-08 20:55:44 +02:00
( this . autoCheckInterval &&
( ! this . lastUpdateCheck || Math . abs ( now . getTime ( ) - this . lastUpdateCheck . getTime ( ) ) > this . intervalms ) )
2024-08-06 20:05:26 +02:00
) {
2024-08-06 23:51:06 +02:00
const result = await autoUpdater . checkForUpdates ( ) ;
2024-08-07 01:08:49 +02:00
// If the user requested this check and we do not have an available update, let them know with a popup dialog. No need to tell them if there is an update, because we show a banner once the update is ready to install.
2024-08-06 23:51:06 +02:00
if ( userInput && ! result . downloadPromise ) {
const dialogOpts : Electron.MessageBoxOptions = {
type : "info" ,
message : "There are currently no updates available." ,
} ;
electron . dialog . showMessageBox ( electron . BrowserWindow . getFocusedWindow ( ) , dialogOpts ) ;
}
2024-08-07 01:08:49 +02:00
// Only update the last check time if this is an automatic check. This ensures the interval remains consistent.
2024-08-07 00:36:21 +02:00
if ( ! userInput ) this . lastUpdateCheck = now ;
2024-08-06 20:05:26 +02:00
}
}
/ * *
* Prompts the user to install the downloaded application update and restarts the application
* /
2024-08-07 01:19:55 +02:00
async promptToInstallUpdate() {
2024-08-06 20:05:26 +02:00
const dialogOpts : Electron.MessageBoxOptions = {
type : "info" ,
buttons : [ "Restart" , "Later" ] ,
title : "Application Update" ,
message : process.platform === "win32" ? this . availableUpdateReleaseNotes : this.availableUpdateReleaseName ,
detail : "A new version has been downloaded. Restart the application to apply the updates." ,
} ;
const allWindows = electron . BrowserWindow . getAllWindows ( ) ;
if ( allWindows . length > 0 ) {
await electron . dialog
. showMessageBox ( electron . BrowserWindow . getFocusedWindow ( ) ? ? allWindows [ 0 ] , dialogOpts )
. then ( ( { response } ) = > {
2024-08-07 00:13:59 +02:00
if ( response === 0 ) {
2024-08-07 01:19:55 +02:00
this . installUpdate ( ) ;
2024-08-07 00:13:59 +02:00
}
2024-08-06 20:05:26 +02:00
} ) ;
}
}
2024-08-07 01:19:55 +02:00
/ * *
* Restarts the app and installs an update if it is available .
* /
installUpdate() {
if ( this . status == "ready" ) {
this . status = "installing" ;
autoUpdater . quitAndInstall ( ) ;
}
}
2024-08-06 20:05:26 +02:00
}
2024-08-07 01:19:55 +02:00
electron . ipcMain . on ( "install-app-update" , ( ) = > fireAndForget ( ( ) = > updater ? . promptToInstallUpdate ( ) ) ) ;
2024-08-06 20:05:26 +02:00
electron . ipcMain . on ( "get-app-update-status" , ( event ) = > {
event . returnValue = updater ? . status ;
} ) ;
2024-08-07 00:13:59 +02:00
let autoUpdateLock = false ;
2024-08-06 20:05:26 +02:00
/ * *
* Configures the auto - updater based on the user ' s preference
* /
export async function configureAutoUpdater() {
2024-08-06 23:25:30 +02:00
if ( isDev ( ) ) {
console . log ( "skipping auto-updater in dev mode" ) ;
2024-08-07 00:13:59 +02:00
return ;
2024-08-06 23:25:30 +02:00
}
2024-08-06 20:05:26 +02:00
// simple lock to prevent multiple auto-update configuration attempts, this should be very rare
if ( autoUpdateLock ) {
console . log ( "auto-update configuration already in progress, skipping" ) ;
return ;
}
autoUpdateLock = true ;
try {
console . log ( "Configuring updater" ) ;
2024-09-04 04:16:04 +02:00
const settings = ( await services . FileService . GetFullConfig ( ) ) . settings ;
updater = new Updater ( settings ) ;
2024-08-08 20:55:44 +02:00
await updater . start ( ) ;
2024-08-06 20:05:26 +02:00
} catch ( e ) {
console . warn ( "error configuring updater" , e . toString ( ) ) ;
}
autoUpdateLock = false ;
}