diff --git a/package-lock.json b/package-lock.json index d0ec9309f4..1343aecc53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -264,7 +264,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "1.0.3" } @@ -634,8 +633,15 @@ "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "bluebird-lst": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.5.tgz", + "integrity": "sha512-Ey0bDNys5qpYPhZ/oQ9vOEvD0TYQDTILMXWP2iGfvMg7rSDde+oV4aQQgqRH+CvBFNz2BSDQnPGMUl6LKBUUQA==", + "requires": { + "bluebird": "3.5.1" + } }, "bn.js": { "version": "4.11.8", @@ -875,12 +881,38 @@ "ieee754": "1.1.11" } }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, + "builder-util-runtime": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-4.2.1.tgz", + "integrity": "sha512-6Ufp6ExT40RDYNXQgD4xG0fgtpUHyc8XIld6lptKr0re1DNnUrQP4sSV/lJOajpzyercMP/YIzO60/mNuAFiWg==", + "requires": { + "bluebird-lst": "1.0.5", + "debug": "3.1.0", + "fs-extra-p": "4.6.0", + "sax": "1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -1881,6 +1913,11 @@ } } }, + "electron-is-dev": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz", + "integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4=" + }, "electron-log": { "version": "2.2.14", "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-2.2.14.tgz", @@ -1894,6 +1931,38 @@ "conf": "1.4.0" } }, + "electron-updater": { + "version": "2.21.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-2.21.4.tgz", + "integrity": "sha512-x6QSbyxgGR3szIOBtFoCJH0TfgB55AWHaXmilNgorfvpnCdEMQEATxEzLOW0JCzzcB5y3vBrawvmMUEdXwutmA==", + "requires": { + "bluebird-lst": "1.0.5", + "builder-util-runtime": "4.2.1", + "electron-is-dev": "0.3.0", + "fs-extra-p": "4.6.0", + "js-yaml": "3.11.0", + "lazy-val": "1.0.3", + "lodash.isequal": "4.5.0", + "semver": "5.5.0", + "source-map-support": "0.5.5" + }, + "dependencies": { + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + } + } + }, "elliptic": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", @@ -2092,8 +2161,7 @@ "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" }, "estraverse": { "version": "1.9.3", @@ -2544,6 +2612,35 @@ } } }, + "fs-extra-p": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.6.0.tgz", + "integrity": "sha512-nSVqB5UfWZQdU6pzBwcFh+7lJpBynnTsVtNJTBhAnAppUQRut0W7WeM271iS0TqQ9FoCqDXqyL0+h+h8DQUCpg==", + "requires": { + "bluebird-lst": "1.0.5", + "fs-extra": "6.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.0.tgz", + "integrity": "sha512-lk2cUCo8QzbiEWEbt7Cw3m27WMiRG321xsssbcIpfMhpRjrlC08WBOVQqj1/nQYYNnPtyIhP1oqLO3QwT2tPCw==", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.1.11" + } + } + } + }, "fs.realpath": { "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", @@ -5219,6 +5316,11 @@ "dev": true, "optional": true }, + "lazy-val": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz", + "integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg==" + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -5264,6 +5366,11 @@ "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "lodash.memoize": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", @@ -5569,8 +5676,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "nan": { "version": "2.10.0", @@ -7302,6 +7408,11 @@ "ret": "0.1.15" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", @@ -7716,6 +7827,22 @@ "urix": "0.1.0" } }, + "source-map-support": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", + "integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", + "requires": { + "buffer-from": "1.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -7808,8 +7935,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.14.1", @@ -8735,6 +8861,11 @@ "crypto-random-string": "1.0.0" } }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index ba4055c47f..4425fdb0c9 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "core-js": "2.4.1", "electron-log": "2.2.14", "electron-store": "1.3.0", + "electron-updater": "2.21.4", "keytar": "4.1.0", "lunr": "2.1.6", "node-forge": "0.7.1", diff --git a/src/electron/updater.main.ts b/src/electron/updater.main.ts new file mode 100644 index 0000000000..012f169f49 --- /dev/null +++ b/src/electron/updater.main.ts @@ -0,0 +1,153 @@ +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) { + autoUpdater.logger = log; + + const linuxCanUpdate = process.platform === 'linux' && isAppImage(); + const windowsCanUpdate = process.platform === 'win32' && !isWindowsStore() && !isWindowsPortable(); + const macCanUpdate = process.platform === 'darwin' && !isMacAppStore(); + this.canUpdate = 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', () => { + if (this.doingUpdateCheckWithFeedback) { + if (this.windowMain.win == null) { + this.reset(); + return; + } + + const result = dialog.showMessageBox(this.windowMain.win, { + type: 'info', + title: 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 === 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', (info) => { + if (this.onUpdateDownloaded != null) { + this.onUpdateDownloaded(); + } + + if (this.windowMain.win == null) { + return; + } + + const result = dialog.showMessageBox(this.windowMain.win, { + type: 'info', + title: 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 === 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; + } +}