import type { AppUpdater } from 'electron-updater' import { Injectable } from '@angular/core' import axios from 'axios' import { Logger, LogService, ConfigService, UpdaterService, PlatformService, TranslateService } from 'tabby-core' import { ElectronService } from '../services/electron.service' const UPDATES_URL = 'https://api.github.com/repos/eugeny/tabby/releases/latest' @Injectable() export class ElectronUpdaterService extends UpdaterService { private logger: Logger private downloaded: Promise private electronUpdaterAvailable = true private updateURL: string private autoUpdater: AppUpdater constructor ( log: LogService, config: ConfigService, private translate: TranslateService, private platform: PlatformService, private electron: ElectronService, ) { super() this.logger = log.create('updater') if (process.platform === 'linux') { this.electronUpdaterAvailable = false return } this.autoUpdater = electron.remote.require('electron-updater').autoUpdater this.autoUpdater.autoDownload = true this.autoUpdater.autoInstallOnAppQuit = false this.autoUpdater.on('update-available', () => { this.logger.info('Update available') }) this.autoUpdater.on('update-not-available', () => { this.logger.info('No updates') }) this.autoUpdater.on('error', err => { this.logger.error(err) this.electronUpdaterAvailable = false }) this.downloaded = new Promise(resolve => { this.autoUpdater.once('update-downloaded', () => resolve(true)) }) config.ready$.toPromise().then(() => { if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TABBY_DEV) { this.logger.debug('Checking for updates') try { this.autoUpdater.setFeedURL({ provider: 'github', repo: 'tabby', owner: 'eugeny', }) this.autoUpdater.checkForUpdates() } catch (e) { this.electronUpdaterAvailable = false this.logger.info('Electron updater unavailable, falling back', e) } } }) } async check (): Promise { if (this.electronUpdaterAvailable) { return new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/init-declarations, prefer-const let cancel const onNoUpdate = () => { cancel() resolve(false) } const onUpdate = () => { cancel() resolve(this.downloaded) } const onError = (err) => { cancel() reject(err) } cancel = () => { this.autoUpdater.off('error', onError) this.autoUpdater.off('update-not-available', onNoUpdate) this.autoUpdater.off('update-available', onUpdate) } this.autoUpdater.on('error', onError) this.autoUpdater.on('update-not-available', onNoUpdate) this.autoUpdater.on('update-available', onUpdate) try { this.autoUpdater.checkForUpdates() } catch (e) { this.electronUpdaterAvailable = false this.logger.info('Electron updater unavailable, falling back', e) } }) this.autoUpdater.on('update-available', () => { this.logger.info('Update available') }) this.autoUpdater.once('update-not-available', () => { this.logger.info('No updates') }) } else { this.logger.debug('Checking for updates through fallback method.') const response = await axios.get(UPDATES_URL) const data = response.data const version = data.tag_name.substring(1) if (this.electron.app.getVersion() !== version) { this.logger.info('Update available') this.updateURL = data.html_url return true } this.logger.info('No updates') return false } return this.downloaded } async update (): Promise { if (!this.electronUpdaterAvailable) { await this.electron.shell.openExternal(this.updateURL) } else { if ((await this.platform.showMessageBox( { type: 'warning', message: this.translate.instant('Installing the update will close all tabs and restart Tabby.'), buttons: [ this.translate.instant('Update'), this.translate.instant('Cancel'), ], defaultId: 0, cancelId: 1, } )).response === 0) { await this.downloaded this.autoUpdater.quitAndInstall() } } } }