import { dialog, Menu, MenuItem, shell, } from 'electron'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; import { isAppImage, isDev, isMacAppStore, isWindowsPortable, isWindowsStore, } from './utils'; import { I18nService } from '../abstractions/i18n.service'; import { WindowMain } from './window.main'; const UpdaterCheckInitalDelay = 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 gitHubProject: string, private onCheckingForUpdate: () => void = null, private onReset: () => void = null, private onUpdateDownloaded: () => void = null, 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(), UpdaterCheckInitalDelay); global.setInterval(async () => await this.checkForUpdate(), UpdaterCheckInterval); autoUpdater.on('checking-for-update', () => { if (this.onCheckingForUpdate != null) { this.onCheckingForUpdate(); } 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.onUpdateDownloaded != null) { this.onUpdateDownloaded(); } 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) { autoUpdater.quitAndInstall(false, 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: boolean = false) { if (this.doingUpdateCheck || isDev()) { return; } if (!this.canUpdate) { if (withFeedback) { shell.openExternal('https://github.com/bitwarden/' + this.gitHubProject + '/releases'); } return; } this.doingUpdateCheckWithFeedback = withFeedback; if (withFeedback) { autoUpdater.autoDownload = false; } await autoUpdater.checkForUpdates(); } private reset() { if (this.onReset != null) { this.onReset(); } autoUpdater.autoDownload = true; this.doingUpdateCheck = false; } }