Introduce way to install update directly from tray. Make application updater unit testable... (#5433)

* Extract product name as injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make tray items comply with Open Closed Principle

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Replace duplicated overrides with global

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add behaviour for navigating to preferences using tray

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce a tray item for updating application

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak naming

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak more naming

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant indirection

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak more naming

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce injectable for package.json being side-effect

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Relocate file to directory containing feature

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable for limiting side effect

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing injection token for implementation of tray item

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove resetting state for update is ready to be installed for being unclear

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kill dead code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make label of tray item reactive

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract updating is enabled to separate injectable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for tray

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Expand scope of behaviour for updating using tray also contain checking for updates

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove dead code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kill dead code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Implement checking of updates from multiple update channels

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Start installing updates automatically when quitting application

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Show application window when checking of updates has happened

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Show notifications and dialog for downloading update

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Implement naive notifications for version updates

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Implement checking of Electron specific updates as responsibility

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Implement downloading of Electron specific updates as responsibility

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce competition for channel abstraction

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant global override

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix typing after enabling strict mode

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce abstraction for a state that is shared between environments

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract states of application update to be usable from all environments

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Handle failing download of update

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make code for window visibility actually work

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate code for sending messages between processes to a window

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Split bloated dependency in smaller pieces

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make state of download progress accessible from all environments

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename files for accuracy

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move channel abstraction to more global directory

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Enhance typing of channels and sync-box

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate channel abstraction types

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Update asyncFn to support strict mode

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix snapshot after rebase

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing global override

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce injection token for channels to allow injecting all of them at once

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add notifications about change in update status

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename property for accuracy

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak code style

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make notifications unit testable in behaviours

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add implementation for asking boolean over processes

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reorganize responsibilities for checking updates

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Reorganize tests for installing update under separate scenarios

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make stuff happening when root frame is rendered unit testable

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce periodical check for updates

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Allow downgrading app versions

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using competition for checking of updates in application menu

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kill dead code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make test less prone to fail for wrong reason

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant boilerplate

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make tests for specific migrations less prone to failing for wrong reason

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move shared stuff under common

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using single source of truth for selected update channel

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Extract tests for installing update from different update channels to separate scenario

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing global override

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using release channel of installed application version as default value for selected update channel

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate usage of channel abstraction to same implementation

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make Channel abstraction support return values

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix direct calling of runnables

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Synchronize initial values of sync boxes when window starts

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add missing global override

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak message of question from user

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate names of directories

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add TODO

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove unimplemented scenario from test

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify test

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Improve name of test

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant overrides

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code style

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make Animate deterministic in unit tests

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Simplify naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify more naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify even more naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify more and more naming

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add todo for cleaning unacceptable code encountered

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Improve name of behaviour

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make unit test more strict

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Enhance name of behaviour

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce dependency to get random IDs

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make asking of boolean value from user not require explicit ID for question

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify code for asking of boolean value from user

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make setting of initial state for sync boxes not trigger irrelevant messaging to main

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make a channel have default type for sent and returned message

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce higher order function to log errors in decorated functions

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Export type for error logging

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak test name

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce higher order function for suppressing errors

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Relocate some explicit error handlings to proper level of abstraction

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make higher order function for logging errors support asynchronous rejecting with non error instance

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make overridden version of application exactly the one required by unit test

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Mark injectable causing side effects

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Revert not required changes

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make code for asserting a promise more strict

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make dependencies readonly

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove duplication for disposers

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Implement initial values for sync-boxes

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Separate concept of message and request channels

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce tests for requesting from channel in renderer

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Implement requesting from renderer in main

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Revert "Implement requesting from renderer in main"

This reverts commit d3e7899d7900516f3dbfacdb317a453202318305.

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak typing of request channel listeners to get rid of unexpected undefined

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove unused variable

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Tweak timing of sentry setup

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Require messages for MessageChannels be JsonValues for serialization

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Require requests and responses for RequestChannels be JsonValues for serialization

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make different MessageChannels not require explicit "extends JsonObject"

Note: Non-escaped lint breaks type here for forcing interface over type. Reasonable effort brought no understanding for what is the relevant difference between the two.

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make a primitive argument an object for readability

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make typing of higher order function for error suppression not lie

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Serialize messages in channels to make IPC not blow up

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce a way to make intentional orphan promises uniform, controllable and deliberate

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make downloading of update and what follows more deliberate as orphan promise

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Move utility function under directory

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Move another utility function under directory

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix incorrect name of file

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant code

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Kill dead code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Round percentage of update download progress in tray

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix rebase conflicts

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix CheckForUpdate type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
authored and GitHub committed 2022-06-03 11:21:36 +03:00
1 parent e1c1e00a2b
commit b414f9e06d
229 files changed
+8652 -1217

No files matched your search

@@ -0,0 +1,60 @@
/**
* 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 electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
import type { UpdateChannel } from "../../../common/application-update/update-channels";
import loggerInjectable from "../../../common/logger.injectable";
import type { UpdateCheckResult } from "electron-updater";
export type CheckForUpdatesResult = {
updateWasDiscovered: false;
} | {
updateWasDiscovered: true;
version: string;
};
export type CheckForPlatformUpdates = (updateChannel: UpdateChannel, opts: { allowDowngrade: boolean }) => Promise<CheckForUpdatesResult>;
const checkForPlatformUpdatesInjectable = getInjectable({
id: "check-for-platform-updates",
instantiate: (di): CheckForPlatformUpdates => {
const electronUpdater = di.inject(electronUpdaterInjectable);
const logger = di.inject(loggerInjectable);
return async (updateChannel, { allowDowngrade }) => {
electronUpdater.channel = updateChannel.id;
electronUpdater.autoDownload = false;
electronUpdater.allowDowngrade = allowDowngrade;
let result: UpdateCheckResult;
try {
result = await electronUpdater.checkForUpdates();
} catch (error) {
logger.error("[UPDATE-APP/CHECK-FOR-UPDATES]", error);
return {
updateWasDiscovered: false,
};
}
const { updateInfo, cancellationToken } = result;
if (!cancellationToken) {
return {
updateWasDiscovered: false,
};
}
return {
updateWasDiscovered: true,
version: updateInfo.version,
};
};
},
});
export default checkForPlatformUpdatesInjectable;
@@ -0,0 +1,128 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { AppUpdater, UpdateCheckResult } from "electron-updater";
import type { CheckForPlatformUpdates } from "./check-for-platform-updates.injectable";
import checkForPlatformUpdatesInjectable from "./check-for-platform-updates.injectable";
import type { UpdateChannel, UpdateChannelId } from "../../../common/application-update/update-channels";
import { getPromiseStatus } from "../../../common/test-utils/get-promise-status";
import loggerInjectable from "../../../common/logger.injectable";
import type { Logger } from "../../../common/logger";
describe("check-for-platform-updates", () => {
let checkForPlatformUpdates: CheckForPlatformUpdates;
let electronUpdaterFake: AppUpdater;
let checkForUpdatesMock: AsyncFnMock<() => UpdateCheckResult>;
let logErrorMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting();
checkForUpdatesMock = asyncFn();
electronUpdaterFake = {
channel: undefined,
autoDownload: undefined,
allowDowngrade: undefined,
checkForUpdates: checkForUpdatesMock,
} as unknown as AppUpdater;
di.override(electronUpdaterInjectable, () => electronUpdaterFake);
logErrorMock = jest.fn();
di.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
checkForPlatformUpdates = di.inject(checkForPlatformUpdatesInjectable);
});
describe("when called", () => {
let actualPromise: Promise<any>;
beforeEach(() => {
const testUpdateChannel: UpdateChannel = {
id: "some-update-channel" as UpdateChannelId,
label: "Some update channel",
moreStableUpdateChannel: null,
};
actualPromise = checkForPlatformUpdates(testUpdateChannel, { allowDowngrade: true });
});
it("sets update channel", () => {
expect(electronUpdaterFake.channel).toBe("some-update-channel");
});
it("sets flag for allowing downgrade", () => {
expect(electronUpdaterFake.allowDowngrade).toBe(true);
});
it("disables auto downloading for being controlled", () => {
expect(electronUpdaterFake.autoDownload).toBe(false);
});
it("checks for updates", () => {
expect(checkForUpdatesMock).toHaveBeenCalled();
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("when checking for updates resolves with update, resolves with the discovered update", async () => {
await checkForUpdatesMock.resolve({
updateInfo: {
version: "some-version",
},
cancellationToken: "some-cancellation-token",
} as unknown as UpdateCheckResult);
const actual = await actualPromise;
expect(actual).toEqual({ updateWasDiscovered: true, version: "some-version" });
});
it("when checking for updates resolves without update, resolves with update not being discovered", async () => {
await checkForUpdatesMock.resolve({
updateInfo: {
version: "some-version-that-matches-to-current-installed-version",
},
cancellationToken: null,
} as unknown as UpdateCheckResult);
const actual = await actualPromise;
expect(actual).toEqual({ updateWasDiscovered: false });
});
describe("when checking for updates rejects", () => {
let errorStub: Error;
beforeEach(() => {
errorStub = new Error("Some error");
checkForUpdatesMock.reject(errorStub);
});
it("logs errors", () => {
expect(logErrorMock).toHaveBeenCalledWith("[UPDATE-APP/CHECK-FOR-UPDATES]", errorStub);
});
it("resolves with update not being discovered", async () => {
const actual = await actualPromise;
expect(actual).toEqual({ updateWasDiscovered: false });
});
});
});
});
@@ -0,0 +1,79 @@
/**
* 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 { 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 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 progressOfUpdateDownloadInjectable from "../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
import assert from "assert";
import processCheckingForUpdatesInjectable from "./check-for-updates/process-checking-for-updates.injectable";
import { withErrorSuppression } from "../../common/utils/with-error-suppression/with-error-suppression";
import { pipeline } from "@ogre-tools/fp";
import withErrorLoggingInjectable from "../../common/utils/with-error-logging/with-error-logging.injectable";
const checkForUpdatesTrayItemInjectable = getInjectable({
id: "check-for-updates-tray-item",
instantiate: (di) => {
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable);
const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable);
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
return {
id: "check-for-updates",
parentId: null,
orderNumber: 30,
label: computed(() => {
if (downloadingUpdateState.value.get()) {
const discoveredVersion = discoveredVersionState.value.get();
assert(discoveredVersion);
const roundedPercentage = Math.round(progressOfUpdateDownload.value.get().percentage);
return `Downloading update ${discoveredVersion.version} (${roundedPercentage}%)...`;
}
if (checkingForUpdatesState.value.get()) {
return "Checking for updates...";
}
return "Check for updates";
}),
enabled: computed(() => !checkingForUpdatesState.value.get() && !downloadingUpdateState.value.get()),
visible: computed(() => updatingIsEnabled),
click: pipeline(
async () => {
await processCheckingForUpdates();
await showApplicationWindow();
},
withErrorLoggingFor(() => "[TRAY]: Checking for updates failed."),
// TODO: Find out how to improve typing so that instead of
// x => withErrorSuppression(x) there could only be withErrorSuppression
(x) => withErrorSuppression(x),
),
};
},
injectionToken: trayMenuItemInjectionToken,
});
export default checkForUpdatesTrayItemInjectable;
@@ -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 { messageToChannelInjectionToken } from "../../../common/utils/channel/message-to-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 messageToChannel = di.inject(messageToChannelInjectionToken);
const applicationUpdateStatusChannel = di.inject(applicationUpdateStatusChannelInjectable);
return (data: ApplicationUpdateStatusChannelMessage) => {
messageToChannel(applicationUpdateStatusChannel, data);
};
},
});
export default broadcastChangeInUpdatingStatusInjectable;
@@ -0,0 +1,54 @@
/**
* 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 "../../../common/application-update/update-channels";
import checkForPlatformUpdatesInjectable from "../check-for-platform-updates/check-for-platform-updates.injectable";
import updateCanBeDowngradedInjectable from "./update-can-be-downgraded.injectable";
export type CheckForUpdatesFromChannelResult = {
updateWasDiscovered: false;
} | {
updateWasDiscovered: true;
version: string;
actualUpdateChannel: UpdateChannel;
};
const checkForUpdatesStartingFromChannelInjectable = getInjectable({
id: "check-for-updates-starting-from-channel",
instantiate: (di) => {
const checkForPlatformUpdates = di.inject(
checkForPlatformUpdatesInjectable,
);
const updateCanBeDowngraded = di.inject(updateCanBeDowngradedInjectable);
const _recursiveCheck = async (
updateChannel: UpdateChannel,
): Promise<CheckForUpdatesFromChannelResult> => {
const result = await checkForPlatformUpdates(updateChannel, {
allowDowngrade: updateCanBeDowngraded.get(),
});
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;
@@ -0,0 +1,93 @@
/**
* 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 selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/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 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";
import withOrphanPromiseInjectable from "../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
const processCheckingForUpdatesInjectable = getInjectable({
id: "process-checking-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 broadcastChangeInUpdatingStatus = di.inject(broadcastChangeInUpdatingStatusInjectable);
const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
const checkForUpdatesStartingFromChannel = di.inject(checkForUpdatesStartingFromChannelInjectable);
const withOrphanPromise = di.inject(withOrphanPromiseInjectable);
return async () => {
broadcastChangeInUpdatingStatus({ eventId: "checking-for-updates" });
runInAction(() => {
checkingForUpdatesState.set(true);
});
const result = await checkForUpdatesStartingFromChannel(selectedUpdateChannel.value.get());
if (!result.updateWasDiscovered) {
broadcastChangeInUpdatingStatus({ eventId: "no-updates-available" });
runInAction(() => {
discoveredVersionState.set(null);
checkingForUpdatesState.set(false);
});
return;
}
const { version, actualUpdateChannel } = result;
broadcastChangeInUpdatingStatus({
eventId: "download-for-update-started",
version,
});
runInAction(() => {
discoveredVersionState.set({
version,
updateChannel: actualUpdateChannel,
});
checkingForUpdatesState.set(false);
});
withOrphanPromise(async () => {
const { downloadWasSuccessful } = await downloadUpdate();
if (!downloadWasSuccessful) {
broadcastChangeInUpdatingStatus({
eventId: "download-for-update-failed",
});
return;
}
const userWantsToInstallUpdate = await askBoolean({
title: "Update Available",
question: `Version ${version} of Lens IDE is available and ready to be installed. Would you like to update now?\n\n` +
`Lens should restart automatically, if it doesn't please restart manually. Installed extensions might require updating.`,
});
if (userWantsToInstallUpdate) {
quitAndInstallUpdate();
}
})();
};
},
});
export default processCheckingForUpdatesInjectable;
@@ -0,0 +1,29 @@
/**
* 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 { computed } from "mobx";
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import { SemVer } from "semver";
const updateCanBeDowngradedInjectable = getInjectable({
id: "update-can-be-downgraded",
instantiate: (di) => {
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
const appVersion = di.inject(appVersionInjectable);
return computed(() => {
const semVer = new SemVer(appVersion);
return (
semVer.prerelease[0] !==
selectedUpdateChannel.value.get().id
);
});
},
});
export default updateCanBeDowngradedInjectable;
@@ -0,0 +1,45 @@
/**
* 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 electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import type { ProgressInfo } from "electron-updater";
import type { ProgressOfDownload } from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
export type DownloadPlatformUpdate = (
onDownloadProgress: (arg: ProgressOfDownload) => void
) => Promise<{ downloadWasSuccessful: boolean }>;
const downloadPlatformUpdateInjectable = getInjectable({
id: "download-platform-update",
instantiate: (di): DownloadPlatformUpdate => {
const electronUpdater = di.inject(electronUpdaterInjectable);
const logger = di.inject(loggerInjectable);
return async (onDownloadProgress) => {
onDownloadProgress({ percentage: 0 });
const updateDownloadProgress = ({ percent: percentage }: ProgressInfo) =>
onDownloadProgress({ percentage });
electronUpdater.on("download-progress", updateDownloadProgress);
try {
await electronUpdater.downloadUpdate();
} catch(error) {
logger.error("[UPDATE-APP/DOWNLOAD]", error);
return { downloadWasSuccessful: false };
} finally {
electronUpdater.off("download-progress", updateDownloadProgress);
}
return { downloadWasSuccessful: true };
};
},
});
export default downloadPlatformUpdateInjectable;
@@ -0,0 +1,155 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import electronUpdaterInjectable from "../../electron-app/features/electron-updater.injectable";
import type { DownloadPlatformUpdate } from "./download-platform-update.injectable";
import downloadPlatformUpdateInjectable from "./download-platform-update.injectable";
import type { AppUpdater } from "electron-updater";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { getPromiseStatus } from "../../../common/test-utils/get-promise-status";
import type { DiContainer } from "@ogre-tools/injectable";
import loggerInjectable from "../../../common/logger.injectable";
import type { Logger } from "../../../common/logger";
describe("download-platform-update", () => {
let downloadPlatformUpdate: DownloadPlatformUpdate;
let downloadUpdateMock: AsyncFnMock<() => void>;
let electronUpdaterFake: AppUpdater;
let electronUpdaterOnMock: jest.Mock;
let electronUpdaterOffMock: jest.Mock;
let di: DiContainer;
let logErrorMock: jest.Mock;
beforeEach(() => {
di = getDiForUnitTesting();
downloadUpdateMock = asyncFn();
electronUpdaterOnMock = jest.fn();
electronUpdaterOffMock = jest.fn();
electronUpdaterFake = {
channel: undefined,
autoDownload: undefined,
on: electronUpdaterOnMock,
off: electronUpdaterOffMock,
downloadUpdate: downloadUpdateMock,
} as unknown as AppUpdater;
di.override(electronUpdaterInjectable, () => electronUpdaterFake);
logErrorMock = jest.fn();
di.override(loggerInjectable, () => ({ error: logErrorMock }) as unknown as Logger);
downloadPlatformUpdate = di.inject(downloadPlatformUpdateInjectable);
});
describe("when called", () => {
let actualPromise: Promise<{ downloadWasSuccessful: boolean }>;
let onDownloadProgressMock: jest.Mock;
beforeEach(() => {
onDownloadProgressMock = jest.fn();
actualPromise = downloadPlatformUpdate(onDownloadProgressMock);
});
it("calls for downloading of update", () => {
expect(downloadUpdateMock).toHaveBeenCalled();
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("starts progress of download from 0", () => {
expect(onDownloadProgressMock).toHaveBeenCalledWith({ percentage: 0 });
});
describe("when downloading progresses", () => {
beforeEach(() => {
onDownloadProgressMock.mockClear();
const [, callback] = electronUpdaterOnMock.mock.calls.find(
([event]) => event === "download-progress",
);
callback({
percent: 42,
total: 0,
delta: 0,
transferred: 0,
bytesPerSecond: 0,
});
});
it("updates progress of the download", () => {
expect(onDownloadProgressMock).toHaveBeenCalledWith({ percentage: 42 });
});
describe("when downloading resolves", () => {
beforeEach(async () => {
onDownloadProgressMock.mockClear();
await downloadUpdateMock.resolve();
});
it("resolves with success", async () => {
const actual = await actualPromise;
expect(actual).toEqual({ downloadWasSuccessful: true });
});
it("does not reset progress of download yet", () => {
expect(onDownloadProgressMock).not.toHaveBeenCalled();
});
it("stops watching for download progress", () => {
expect(electronUpdaterOffMock).toHaveBeenCalledWith(
"download-progress",
expect.any(Function),
);
});
it("when starting download again, resets progress of download", () => {
downloadPlatformUpdate(onDownloadProgressMock);
expect(onDownloadProgressMock).toHaveBeenCalledWith({ percentage: 0 });
});
});
describe("when downloading rejects", () => {
let errorStub: Error;
beforeEach(() => {
errorStub = new Error("Some error");
downloadUpdateMock.reject(errorStub);
});
it("logs error", () => {
expect(logErrorMock).toHaveBeenCalledWith("[UPDATE-APP/DOWNLOAD]", errorStub);
});
it("stops watching for download progress", () => {
expect(electronUpdaterOffMock).toHaveBeenCalledWith(
"download-progress",
expect.any(Function),
);
});
it("resolves with failure", async () => {
const actual = await actualPromise;
expect(actual).toEqual({ downloadWasSuccessful: false });
});
});
});
});
});
@@ -0,0 +1,49 @@
/**
* 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 downloadPlatformUpdateInjectable from "../download-platform-update/download-platform-update.injectable";
import updateIsBeingDownloadedInjectable from "../../../common/application-update/update-is-being-downloaded/update-is-being-downloaded.injectable";
import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable";
import { action, runInAction } from "mobx";
import type { ProgressOfDownload } from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
import progressOfUpdateDownloadInjectable from "../../../common/application-update/progress-of-update-download/progress-of-update-download.injectable";
const downloadUpdateInjectable = getInjectable({
id: "download-update",
instantiate: (di) => {
const downloadPlatformUpdate = di.inject(downloadPlatformUpdateInjectable);
const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable);
const updateDownloadProgress = action((progressOfDownload: ProgressOfDownload) => {
progressOfUpdateDownload.set(progressOfDownload);
});
return async () => {
runInAction(() => {
progressOfUpdateDownload.set({ percentage: 0 });
downloadingUpdateState.set(true);
});
const { downloadWasSuccessful } = await downloadPlatformUpdate(
updateDownloadProgress,
);
runInAction(() => {
if (!downloadWasSuccessful) {
discoveredVersionState.set(null);
}
downloadingUpdateState.set(false);
});
return { downloadWasSuccessful };
};
},
});
export default downloadUpdateInjectable;
@@ -0,0 +1,56 @@
/**
* 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 { computed } from "mobx";
import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token";
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 { withErrorSuppression } from "../../common/utils/with-error-suppression/with-error-suppression";
import { pipeline } from "@ogre-tools/fp";
import withErrorLoggingInjectable from "../../common/utils/with-error-logging/with-error-logging.injectable";
const installApplicationUpdateTrayItemInjectable = getInjectable({
id: "install-update-tray-item",
instantiate: (di) => {
const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
const downloadingUpdateState = di.inject(updateIsBeingDownloadedInjectable);
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
return {
id: "install-update",
parentId: null,
orderNumber: 50,
label: computed(() => {
const versionToBeInstalled = discoveredVersionState.value.get()?.version;
return `Install update ${versionToBeInstalled}`;
}),
enabled: computed(() => true),
visible: computed(
() => !!discoveredVersionState.value.get() && !downloadingUpdateState.value.get(),
),
click: pipeline(
quitAndInstallUpdate,
withErrorLoggingFor(() => "[TRAY]: Update installation failed."),
// TODO: Find out how to improve typing so that instead of
// x => withErrorSuppression(x) there could only be withErrorSuppression
(x) => withErrorSuppression(x),
),
};
},
injectionToken: trayMenuItemInjectionToken,
});
export default installApplicationUpdateTrayItemInjectable;
@@ -0,0 +1,35 @@
/**
* 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 { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
import processCheckingForUpdatesInjectable from "../check-for-updates/process-checking-for-updates.injectable";
const periodicalCheckForUpdatesInjectable = getInjectable({
id: "periodical-check-for-updates",
instantiate: (di) => {
const processCheckingForUpdates = di.inject(processCheckingForUpdatesInjectable);
return getStartableStoppable("periodical-check-for-updates", () => {
const TWO_HOURS = 1000 * 60 * 60 * 2;
// Note: intentional orphan promise to make checking for updates happen in the background
processCheckingForUpdates();
const intervalId = setInterval(() => {
// Note: intentional orphan promise to make checking for updates happen in the background
processCheckingForUpdates();
}, TWO_HOURS);
return () => {
clearInterval(intervalId);
};
});
},
causesSideEffects: true,
});
export default periodicalCheckForUpdatesInjectable;
@@ -0,0 +1,29 @@
/**
* 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 periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
import { afterRootFrameIsReadyInjectionToken } from "../../start-main-application/runnable-tokens/after-root-frame-is-ready-injection-token";
import updatingIsEnabledInjectable from "../updating-is-enabled.injectable";
const startCheckingForUpdatesInjectable = getInjectable({
id: "start-checking-for-updates",
instantiate: (di) => {
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
return {
run: async () => {
if (updatingIsEnabled) {
await periodicalCheckForUpdates.start();
}
},
};
},
injectionToken: afterRootFrameIsReadyInjectionToken,
});
export default startCheckingForUpdatesInjectable;
@@ -0,0 +1,27 @@
/**
* 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 { beforeQuitOfFrontEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-front-end-injection-token";
import periodicalCheckForUpdatesInjectable from "./periodical-check-for-updates.injectable";
const stopCheckingForUpdatesInjectable = getInjectable({
id: "stop-checking-for-updates",
instantiate: (di) => {
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
return {
run: async () => {
if (periodicalCheckForUpdates.started) {
await periodicalCheckForUpdates.stop();
}
},
};
},
injectionToken: beforeQuitOfFrontEndInjectionToken,
});
export default stopCheckingForUpdatesInjectable;
@@ -0,0 +1,20 @@
/**
* 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 packageJsonInjectable from "../../common/vars/package-json.injectable";
import { has } from "lodash/fp";
// TOOO: Rename to something less technical
const publishIsConfiguredInjectable = getInjectable({
id: "publish-is-configured",
instantiate: (di) => {
const packageJson = di.inject(packageJsonInjectable);
return has("build.publish", packageJson);
},
});
export default publishIsConfiguredInjectable;
@@ -0,0 +1,20 @@
/**
* 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 electronUpdaterIsActiveInjectable from "../electron-app/features/electron-updater-is-active.injectable";
import publishIsConfiguredInjectable from "./publish-is-configured.injectable";
const updatingIsEnabledInjectable = getInjectable({
id: "updating-is-enabled",
instantiate: (di) => {
const electronUpdaterIsActive = di.inject(electronUpdaterIsActiveInjectable);
const publishIsConfigured = di.inject(publishIsConfiguredInjectable);
return electronUpdaterIsActive && publishIsConfigured;
},
});
export default updatingIsEnabledInjectable;
@@ -0,0 +1,25 @@
/**
* 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 { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable";
const startWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({
id: "start-watching-if-update-should-happen-on-quit",
instantiate: (di) => {
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
return {
run: () => {
watchIfUpdateShouldHappenOnQuit.start();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startWatchingIfUpdateShouldHappenOnQuitInjectable;
@@ -0,0 +1,25 @@
/**
* 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 watchIfUpdateShouldHappenOnQuitInjectable from "./watch-if-update-should-happen-on-quit.injectable";
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
const stopWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({
id: "stop-watching-if-update-should-happen-on-quit",
instantiate: (di) => {
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
return {
run: () => {
watchIfUpdateShouldHappenOnQuit.stop();
},
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopWatchingIfUpdateShouldHappenOnQuitInjectable;
@@ -0,0 +1,54 @@
/**
* 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 { autorun } from "mobx";
import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
import setUpdateOnQuitInjectable from "../../electron-app/features/set-update-on-quit.injectable";
import selectedUpdateChannelInjectable from "../../../common/application-update/selected-update-channel/selected-update-channel.injectable";
import type { UpdateChannel } from "../../../common/application-update/update-channels";
import discoveredUpdateVersionInjectable from "../../../common/application-update/discovered-update-version/discovered-update-version.injectable";
const watchIfUpdateShouldHappenOnQuitInjectable = getInjectable({
id: "watch-if-update-should-happen-on-quit",
instantiate: (di) => {
const setUpdateOnQuit = di.inject(setUpdateOnQuitInjectable);
const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable);
const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable);
return getStartableStoppable("watch-if-update-should-happen-on-quit", () =>
autorun(() => {
const sufficientlyStableUpdateChannels =
getSufficientlyStableUpdateChannels(selectedUpdateChannel.value.get());
const discoveredVersion = discoveredVersionState.value.get();
const updateIsDiscoveredFromChannel = discoveredVersion?.updateChannel;
const updateOnQuit = updateIsDiscoveredFromChannel
? sufficientlyStableUpdateChannels.includes(
updateIsDiscoveredFromChannel,
)
: false;
setUpdateOnQuit(updateOnQuit);
}),
);
},
});
const getSufficientlyStableUpdateChannels = (updateChannel: UpdateChannel): UpdateChannel[] => {
if (!updateChannel.moreStableUpdateChannel) {
return [updateChannel];
}
return [
updateChannel,
...getSufficientlyStableUpdateChannels(updateChannel.moreStableUpdateChannel),
];
};
export default watchIfUpdateShouldHappenOnQuitInjectable;