diff --git a/src/common/application-update/application-update-status-channel.injectable.ts b/src/common/application-update/application-update-status-channel.injectable.ts index 8d92ca4070..1f68ab12ae 100644 --- a/src/common/application-update/application-update-status-channel.injectable.ts +++ b/src/common/application-update/application-update-status-channel.injectable.ts @@ -12,7 +12,8 @@ export type ApplicationUpdateStatusEventId = | "download-for-update-started" | "download-for-update-failed"; -export type ApplicationUpdateStatusChannel = Channel<{ eventId: ApplicationUpdateStatusEventId; version?: string }>; +export interface ApplicationUpdateStatusChannelMessage { eventId: ApplicationUpdateStatusEventId; version?: string } +export type ApplicationUpdateStatusChannel = Channel; const applicationUpdateStatusChannelInjectable = getInjectable({ id: "application-update-status-channel", diff --git a/src/main/update-app/check-for-platform-updates/check-for-platform-updates.injectable.ts b/src/main/update-app/check-for-platform-updates/check-for-platform-updates.injectable.ts index a22be24a1e..06ed1048fd 100644 --- a/src/main/update-app/check-for-platform-updates/check-for-platform-updates.injectable.ts +++ b/src/main/update-app/check-for-platform-updates/check-for-platform-updates.injectable.ts @@ -20,6 +20,7 @@ const checkForPlatformUpdatesInjectable = getInjectable({ return async (updateChannel) => { electronUpdater.channel = updateChannel.id; electronUpdater.autoDownload = false; + electronUpdater.allowDowngrade = false; let result: UpdateCheckResult; diff --git a/src/main/update-app/check-for-platform-updates/check-for-platform-updates.test.ts b/src/main/update-app/check-for-platform-updates/check-for-platform-updates.test.ts index 8dd51c7a98..77aadfd07a 100644 --- a/src/main/update-app/check-for-platform-updates/check-for-platform-updates.test.ts +++ b/src/main/update-app/check-for-platform-updates/check-for-platform-updates.test.ts @@ -28,6 +28,7 @@ describe("check-for-platform-updates", () => { electronUpdaterFake = { channel: undefined, autoDownload: undefined, + allowDowngrade: undefined, checkForUpdates: checkForUpdatesMock, } as unknown as AppUpdater; @@ -58,6 +59,10 @@ describe("check-for-platform-updates", () => { expect(electronUpdaterFake.channel).toBe("some-update-channel"); }); + it("sets flag for allowing downgrade", () => { + expect(electronUpdaterFake.allowDowngrade).toBe(false); + }); + it("disables auto downloading for being controlled", () => { expect(electronUpdaterFake.autoDownload).toBe(false); }); diff --git a/src/main/update-app/check-for-updates-tray-item.injectable.ts b/src/main/update-app/check-for-updates-tray-item.injectable.ts index 8a0c7816ca..5c5aea835f 100644 --- a/src/main/update-app/check-for-updates-tray-item.injectable.ts +++ b/src/main/update-app/check-for-updates-tray-item.injectable.ts @@ -7,17 +7,12 @@ import { computed } from "mobx"; import updatingIsEnabledInjectable from "./updating-is-enabled.injectable"; import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token"; import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable"; -import askBooleanInjectable from "../ask-boolean/ask-boolean.injectable"; -import quitAndInstallUpdateInjectable from "../electron-app/features/quit-and-install-update.injectable"; import discoveredUpdateVersionInjectable from "../../common/application-update/discovered-update-version/discovered-update-version.injectable"; import updateIsBeingDownloadedInjectable from "../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable"; import updatesAreBeingDiscoveredInjectable from "../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; -import checkForUpdatesInjectable from "./check-for-updates/check-for-updates.injectable"; -import downloadUpdateInjectable from "./download-update/download-update.injectable"; import progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable"; import assert from "assert"; -import { sendToAgnosticChannelInjectionToken } from "../../common/channel/send-to-agnostic-channel-injection-token"; -import applicationUpdateStatusChannelInjectable from "../../common/application-update/application-update-status-channel.injectable"; +import checkForUpdatesInjectable from "./check-for-updates/check-for-updates.injectable"; const checkForUpdatesTrayItemInjectable = getInjectable({ id: "check-for-updates-tray-item", @@ -26,15 +21,10 @@ const checkForUpdatesTrayItemInjectable = getInjectable({ const showApplicationWindow = di.inject(showApplicationWindowInjectable); const updatingIsEnabled = di.inject(updatingIsEnabledInjectable); const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable); - const askBoolean = di.inject(askBooleanInjectable); - const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable); const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable); const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable); const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable); const checkForUpdates = di.inject(checkForUpdatesInjectable); - const downloadUpdate = di.inject(downloadUpdateInjectable); - const sendToAgnosticChannel = di.inject(sendToAgnosticChannelInjectionToken); - const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable); return { id: "check-for-updates", @@ -62,31 +52,7 @@ const checkForUpdatesTrayItemInjectable = getInjectable({ visible: computed(() => updatingIsEnabled), click: async () => { - const { updateWasDiscovered, version } = await checkForUpdates(); - - if (updateWasDiscovered) { - sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "download-for-update-started", version }); - - // Note: intentional orphan promise to make download happen in the background - downloadUpdate().then(async ({ downloadWasSuccessful }) => { - - if (!downloadWasSuccessful) { - sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "download-for-update-failed" }); - - return; - } - - const userWantsToInstallUpdate = await askBoolean({ - id: "install-update", - title: "Update available", - question: `Version ${version} of Lens IDE is available and ready to be installed. Would you like to update now?`, - }); - - if (userWantsToInstallUpdate) { - quitAndInstallUpdate(); - } - }); - } + await checkForUpdates(); await showApplicationWindow(); }, diff --git a/src/main/update-app/check-for-updates/broadcast-change-in-updating-status.injectable.ts b/src/main/update-app/check-for-updates/broadcast-change-in-updating-status.injectable.ts new file mode 100644 index 0000000000..f6713d50e8 --- /dev/null +++ b/src/main/update-app/check-for-updates/broadcast-change-in-updating-status.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ApplicationUpdateStatusChannelMessage } from "../../../common/application-update/application-update-status-channel.injectable"; +import { sendToAgnosticChannelInjectionToken } from "../../../common/channel/send-to-agnostic-channel-injection-token"; +import applicationUpdateStatusChannelInjectable from "../../../common/application-update/application-update-status-channel.injectable"; + +const broadcastChangeInUpdatingStatusInjectable = getInjectable({ + id: "broadcast-change-in-updating-status", + + instantiate: (di) => { + const sendToAgnosticChannel = di.inject(sendToAgnosticChannelInjectionToken); + const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable); + + return (data: ApplicationUpdateStatusChannelMessage) => { + sendToAgnosticChannel(applicationUpdateStatusChannel, data); + }; + }, +}); + +export default broadcastChangeInUpdatingStatusInjectable; diff --git a/src/main/update-app/check-for-updates/check-for-updates-starting-from-channel.injectable.ts b/src/main/update-app/check-for-updates/check-for-updates-starting-from-channel.injectable.ts new file mode 100644 index 0000000000..a81f7c4947 --- /dev/null +++ b/src/main/update-app/check-for-updates/check-for-updates-starting-from-channel.injectable.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { UpdateChannel } from "../update-channels"; +import checkForPlatformUpdatesInjectable from "../check-for-platform-updates/check-for-platform-updates.injectable"; + +interface CheckForUpdatesFromChannelResult { + updateWasDiscovered: boolean; + version?: string; + actualUpdateChannel?: UpdateChannel; +} + +const checkForUpdatesStartingFromChannelInjectable = getInjectable({ + id: "check-for-updates-starting-from-channel", + + instantiate: (di) => { + const checkForPlatformUpdates = di.inject( + checkForPlatformUpdatesInjectable, + ); + + const _recursiveCheck = async ( + updateChannel: UpdateChannel, + ): Promise => { + const result = await checkForPlatformUpdates(updateChannel); + + if (result.updateWasDiscovered) { + return { + updateWasDiscovered: true, + version: result.version, + actualUpdateChannel: updateChannel, + }; + } + + if (updateChannel.moreStableUpdateChannel) { + return await _recursiveCheck(updateChannel.moreStableUpdateChannel); + } + + return { updateWasDiscovered: false }; + }; + + return _recursiveCheck; + }, +}); + +export default checkForUpdatesStartingFromChannelInjectable; diff --git a/src/main/update-app/check-for-updates/check-for-updates.injectable.ts b/src/main/update-app/check-for-updates/check-for-updates.injectable.ts index 8d06439566..5deb281e05 100644 --- a/src/main/update-app/check-for-updates/check-for-updates.injectable.ts +++ b/src/main/update-app/check-for-updates/check-for-updates.injectable.ts @@ -3,103 +3,91 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { CheckForPlatformUpdates } from "../check-for-platform-updates/check-for-platform-updates.injectable"; -import checkForPlatformUpdatesInjectable from "../check-for-platform-updates/check-for-platform-updates.injectable"; -import type { UpdateChannel } from "../update-channels"; import selectedUpdateChannelInjectable from "../selected-update-channel.injectable"; import updatesAreBeingDiscoveredInjectable from "../../../common/application-update/updates-are-being-discovered/updates-are-being-discovered.injectable"; import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable"; import { runInAction } from "mobx"; import assert from "assert"; -import applicationUpdateStatusChannelInjectable from "../../../common/application-update/application-update-status-channel.injectable"; -import { sendToAgnosticChannelInjectionToken } from "../../../common/channel/send-to-agnostic-channel-injection-token"; +import askBooleanInjectable from "../../ask-boolean/ask-boolean.injectable"; +import quitAndInstallUpdateInjectable from "../../electron-app/features/quit-and-install-update.injectable"; +import downloadUpdateInjectable from "../download-update/download-update.injectable"; +import broadcastChangeInUpdatingStatusInjectable from "./broadcast-change-in-updating-status.injectable"; +import checkForUpdatesStartingFromChannelInjectable from "./check-for-updates-starting-from-channel.injectable"; const checkForUpdatesInjectable = getInjectable({ id: "check-for-updates", instantiate: (di) => { + const askBoolean = di.inject(askBooleanInjectable); + const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable); + const downloadUpdate = di.inject(downloadUpdateInjectable); const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable); - const sendToAgnosticChannel = di.inject(sendToAgnosticChannelInjectionToken); - const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable); - - const checkForPlatformUpdates = di.inject( - checkForPlatformUpdatesInjectable, - ); - - const checkingForUpdatesState = di.inject( - updatesAreBeingDiscoveredInjectable, - ); - + const broadcastChangeInUpdatingStatus = di.inject(broadcastChangeInUpdatingStatusInjectable); + const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable); const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable); + const checkForUpdatesStartingFromChannel = di.inject(checkForUpdatesStartingFromChannelInjectable); return async () => { + broadcastChangeInUpdatingStatus({ eventId: "checking-for-updates" }); + runInAction(() => { checkingForUpdatesState.set(true); }); - const checkForUpdatesStartingFromChannel = - checkForUpdatesStartingFromChannelFor(checkForPlatformUpdates); - - sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "checking-for-updates" }); - const { updateWasDiscovered, version, actualUpdateChannel } = await checkForUpdatesStartingFromChannel(selectedUpdateChannel.value.get()); if (!updateWasDiscovered) { - sendToAgnosticChannel(applicationUpdateStatusChannel, { eventId: "no-updates-available" }); + broadcastChangeInUpdatingStatus({ eventId: "no-updates-available" }); + + runInAction(() => { + discoveredVersionState.set(null); + checkingForUpdatesState.set(false); + }); + + return; } + broadcastChangeInUpdatingStatus({ + eventId: "download-for-update-started", + version, + }); + runInAction(() => { - if (!updateWasDiscovered) { - discoveredVersionState.set(null); - } else { + // TODO: Unacceptable damage caused by strict mode + assert(version); + assert(actualUpdateChannel); - // TODO: Unacceptable damage caused by strict mode - assert(version); - assert(actualUpdateChannel); - - discoveredVersionState.set({ - version, - updateChannel: actualUpdateChannel, - }); - } + discoveredVersionState.set({ + version, + updateChannel: actualUpdateChannel, + }); checkingForUpdatesState.set(false); }); - return { updateWasDiscovered, version }; + // Note: intentional orphan promise to make download happen in the background + downloadUpdate().then(async ({ downloadWasSuccessful }) => { + if (!downloadWasSuccessful) { + broadcastChangeInUpdatingStatus({ + eventId: "download-for-update-failed", + }); + + return; + } + + const userWantsToInstallUpdate = await askBoolean({ + id: "install-update", + title: "Update Available", + question: `Version ${version} of Lens IDE is available and ready to be installed. Would you like to update now?`, + }); + + if (userWantsToInstallUpdate) { + quitAndInstallUpdate(); + } + }); }; }, }); export default checkForUpdatesInjectable; - -const checkForUpdatesStartingFromChannelFor = ( - checkForPlatformUpdates: CheckForPlatformUpdates, -) => { - const _recursiveCheck = async ( - updateChannel: UpdateChannel, - ): Promise<{ - updateWasDiscovered: boolean; - version?: string; - actualUpdateChannel?: UpdateChannel; - }> => { - const result = await checkForPlatformUpdates(updateChannel); - - if (result.updateWasDiscovered) { - return { - updateWasDiscovered: true, - version: result.version, - actualUpdateChannel: updateChannel, - }; - } - - if (updateChannel.moreStableUpdateChannel) { - return await _recursiveCheck(updateChannel.moreStableUpdateChannel); - } - - return { updateWasDiscovered: false }; - }; - - return _recursiveCheck; -};