diff --git a/packages/uhk-agent/src/services/app-update.service.ts b/packages/uhk-agent/src/services/app-update.service.ts index 18f7514d72a..7aeeb2a8c22 100644 --- a/packages/uhk-agent/src/services/app-update.service.ts +++ b/packages/uhk-agent/src/services/app-update.service.ts @@ -46,14 +46,8 @@ export class AppUpdateService extends MainServiceBase { }); autoUpdater.on('update-available', (info: UpdateInfo) => { - this.logService.misc('[AppUpdateService] update available. Downloading started'); - autoUpdater.downloadUpdate() - .then(() => { - this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info); - }) - .catch((error) => { - this.logService.error('[AppUpdateService] Error when reporting update available: ', error); - }); + this.logService.misc('[AppUpdateService] update available'); + this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info); }); autoUpdater.on('update-not-available', (info: UpdateInfo) => { @@ -93,13 +87,18 @@ export class AppUpdateService extends MainServiceBase { return autoUpdater.quitAndInstall(true, true); }); + ipcMain.on(IpcEvents.autoUpdater.downloadUpdate, () => { + this.logService.misc('[AppUpdateService] download update from renderer process'); + this.downloadUpdate(); + }); + ipcMain.on(IpcEvents.app.appStarted, () => { this.logService.misc('[AppUpdateService] app started'); this.checkForUpdateAtStartup() .then((checkForUpdate) => { if (checkForUpdate) { this.sendAutoUpdateNotification = false; - this.logService.misc('[AppUpdateService] app started. Automatically check for update.'); + this.logService.misc('[AppUpdateService] app started. Check for new Agent version on startup.'); this.checkForUpdate(); } }) @@ -136,6 +135,13 @@ export class AppUpdateService extends MainServiceBase { }); } + private downloadUpdate(): void { + autoUpdater.downloadUpdate() + .catch((error) => { + this.logService.error('[AppUpdateService] Error when downloading update: ', error); + }); + } + private async checkForUpdateAtStartup() { const { checkForUpdateOnStartUp = true } = await this.getApplicationSettings(); diff --git a/packages/uhk-common/src/util/ipcEvents.ts b/packages/uhk-common/src/util/ipcEvents.ts index 0c8b05b524e..3d77152de6b 100644 --- a/packages/uhk-common/src/util/ipcEvents.ts +++ b/packages/uhk-common/src/util/ipcEvents.ts @@ -17,6 +17,7 @@ export class AutoUpdate { public static readonly autoUpdateDownloaded = 'update-downloaded'; public static readonly autoUpdateDownloadProgress = 'auto-update-download-progress'; public static readonly updateAndRestart = 'update-and-restart'; + public static readonly downloadUpdate = 'download-update'; public static readonly checkForUpdate = 'check-for-update'; public static readonly checkForUpdateNotAvailable = 'check-for-update-not-available'; } diff --git a/packages/uhk-web/src/app/app.component.html b/packages/uhk-web/src/app/app.component.html index ba2def56b50..1d74eec62c2 100644 --- a/packages/uhk-web/src/app/app.component.html +++ b/packages/uhk-web/src/app/app.component.html @@ -1,7 +1,6 @@
diff --git a/packages/uhk-web/src/app/app.component.ts b/packages/uhk-web/src/app/app.component.ts index 197d3b3f43f..7ce35888e6b 100644 --- a/packages/uhk-web/src/app/app.component.ts +++ b/packages/uhk-web/src/app/app.component.ts @@ -24,7 +24,6 @@ import { runningInElectron, saveToKeyboardState, keypressCapturing, - getUpdateInfo, firstAttemptOfSaveToKeyboard, isStatusBufferErrorHidden, getOutOfSpaceWaringData, @@ -33,7 +32,6 @@ import { import { StartDonglePairingAction } from './store/actions/dongle-pairing.action'; import { AddNewPairedDevicesToHostConnectionsAction } from './store/actions/user-config'; import { ProgressButtonState } from './store/reducers/progress-button-state'; -import { UpdateInfo } from './models/update-info'; import { CloseErrorPanelAction, ShowErrorPanelAction, @@ -113,7 +111,6 @@ export class MainAppComponent implements OnDestroy { newPairedDevicesState: BleAddingState; showFirmwareUpgradePanel: boolean; showUpdateAvailable: boolean; - updateInfo$: Observable; deviceConfigurationLoaded$: Observable; runningInElectron$: Observable; saveToKeyboardState: ProgressButtonState; @@ -188,7 +185,6 @@ export class MainAppComponent implements OnDestroy { this.showUpdateAvailable = data; this.cdRef.markForCheck(); }); - this.updateInfo$ = store.select(getUpdateInfo); this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded); this.runningInElectron$ = store.select(runningInElectron); this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState) diff --git a/packages/uhk-web/src/app/components/auto-update-settings/auto-update-settings.html b/packages/uhk-web/src/app/components/auto-update-settings/auto-update-settings.html index e6d5f9874ec..263c0317b0e 100644 --- a/packages/uhk-web/src/app/components/auto-update-settings/auto-update-settings.html +++ b/packages/uhk-web/src/app/components/auto-update-settings/auto-update-settings.html @@ -3,8 +3,7 @@
diff --git a/packages/uhk-web/src/app/components/update-available/update-available.component.html b/packages/uhk-web/src/app/components/update-available/update-available.component.html index 4b16299555b..fea5283d97f 100644 --- a/packages/uhk-web/src/app/components/update-available/update-available.component.html +++ b/packages/uhk-web/src/app/components/update-available/update-available.component.html @@ -1,15 +1,19 @@ diff --git a/packages/uhk-web/src/app/components/update-available/update-available.component.ts b/packages/uhk-web/src/app/components/update-available/update-available.component.ts index 2997e8dda7e..5ff4c915c8a 100644 --- a/packages/uhk-web/src/app/components/update-available/update-available.component.ts +++ b/packages/uhk-web/src/app/components/update-available/update-available.component.ts @@ -1,6 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; - -import { UpdateInfo } from '../../models/update-info'; +import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-update-available', @@ -10,7 +8,8 @@ import { UpdateInfo } from '../../models/update-info'; changeDetection: ChangeDetectionStrategy.OnPush }) export class UpdateAvailableComponent { - @Input() updateInfo: UpdateInfo; + protected readonly agentChangelogUrl = + 'https://github.com/UltimateHackingKeyboard/agent/blob/master/CHANGELOG.md'; @Output() updateApp = new EventEmitter(); @Output() doNotUpdateApp = new EventEmitter(); diff --git a/packages/uhk-web/src/app/services/app-update-renderer.service.ts b/packages/uhk-web/src/app/services/app-update-renderer.service.ts index 2eef84c5131..302e1b6e318 100644 --- a/packages/uhk-web/src/app/services/app-update-renderer.service.ts +++ b/packages/uhk-web/src/app/services/app-update-renderer.service.ts @@ -4,7 +4,7 @@ import { Action, Store } from '@ngrx/store'; import { IpcEvents, LogService } from 'uhk-common'; import { UpdateInfo } from '../models/update-info'; import { AppState } from '../store'; -import { UpdateDownloadedAction, UpdateErrorAction } from '../store/actions/app-update.action'; +import { UpdateAvailableAction, ClearUpdateAvailabilityAction, UpdateDownloadedAction, UpdateErrorAction } from '../store/actions/app-update.action'; import { CheckForUpdateFailedAction, CheckForUpdateSuccessAction } from '../store/actions/auto-update-settings'; import { IpcCommonRenderer } from './ipc-common-renderer'; @@ -34,18 +34,23 @@ export class AppUpdateRendererService { this.ipcRenderer.send(IpcEvents.autoUpdater.updateAndRestart); } + downloadUpdate(): void { + this.ipcRenderer.send(IpcEvents.autoUpdater.downloadUpdate); + } + checkForUpdate(allowPrerelease: boolean): void { this.ipcRenderer.send(IpcEvents.autoUpdater.checkForUpdate, allowPrerelease); } private registerEvents() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.ipcRenderer.on(IpcEvents.autoUpdater.updateAvailable, (event: string, arg: any) => { + this.ipcRenderer.on(IpcEvents.autoUpdater.updateAvailable, (event: string, arg: UpdateInfo) => { this.logService.misc(IpcEvents.autoUpdater.updateAvailable, arg); + this.dispatchStoreAction(new UpdateAvailableAction(arg)); }); this.ipcRenderer.on(IpcEvents.autoUpdater.updateNotAvailable, () => { this.logService.misc(IpcEvents.autoUpdater.updateNotAvailable); + this.dispatchStoreAction(new ClearUpdateAvailabilityAction()); this.dispatchStoreAction(new CheckForUpdateSuccessAction('No update available')); }); diff --git a/packages/uhk-web/src/app/store/actions/app-update.action.ts b/packages/uhk-web/src/app/store/actions/app-update.action.ts index c6f23fc290f..de94a013f3d 100644 --- a/packages/uhk-web/src/app/store/actions/app-update.action.ts +++ b/packages/uhk-web/src/app/store/actions/app-update.action.ts @@ -10,7 +10,9 @@ export enum ActionTypes { DoNotUpdateApp = '[app-update] do not update app', UpdateDownloaded = '[app-update] update downloaded', Updating = '[app-update] updating', - UpdateError = '[app-update] error' + UpdateError = '[app-update] error', + ResetUpdateDismiss = '[app-update] reset update dismiss', + ClearUpdateAvailability = '[app-update] clear update availability', } export class ForceUpdateAction implements Action { @@ -23,6 +25,9 @@ export class InvalidCodesignSignatureAction implements Action { export class UpdateAvailableAction implements Action { type = ActionTypes.UpdateAvailable; + + constructor(public payload: UpdateInfo) { + } } export class UpdateAppAction implements Action { @@ -33,6 +38,14 @@ export class DoNotUpdateAppAction implements Action { type = ActionTypes.DoNotUpdateApp; } +export class ResetUpdateDismissAction implements Action { + type = ActionTypes.ResetUpdateDismiss; +} + +export class ClearUpdateAvailabilityAction implements Action { + type = ActionTypes.ClearUpdateAvailability; +} + export class UpdateDownloadedAction implements Action { type = ActionTypes.UpdateDownloaded; @@ -52,10 +65,13 @@ export class UpdateErrorAction implements Action { } export type Actions - = UpdateAvailableAction + = ForceUpdateAction + | UpdateAvailableAction | InvalidCodesignSignatureAction | UpdateAppAction | DoNotUpdateAppAction + | ResetUpdateDismissAction + | ClearUpdateAvailabilityAction | UpdateDownloadedAction | UpdatingAction | UpdateErrorAction; diff --git a/packages/uhk-web/src/app/store/effects/app-update.ts b/packages/uhk-web/src/app/store/effects/app-update.ts index f3909cafaae..3e7df0e9c08 100644 --- a/packages/uhk-web/src/app/store/effects/app-update.ts +++ b/packages/uhk-web/src/app/store/effects/app-update.ts @@ -1,32 +1,38 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { EMPTY } from 'rxjs'; -import { first, map, tap, withLatestFrom } from 'rxjs/operators'; +import { filter, map, tap, withLatestFrom } from 'rxjs/operators'; import { ERR_UPDATER_INVALID_SIGNATURE, LogService, NotificationType } from 'uhk-common'; import { ActionTypes, + ClearUpdateAvailabilityAction, ForceUpdateAction, InvalidCodesignSignatureAction, + ResetUpdateDismissAction, UpdateAppAction, UpdateAvailableAction, + UpdateDownloadedAction, UpdateErrorAction } from '../actions/app-update.action'; import { ActionTypes as AutoUpdateActionTypes, CheckForUpdateNowAction } from '../actions/auto-update-settings'; import { ShowNotificationAction } from '../actions/app'; import { AppUpdateRendererService } from '../../services/app-update-renderer.service'; -import { AppState, isForceUpdate } from '../index'; +import { AppState, isForceUpdate, isUpdateDownloaded, isUpdateRequested } from '../index'; @Injectable() export class AppUpdateEffect { - appStart$ = createEffect(() => this.actions$ + updateApp$ = createEffect(() => this.actions$ .pipe( ofType(ActionTypes.UpdateApp), - first(), - tap(() => { - this.appUpdateRendererService.sendUpdateAndRestartApp(); + withLatestFrom(this.store.select(isUpdateDownloaded)), + tap(([, updateDownloaded]) => { + if (updateDownloaded) { + this.appUpdateRendererService.sendUpdateAndRestartApp(); + } else { + this.appUpdateRendererService.downloadUpdate(); + } }) ), { dispatch: false } @@ -35,6 +41,7 @@ export class AppUpdateEffect { checkForUpdate$ = createEffect(() => this.actions$ .pipe( ofType(AutoUpdateActionTypes.CheckForUpdateNow), + tap(() => this.store.dispatch(new ResetUpdateDismissAction())), map(action => action.payload), tap((allowPrerelease: boolean) => { this.logService.misc('[AppUpdateEffect] call checkForUpdate'); @@ -55,21 +62,31 @@ export class AppUpdateEffect { { dispatch: false } ); - updateAvailable$ = createEffect(() => this.actions$ + forceUpdateDownload$ = createEffect(() => this.actions$ .pipe( ofType(ActionTypes.UpdateAvailable), withLatestFrom(this.store.select(isForceUpdate)), - map(([, forceUpdate]) => { + tap(([, forceUpdate]) => { if (forceUpdate) { - return new UpdateAppAction(); + this.appUpdateRendererService.downloadUpdate(); } - - return EMPTY; }) ), { dispatch: false } ); + autoInstallAfterDownload$ = createEffect(() => this.actions$ + .pipe( + ofType(ActionTypes.UpdateDownloaded), + withLatestFrom( + this.store.select(isForceUpdate), + this.store.select(isUpdateRequested) + ), + filter(([, forceUpdate, updateRequested]) => forceUpdate || updateRequested), + map(() => new UpdateAppAction()) + ) + ); + handleError$ = createEffect(() => this.actions$ .pipe( ofType(ActionTypes.UpdateError), diff --git a/packages/uhk-web/src/app/store/index.ts b/packages/uhk-web/src/app/store/index.ts index 359e1157c14..d4a32c2155a 100644 --- a/packages/uhk-web/src/app/store/index.ts +++ b/packages/uhk-web/src/app/store/index.ts @@ -213,6 +213,8 @@ export const appUpdateState = (state: AppState) => state.appUpdate; export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable); export const getUpdateInfo = createSelector(appUpdateState, fromAppUpdate.getUpdateInfo); export const isForceUpdate = createSelector(appUpdateState, fromAppUpdate.isForceUpdate); +export const isUpdateRequested = createSelector(appUpdateState, fromAppUpdate.isUpdateRequested); +export const isUpdateDownloaded = createSelector(appUpdateState, fromAppUpdate.isUpdateDownloaded); export const appUpdateSettingsState = (state: AppState) => state.autoUpdateSettings; diff --git a/packages/uhk-web/src/app/store/reducers/app-update.reducer.ts b/packages/uhk-web/src/app/store/reducers/app-update.reducer.ts index f3a560a0079..fc6defc141e 100644 --- a/packages/uhk-web/src/app/store/reducers/app-update.reducer.ts +++ b/packages/uhk-web/src/app/store/reducers/app-update.reducer.ts @@ -1,11 +1,12 @@ import * as AppUpdate from '../actions/app-update.action'; import { UpdateInfo } from '../../models/update-info'; -import { UpdateDownloadedAction } from '../actions/app-update.action'; +import { UpdateAvailableAction, UpdateDownloadedAction } from '../actions/app-update.action'; export interface State { forceUpdate: boolean; updateAvailable: boolean; updateDownloaded: boolean; + updateRequested: boolean; doNotUpdateApp: boolean; updateInfo: UpdateInfo; } @@ -14,6 +15,7 @@ export const initialState: State = { forceUpdate: false, updateAvailable: false, updateDownloaded: false, + updateRequested: false, doNotUpdateApp: false, updateInfo: { isPrerelease: false, @@ -24,6 +26,20 @@ export const initialState: State = { export function reducer(state = initialState, action: AppUpdate.Actions) { switch (action.type) { + case AppUpdate.ActionTypes.ResetUpdateDismiss: + return { + ...state, + doNotUpdateApp: false + }; + + case AppUpdate.ActionTypes.ClearUpdateAvailability: + return { + ...state, + updateAvailable: false, + updateDownloaded: false, + updateRequested: false + }; + case AppUpdate.ActionTypes.ForceUpdate: return { ...state, @@ -33,17 +49,21 @@ export function reducer(state = initialState, action: AppUpdate.Actions) { case AppUpdate.ActionTypes.UpdateAvailable: return { ...state, - updateAvailable: true + updateAvailable: true, + updateInfo: (action as UpdateAvailableAction).payload + }; + + case AppUpdate.ActionTypes.UpdateApp: + return { + ...state, + updateRequested: !state.updateDownloaded }; case AppUpdate.ActionTypes.UpdateDownloaded: return { ...state, updateDownloaded: true, - updateInfo: (action as UpdateDownloadedAction).payload || { - isPrerelease: false, - version: '' - } + updateInfo: (action as UpdateDownloadedAction).payload ?? initialState.updateInfo }; case AppUpdate.ActionTypes.DoNotUpdateApp: @@ -52,11 +72,20 @@ export function reducer(state = initialState, action: AppUpdate.Actions) { doNotUpdateApp: true }; + case AppUpdate.ActionTypes.UpdateError: + return { + ...state, + updateRequested: false + }; + default: return state; } } -export const getShowAppUpdateAvailable = (state: State) => state.updateDownloaded && !state.doNotUpdateApp && !state.forceUpdate; +export const getShowAppUpdateAvailable = (state: State) => + state.updateAvailable && !state.doNotUpdateApp && !state.forceUpdate; export const getUpdateInfo = (state: State) => state.updateInfo; export const isForceUpdate = (state: State) => state.forceUpdate; +export const isUpdateRequested = (state: State) => state.updateRequested; +export const isUpdateDownloaded = (state: State) => state.updateDownloaded; diff --git a/packages/uhk-web/src/app/store/reducers/auto-update-settings.ts b/packages/uhk-web/src/app/store/reducers/auto-update-settings.ts index 70e28348607..ffedfca32ac 100644 --- a/packages/uhk-web/src/app/store/reducers/auto-update-settings.ts +++ b/packages/uhk-web/src/app/store/reducers/auto-update-settings.ts @@ -37,6 +37,8 @@ export function reducer(state = initialState, action: AutoUpdate.Actions | AppUp }; } + case AppUpdate.ActionTypes.UpdateAvailable: + case AppUpdate.ActionTypes.UpdateDownloaded: case AppUpdate.ActionTypes.UpdateError: case AutoUpdate.ActionTypes.CheckForUpdateSuccess: case AutoUpdate.ActionTypes.CheckForUpdateFailed: {