From a6d6e70b3dd35f83ddc6438f76dab723230910f8 Mon Sep 17 00:00:00 2001 From: Alice <53339016+AliceHincu@users.noreply.github.com> Date: Sun, 28 Jul 2024 14:09:30 +0300 Subject: [PATCH] Chore: Desktop: Seamless-Updates - creation of auto updater service (#10772) --- packages/app-desktop/ElectronAppWrapper.ts | 7 ++ packages/app-desktop/app.ts | 17 --- packages/app-desktop/package.json | 2 + .../autoUpdater/AutoUpdaterService.ts | 108 ++++++++++++++++++ .../services/autoUpdater/dev-app-update.yml | 7 ++ yarn.lock | 46 ++++++++ 6 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 packages/app-desktop/services/autoUpdater/AutoUpdaterService.ts create mode 100644 packages/app-desktop/services/autoUpdater/dev-app-update.yml diff --git a/packages/app-desktop/ElectronAppWrapper.ts b/packages/app-desktop/ElectronAppWrapper.ts index fc6deec94..945565be5 100644 --- a/packages/app-desktop/ElectronAppWrapper.ts +++ b/packages/app-desktop/ElectronAppWrapper.ts @@ -1,5 +1,6 @@ import Logger, { LoggerWrapper } from '@joplin/utils/Logger'; import { PluginMessage } from './services/plugins/PluginRunner'; +import AutoUpdaterService from './services/autoUpdater/AutoUpdaterService'; import shim from '@joplin/lib/shim'; import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils'; @@ -41,6 +42,7 @@ export default class ElectronAppWrapper { private rendererProcessQuitReply_: RendererProcessQuitReply = null; private pluginWindows_: PluginWindows = {}; private initialCallbackUrl_: string = null; + private updaterService_: AutoUpdaterService = null; private customProtocolHandler_: CustomProtocolHandler = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -474,6 +476,11 @@ export default class ElectronAppWrapper { this.createWindow(); + if (!shim.isLinux) { + this.updaterService_ = new AutoUpdaterService(); + this.updaterService_.startPeriodicUpdateCheck(); + } + this.electronApp_.on('before-quit', () => { this.willQuitApp_ = true; }); diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index 0282cb935..9c7a054f4 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -59,7 +59,6 @@ const globalCommands = appCommands.concat(libCommands); import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations'; import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService'; import ShareService from '@joplin/lib/services/share/ShareService'; -import checkForUpdates from './checkForUpdates'; import { AppState } from './app.reducer'; import syncDebugLog from '@joplin/lib/services/synchronizer/syncDebugLog'; import eventManager, { EventName } from '@joplin/lib/eventManager'; @@ -553,22 +552,6 @@ class Application extends BaseApplication { value: Setting.value('flagOpenDevTools'), }); - // Note: Auto-update is a misnomer in the code. - // The code below only checks, if a new version is available. - // We only allow Windows and macOS users to automatically check for updates - if (shim.isWindows() || shim.isMac()) { - const runAutoUpdateCheck = () => { - if (Setting.value('autoUpdateEnabled')) { - void checkForUpdates(true, bridge().window(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') }); - } - }; - - // Initial check on startup - shim.setTimeout(() => { runAutoUpdateCheck(); }, 5000); - // Then every x hours - shim.setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000); - } - initializeUserFetcher(); shim.setInterval(() => { void userFetcher(); }, 1000 * 60 * 60); diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index 0bad2e0af..eb424abd6 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -161,6 +161,8 @@ "compare-versions": "6.1.0", "countable": "3.0.1", "debounce": "1.2.1", + "electron-log": "5.1.6", + "electron-updater": "6.2.1", "electron-window-state": "5.0.3", "formatcoords": "1.1.3", "fs-extra": "11.2.0", diff --git a/packages/app-desktop/services/autoUpdater/AutoUpdaterService.ts b/packages/app-desktop/services/autoUpdater/AutoUpdaterService.ts new file mode 100644 index 000000000..35eefc4c0 --- /dev/null +++ b/packages/app-desktop/services/autoUpdater/AutoUpdaterService.ts @@ -0,0 +1,108 @@ +import { app } from 'electron'; +import { autoUpdater, UpdateInfo } from 'electron-updater'; +import log from 'electron-log'; +import path = require('path'); +import { setInterval } from 'timers'; + + +export enum AutoUpdaterEvents { + CheckingForUpdate = 'checking-for-update', + UpdateAvailable = 'update-available', + UpdateNotAvailable = 'update-not-available', + Error = 'error', + DownloadProgress = 'download-progress', + UpdateDownloaded = 'update-downloaded', +} + +const defaultUpdateInterval = 12 * 60 * 60 * 1000; +const initialUpdateStartup = 5 * 1000; + +export interface AutoUpdaterServiceInterface { + startPeriodicUpdateCheck(interval?: number): void; + stopPeriodicUpdateCheck(): void; + checkForUpdates(): void; +} + +export default class AutoUpdaterService implements AutoUpdaterServiceInterface { + private updatePollInterval_: ReturnType|null = null; + + public constructor() { + this.configureAutoUpdater(); + } + + public startPeriodicUpdateCheck = (interval: number = defaultUpdateInterval): void => { + this.stopPeriodicUpdateCheck(); + this.updatePollInterval_ = setInterval(() => { + void this.checkForUpdates(); + }, interval); + setTimeout(this.checkForUpdates, initialUpdateStartup); + }; + + public stopPeriodicUpdateCheck = (): void => { + if (this.updatePollInterval_) { + clearInterval(this.updatePollInterval_); + this.updatePollInterval_ = null; + } + }; + + public checkForUpdates = async (): Promise => { + try { + await autoUpdater.checkForUpdates(); // Use async/await + } catch (error) { + log.error('Failed to check for updates:', error); + if (error.message.includes('ERR_CONNECTION_REFUSED')) { + log.info('Server is not reachable. Will try again later.'); + } + } + }; + + private configureAutoUpdater = (): void => { + autoUpdater.logger = log; + log.transports.file.level = 'info'; + if (this.electronIsDev()) { + log.info('Development mode: using dev-app-update.yml'); + autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml'); + autoUpdater.forceDevUpdateConfig = true; + } + + autoUpdater.autoDownload = false; + + autoUpdater.on(AutoUpdaterEvents.CheckingForUpdate, this.onCheckingForUpdate); + autoUpdater.on(AutoUpdaterEvents.UpdateNotAvailable, this.onUpdateNotAvailable); + autoUpdater.on(AutoUpdaterEvents.UpdateAvailable, this.onUpdateAvailable); + autoUpdater.on(AutoUpdaterEvents.DownloadProgress, this.onDownloadProgress); + autoUpdater.on(AutoUpdaterEvents.UpdateDownloaded, this.onUpdateDownloaded); + autoUpdater.on(AutoUpdaterEvents.Error, this.onError); + }; + + private electronIsDev = (): boolean => !app.isPackaged; + + private onCheckingForUpdate = () => { + log.info('Checking for update...'); + }; + + private onUpdateNotAvailable = (_info: UpdateInfo): void => { + log.info('Update not available.'); + }; + + private onUpdateAvailable = (info: UpdateInfo): void => { + log.info(`Update available: ${info.version}.`); + }; + + private onDownloadProgress = (progressObj: { bytesPerSecond: number; percent: number; transferred: number; total: number }): void => { + log.info(`Download progress... ${progressObj.percent}% completed`); + }; + + private onUpdateDownloaded = (info: UpdateInfo): void => { + log.info('Update downloaded. It will be installed on restart.'); + void this.promptUserToUpdate(info); + }; + + private onError = (error: Error): void => { + log.error('Error in auto-updater.', error); + }; + + private promptUserToUpdate = async (info: UpdateInfo): Promise => { + log.info(`Update is available: ${info.version}.`); + }; +} diff --git a/packages/app-desktop/services/autoUpdater/dev-app-update.yml b/packages/app-desktop/services/autoUpdater/dev-app-update.yml new file mode 100644 index 000000000..4b82e6e5b --- /dev/null +++ b/packages/app-desktop/services/autoUpdater/dev-app-update.yml @@ -0,0 +1,7 @@ +# -- local sever +# provider: generic +# url: http://localhost:8080 +# -- github releases +provider: github +owner: laurent22 +repo: joplin \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4c4eb1331..b3e432604 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6783,6 +6783,8 @@ __metadata: debounce: 1.2.1 electron: 29.1.0 electron-builder: 24.13.3 + electron-log: 5.1.6 + electron-updater: 6.2.1 electron-window-state: 5.0.3 formatcoords: 1.1.3 fs-extra: 11.2.0 @@ -19959,6 +19961,13 @@ __metadata: languageName: node linkType: hard +"electron-log@npm:5.1.6": + version: 5.1.6 + resolution: "electron-log@npm:5.1.6" + checksum: 57f01fd535a2d6654e2dcde96afb85e2bd7433100e220a183a90b9d83ccd23468178188a4c88b3b312c2b6146962f4277910502d8acf02d94841cb5b28c3c65e + languageName: node + linkType: hard + "electron-publish@npm:24.13.1": version: 24.13.1 resolution: "electron-publish@npm:24.13.1" @@ -20016,6 +20025,22 @@ __metadata: languageName: node linkType: hard +"electron-updater@npm:6.2.1": + version: 6.2.1 + resolution: "electron-updater@npm:6.2.1" + dependencies: + builder-util-runtime: 9.2.4 + fs-extra: ^10.1.0 + js-yaml: ^4.1.0 + lazy-val: ^1.0.5 + lodash.escaperegexp: ^4.1.2 + lodash.isequal: ^4.5.0 + semver: ^7.3.8 + tiny-typed-emitter: ^2.1.0 + checksum: 92a064610a3c9df747dce9c3eccd69c48adb2c8b37b9d7c13d1c39f7e2a9ffaef4e909ab50e6973d557566bde6de7ec9c64e2cc3a2cdef18b3220c5d91333e1c + languageName: node + linkType: hard + "electron-window-state@npm:5.0.3": version: 5.0.3 resolution: "electron-window-state@npm:5.0.3" @@ -28797,6 +28822,13 @@ __metadata: languageName: node linkType: hard +"lodash.escaperegexp@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.escaperegexp@npm:4.1.2" + checksum: 6d99452b1cfd6073175a9b741a9b09ece159eac463f86f02ea3bee2e2092923fce812c8d2bf446309cc52d1d61bf9af51c8118b0d7421388e6cead7bd3798f0f + languageName: node + linkType: hard + "lodash.flatten@npm:^4.4.0": version: 4.4.0 resolution: "lodash.flatten@npm:4.4.0" @@ -28832,6 +28864,13 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: da27515dc5230eb1140ba65ff8de3613649620e8656b19a6270afe4866b7bd461d9ba2ac8a48dcc57f7adac4ee80e1de9f965d89d4d81a0ad52bb3eec2609644 + languageName: node + linkType: hard + "lodash.ismatch@npm:^4.4.0": version: 4.4.0 resolution: "lodash.ismatch@npm:4.4.0" @@ -41471,6 +41510,13 @@ __metadata: languageName: node linkType: hard +"tiny-typed-emitter@npm:^2.1.0": + version: 2.1.0 + resolution: "tiny-typed-emitter@npm:2.1.0" + checksum: 709bca410054e08df4dc29d5ea0916328bb2900d60245c6a743068ea223887d9fd2c945b6070eb20336275a557a36c2808e5c87d2ed4b60633458632be4a3e10 + languageName: node + linkType: hard + "tiny-warning@npm:^1.0.0": version: 1.0.3 resolution: "tiny-warning@npm:1.0.3"