import { dialog, shell } from "electron"; import log from "electron-log"; import { autoUpdater } from "electron-updater"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { isAppImage, isDev, isMacAppStore, isWindowsPortable, isWindowsStore } from "../utils"; import { WindowMain } from "./window.main"; const UpdaterCheckInitialDelay = 5 * 1000; // 5 seconds const UpdaterCheckInterval = 12 * 60 * 60 * 1000; // 12 hours export class UpdaterMain { private doingUpdateCheck = false; private doingUpdateCheckWithFeedback = false; private canUpdate = false; constructor( private i18nService: I18nService, private windowMain: WindowMain, private projectName: string ) { autoUpdater.logger = log; const linuxCanUpdate = process.platform === "linux" && isAppImage(); const windowsCanUpdate = process.platform === "win32" && !isWindowsStore() && !isWindowsPortable(); const macCanUpdate = process.platform === "darwin" && !isMacAppStore(); this.canUpdate = process.env.ELECTRON_NO_UPDATER !== "1" && (linuxCanUpdate || windowsCanUpdate || macCanUpdate); } async init() { global.setTimeout(async () => await this.checkForUpdate(), UpdaterCheckInitialDelay); global.setInterval(async () => await this.checkForUpdate(), UpdaterCheckInterval); autoUpdater.on("checking-for-update", () => { this.doingUpdateCheck = true; }); autoUpdater.on("update-available", async () => { if (this.doingUpdateCheckWithFeedback) { if (this.windowMain.win == null) { this.reset(); return; } const result = await dialog.showMessageBox(this.windowMain.win, { type: "info", title: this.i18nService.t(this.projectName) + " - " + this.i18nService.t("updateAvailable"), message: this.i18nService.t("updateAvailable"), detail: this.i18nService.t("updateAvailableDesc"), buttons: [this.i18nService.t("yes"), this.i18nService.t("no")], cancelId: 1, defaultId: 0, noLink: true, }); if (result.response === 0) { autoUpdater.downloadUpdate(); } else { this.reset(); } } }); autoUpdater.on("update-not-available", () => { if (this.doingUpdateCheckWithFeedback && this.windowMain.win != null) { dialog.showMessageBox(this.windowMain.win, { message: this.i18nService.t("noUpdatesAvailable"), buttons: [this.i18nService.t("ok")], defaultId: 0, noLink: true, }); } this.reset(); }); autoUpdater.on("update-downloaded", async (info) => { if (this.windowMain.win == null) { return; } const result = await dialog.showMessageBox(this.windowMain.win, { type: "info", title: this.i18nService.t(this.projectName) + " - " + this.i18nService.t("restartToUpdate"), message: this.i18nService.t("restartToUpdate"), detail: this.i18nService.t("restartToUpdateDesc", info.version), buttons: [this.i18nService.t("restart"), this.i18nService.t("later")], cancelId: 1, defaultId: 0, noLink: true, }); if (result.response === 0) { // Quit and install have a different window logic, setting `isQuitting` just to be safe. this.windowMain.isQuitting = true; autoUpdater.quitAndInstall(true, true); } }); autoUpdater.on("error", (error) => { if (this.doingUpdateCheckWithFeedback) { dialog.showErrorBox( this.i18nService.t("updateError"), error == null ? this.i18nService.t("unknown") : (error.stack || error).toString() ); } this.reset(); }); } async checkForUpdate(withFeedback = false) { if (this.doingUpdateCheck || isDev()) { return; } if (!this.canUpdate) { if (withFeedback) { shell.openExternal("https://github.com/bitwarden/clients/releases"); } return; } this.doingUpdateCheckWithFeedback = withFeedback; if (withFeedback) { autoUpdater.autoDownload = false; } await autoUpdater.checkForUpdates(); } private reset() { autoUpdater.autoDownload = true; this.doingUpdateCheck = false; } }