diff --git a/packages/core/src/common/persistent-storage/create.injectable.ts b/packages/core/src/common/persistent-storage/create.injectable.ts index 47c7b159b5..63da72f413 100644 --- a/packages/core/src/common/persistent-storage/create.injectable.ts +++ b/packages/core/src/common/persistent-storage/create.injectable.ts @@ -22,6 +22,10 @@ import { shouldPersistentStorageDisableSyncInIpcListenerInjectionToken } from ". import { persistStateToConfigInjectionToken } from "./save-to-file"; export interface PersistentStorage { + /** + * This method does the initial synchronous load from disk and then starts writing the state + * back to disk whenever it changes. + */ loadAndStartSyncing: () => void; } diff --git a/packages/core/src/common/persistent-storage/storage-migration-version.injectable.ts b/packages/core/src/common/persistent-storage/storage-migration-version.injectable.ts new file mode 100644 index 0000000000..6568e8f983 --- /dev/null +++ b/packages/core/src/common/persistent-storage/storage-migration-version.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { InjectionToken } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import * as semver from "semver"; +import type { MigrationDeclaration } from "./migrations.injectable"; + +/** + * NOTE: not all stores can use this computed version, namely if any migration uses a range for + * the version selector. + */ +const storageMigrationVersionInjectable = getInjectable({ + id: "storage-migration-version", + instantiate: (di, token) => { + const declarations = di.injectMany(token); + + return declarations.reduce((version, decl) => { + if (semver.gte(decl.version, version)) { + return decl.version; + } + + return version; + }, "1.0.0"); + }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, token: InjectionToken) => token.id, + }), +}); + +export default storageMigrationVersionInjectable; diff --git a/packages/core/src/common/protocol-handler/router.ts b/packages/core/src/common/protocol-handler/router.ts index 950020d793..7a48dbb343 100644 --- a/packages/core/src/common/protocol-handler/router.ts +++ b/packages/core/src/common/protocol-handler/router.ts @@ -16,7 +16,7 @@ import type { RouteHandler, RouteParams } from "./registration"; import { when } from "mobx"; import { ipcRenderer } from "electron"; import type { Logger } from "../logger"; -import type { EnabledExtensionsState } from "../../extensions/enabled-extensions-state.injectable"; +import type { IsExtensionEnabled } from "../../features/extensions/enabled/common/is-enabled.injectable"; // IPC channel for protocol actions. Main broadcasts the open-url events to this channel. export const ProtocolHandlerIpcPrefix = "protocol-handler"; @@ -65,8 +65,8 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R export interface LensProtocolRouterDependencies { readonly extensionLoader: ExtensionLoader; - readonly enabledExtensionsState: EnabledExtensionsState; readonly logger: Logger; + isExtensionEnabled: IsExtensionEnabled; } export abstract class LensProtocolRouter { @@ -209,7 +209,7 @@ export abstract class LensProtocolRouter { return name; } - if (!this.dependencies.enabledExtensionsState.isEnabled(extension)) { + if (!this.dependencies.isExtensionEnabled(extension)) { this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`); return name; diff --git a/packages/core/src/extensions/__tests__/extension-loader.test.ts b/packages/core/src/extensions/__tests__/extension-loader.test.ts index 4ded8cdbb9..31c019e733 100644 --- a/packages/core/src/extensions/__tests__/extension-loader.test.ts +++ b/packages/core/src/extensions/__tests__/extension-loader.test.ts @@ -6,13 +6,13 @@ import type { ExtensionLoader } from "../extension-loader"; import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable"; import { runInAction } from "mobx"; -import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable"; import { delay } from "@k8slens/utilities"; import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting"; import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable"; import type { IpcRenderer } from "electron"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import currentlyInClusterFrameInjectable from "../../renderer/routes/currently-in-cluster-frame.injectable"; +import updateExtensionsStateInjectable from "../../features/extensions/enabled/common/update-state.injectable"; const manifestPath = "manifest/path"; const manifestPath2 = "manifest/path2"; diff --git a/packages/core/src/extensions/common-api/app.ts b/packages/core/src/extensions/common-api/app.ts index c25848539a..26bfaafef6 100644 --- a/packages/core/src/extensions/common-api/app.ts +++ b/packages/core/src/extensions/common-api/app.ts @@ -13,10 +13,9 @@ import { issuesTrackerUrl } from "../../common/vars"; import { buildVersionInjectionToken } from "../../common/vars/build-semantic-version.injectable"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; import userStoreInjectable from "../../common/user-store/user-store.injectable"; -import enabledExtensionsInjectable from "./get-enabled-extensions/get-enabled-extensions.injectable"; +import enabledExtensionsInjectable from "../../features/extensions/enabled/common/enabled-extensions.injectable"; const userStore = asLegacyGlobalForExtensionApi(userStoreInjectable); - const enabledExtensions = asLegacyGlobalForExtensionApi(enabledExtensionsInjectable); export const App = { diff --git a/packages/core/src/extensions/common-api/get-enabled-extensions/get-enabled-extensions.injectable.ts b/packages/core/src/extensions/common-api/get-enabled-extensions/get-enabled-extensions.injectable.ts deleted file mode 100644 index 7460672ee8..0000000000 --- a/packages/core/src/extensions/common-api/get-enabled-extensions/get-enabled-extensions.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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 enabledExtensionsStateInjectable from "../../enabled-extensions-state.injectable"; - -const enabledExtensionsInjectable = getInjectable({ - id: "enabled-extensions", - instantiate: (di) => di.inject(enabledExtensionsStateInjectable).enabledExtensions, -}); - -export default enabledExtensionsInjectable; diff --git a/packages/core/src/extensions/enabled-extensions-state.injectable.ts b/packages/core/src/extensions/enabled-extensions-state.injectable.ts deleted file mode 100644 index d8997fa0aa..0000000000 --- a/packages/core/src/extensions/enabled-extensions-state.injectable.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { LensExtensionId } from "@k8slens/legacy-extensions"; -import { iter, object } from "@k8slens/utilities"; -import { getInjectable } from "@ogre-tools/injectable"; -import type { IComputedValue } from "mobx"; -import { action, computed, observable } from "mobx"; -import createPersistentStorageInjectable from "../common/persistent-storage/create.injectable"; -import storeMigrationVersionInjectable from "../common/vars/store-migration-version.injectable"; - -export interface LensExtensionState { - enabled?: boolean; - name: string; -} - -export interface IsEnabledExtensionDescriptor { - id: string; - isBundled: boolean; -} - -export interface EnabledExtensionsState { - readonly enabledExtensions: IComputedValue; - isEnabled: (desc: IsEnabledExtensionDescriptor) => boolean; - mergeState: (newPartialState: Partial> | [LensExtensionId, LensExtensionState][]) => void; -} - -const enabledExtensionsStateInjectable = getInjectable({ - id: "enabled-extensions-state", - instantiate: (di): EnabledExtensionsState => { - const storeMigrationVersion = di.inject(storeMigrationVersionInjectable); - const createPersistentStorage = di.inject(createPersistentStorageInjectable); - - const state = observable.map(); - const storage = createPersistentStorage({ - configName: "lens-extensions", - fromStore: action(({ extensions = {}}) => { - state.merge(extensions); - }), - toJSON: () => ({ - extensions: Object.fromEntries(state), - }), - projectVersion: storeMigrationVersion, - }); - - // NOTE: this is done implicitly here currently - storage.loadAndStartSyncing(); - - return { - enabledExtensions: computed(() => ( - iter.chain(state.values()) - .filter(({ enabled }) => enabled) - .map(({ name }) => name) - .toArray() - )), - isEnabled: ({ id, isBundled }) => isBundled || (state.get(id)?.enabled ?? false), - mergeState: action((newPartialState) => { - if (Array.isArray(newPartialState)) { - state.merge(newPartialState); - } else { - state.merge(object.entries(newPartialState)); - } - }), - }; - }, -}); - -export default enabledExtensionsStateInjectable; diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts index 59695cf0f2..92b1c8cf77 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionDiscovery } from "./extension-discovery"; import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable"; import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable"; -import enabledExtensionsStateInjectable from "../enabled-extensions-state.injectable"; import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable"; import installExtensionInjectable from "../install-extension/install-extension.injectable"; import extensionPackageRootDirectoryInjectable from "../install-extension/extension-package-root-directory.injectable"; @@ -28,13 +27,14 @@ import joinPathsInjectable from "../../common/path/join-paths.injectable"; import removePathInjectable from "../../common/fs/remove.injectable"; import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable"; import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable"; +import isExtensionEnabledInjectable from "../../features/extensions/enabled/common/is-enabled.injectable"; const extensionDiscoveryInjectable = getInjectable({ id: "extension-discovery", instantiate: (di) => new ExtensionDiscovery({ extensionLoader: di.inject(extensionLoaderInjectable), - enabledExtensionsState: di.inject(enabledExtensionsStateInjectable), + isExtensionEnabled: di.inject(isExtensionEnabledInjectable), extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable), isCompatibleExtension: di.inject(isCompatibleExtensionInjectable), installExtension: di.inject(installExtensionInjectable), diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.ts index 2c8c4d9fc1..7a39fa7467 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.ts @@ -30,11 +30,10 @@ import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable" import type { GetRelativePath } from "../../common/path/get-relative-path.injectable"; import type { RemovePath } from "../../common/fs/remove.injectable"; import type TypedEventEmitter from "typed-emitter"; -import type { EnabledExtensionsState } from "../enabled-extensions-state.injectable"; +import type { IsExtensionEnabled } from "../../features/extensions/enabled/common/is-enabled.injectable"; interface Dependencies { readonly extensionLoader: ExtensionLoader; - readonly enabledExtensionsState: EnabledExtensionsState; readonly extensionInstallationStateStore: ExtensionInstallationStateStore; readonly extensionPackageRootDirectory: string; readonly resourcesDirectory: string; @@ -42,6 +41,7 @@ interface Dependencies { readonly isProduction: boolean; readonly fileSystemSeparator: string; readonly homeDirectoryPath: string; + isExtensionEnabled: IsExtensionEnabled; isCompatibleExtension: (manifest: LensExtensionManifest) => boolean; installExtension: (name: string) => Promise; readJsonFile: ReadJson; @@ -334,7 +334,7 @@ export class ExtensionDiscovery { try { const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest; const id = isBundled ? manifestPath : this.getInstalledManifestPath(manifest.name); - const isEnabled = this.dependencies.enabledExtensionsState.isEnabled({ id, isBundled }); + const isEnabled = this.dependencies.isExtensionEnabled({ id, isBundled }); const extensionDir = this.dependencies.getDirnameOfPath(manifestPath); const npmPackage = this.dependencies.joinPaths(extensionDir, `${manifest.name}-${manifest.version}.tgz`); const absolutePath = this.dependencies.isProduction && await this.dependencies.pathExists(npmPackage) diff --git a/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts b/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts index e635a6cca1..12187c38c0 100644 --- a/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts +++ b/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts @@ -4,7 +4,6 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionLoader } from "./extension-loader"; -import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable"; import { createExtensionInstanceInjectionToken } from "./create-extension-instance.token"; import extensionInstancesInjectable from "./extension-instances.injectable"; import type { LensExtension } from "../lens-extension"; @@ -14,6 +13,7 @@ import joinPathsInjectable from "../../common/path/join-paths.injectable"; import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable"; import { bundledExtensionInjectionToken } from "@k8slens/legacy-extensions"; import { extensionEntryPointNameInjectionToken } from "./entry-point-name"; +import updateExtensionsStateInjectable from "../../features/extensions/enabled/common/update-state.injectable"; const extensionLoaderInjectable = getInjectable({ id: "extension-loader", diff --git a/packages/core/src/extensions/extension-loader/extension-loader.ts b/packages/core/src/extensions/extension-loader/extension-loader.ts index eec5924dab..4a24ad23a1 100644 --- a/packages/core/src/extensions/extension-loader/extension-loader.ts +++ b/packages/core/src/extensions/extension-loader/extension-loader.ts @@ -20,7 +20,7 @@ import type { Logger } from "../../common/logger"; import type { JoinPaths } from "../../common/path/join-paths.injectable"; import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable"; import type { LensExtensionId, BundledExtension, InstalledExtension, LensExtensionConstructor } from "@k8slens/legacy-extensions"; -import type { LensExtensionState } from "../enabled-extensions-state.injectable"; +import type { UpdateExtensionsState } from "../../features/extensions/enabled/common/update-state.injectable"; const logModule = "[EXTENSIONS-LOADER]"; @@ -29,7 +29,7 @@ interface Dependencies { readonly bundledExtensions: BundledExtension[]; readonly logger: Logger; readonly extensionEntryPointName: "main" | "renderer"; - updateExtensionsState: (extensionsState: Record) => void; + updateExtensionsState: UpdateExtensionsState; createExtensionInstance: CreateExtensionInstance; getExtension: (instance: LensExtension) => Extension; joinPaths: JoinPaths; @@ -125,13 +125,11 @@ export class ExtensionLoader { // Transform userExtensions to a state object for storing into ExtensionsStore @computed get storeState() { - return Object.fromEntries( - Array.from(this.userExtensions) - .map(([extId, extension]) => [extId, { - enabled: extension.isEnabled, - name: extension.manifest.name, - }]), - ); + return Array.from(this.userExtensions) + .map(([extId, extension]) => [extId, { + enabled: extension.isEnabled, + name: extension.manifest.name, + }] as const); } @action diff --git a/packages/core/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts b/packages/core/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts deleted file mode 100644 index 10f35bfc32..0000000000 --- a/packages/core/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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 enabledExtensionsStateInjectable from "../../enabled-extensions-state.injectable"; - -const updateExtensionsStateInjectable = getInjectable({ - id: "update-extensions-state", - instantiate: (di) => di.inject(enabledExtensionsStateInjectable).mergeState, -}); - -export default updateExtensionsStateInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/enabled-extensions.injectable.ts b/packages/core/src/features/extensions/enabled/common/enabled-extensions.injectable.ts new file mode 100644 index 0000000000..0c6afbb493 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/enabled-extensions.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { iter } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import enabledExtensionsStateInjectable from "./state.injectable"; + +const enabledExtensionsInjectable = getInjectable({ + id: "enabled-extensions", + instantiate: (di) => { + const state = di.inject(enabledExtensionsStateInjectable); + + return computed(() => ( + iter.chain(state.values()) + .filter(({ enabled }) => enabled) + .map(({ name }) => name) + .toArray() + )); + }, +}); + +export default enabledExtensionsInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/is-enabled.injectable.ts b/packages/core/src/features/extensions/enabled/common/is-enabled.injectable.ts new file mode 100644 index 0000000000..bb7f531cb3 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/is-enabled.injectable.ts @@ -0,0 +1,24 @@ +/** + * 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 enabledExtensionsStateInjectable from "./state.injectable"; + +export interface IsEnabledExtensionDescriptor { + readonly id: string; + readonly isBundled: boolean; +} + +export type IsExtensionEnabled = (desc: IsEnabledExtensionDescriptor) => boolean; + +const isExtensionEnabledInjectable = getInjectable({ + id: "is-extension-enabled", + instantiate: (di): IsExtensionEnabled => { + const state = di.inject(enabledExtensionsStateInjectable); + + return ({ id, isBundled }) => isBundled || (state.get(id)?.enabled ?? false); + }, +}); + +export default isExtensionEnabledInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/migrations.ts b/packages/core/src/features/extensions/enabled/common/migrations.ts new file mode 100644 index 0000000000..eef1c4c996 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/migrations.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable"; + +export const enabledExtensionsMigrationDeclarationInjectionToken = getInjectionToken({ + id: "enabled-extensions-migration-declaration", +}); diff --git a/packages/core/src/features/extensions/enabled/common/state.injectable.ts b/packages/core/src/features/extensions/enabled/common/state.injectable.ts new file mode 100644 index 0000000000..6cf87ace88 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/state.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; + +export interface LensExtensionState { + enabled?: boolean; + name: string; +} + +const enabledExtensionsStateInjectable = getInjectable({ + id: "enabled-extensions-state", + instantiate: () => observable.map(), +}); + +export default enabledExtensionsStateInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/storage.injectable.ts b/packages/core/src/features/extensions/enabled/common/storage.injectable.ts new file mode 100644 index 0000000000..47a9930ccf --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/storage.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import { getInjectable } from "@ogre-tools/injectable"; +import { action, toJS } from "mobx"; +import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable"; +import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable"; +import storageMigrationVersionInjectable from "../../../../common/persistent-storage/storage-migration-version.injectable"; +import { enabledExtensionsMigrationDeclarationInjectionToken } from "./migrations"; +import type { LensExtensionState } from "./state.injectable"; +import enabledExtensionsStateInjectable from "./state.injectable"; + +interface EnabledExtensionsStorageModal { + extensions: [LensExtensionId, LensExtensionState][]; +} + +const enabledExtensionsPersistentStorageInjectable = getInjectable({ + id: "enabled-extensions-persistent-storage", + instantiate: (di) => { + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + const state = di.inject(enabledExtensionsStateInjectable); + + return createPersistentStorage({ + configName: "lens-extensions", + fromStore: action(({ extensions = [] }) => { + state.replace(extensions); + }), + toJSON: () => ({ + extensions: [...toJS(state)], + }), + projectVersion: di.inject(storageMigrationVersionInjectable, enabledExtensionsMigrationDeclarationInjectionToken), + migrations: di.inject(persistentStorageMigrationsInjectable, enabledExtensionsMigrationDeclarationInjectionToken), + }); + }, +}); + +export default enabledExtensionsPersistentStorageInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/update-state.injectable.ts b/packages/core/src/features/extensions/enabled/common/update-state.injectable.ts new file mode 100644 index 0000000000..e12c2d32d9 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/update-state.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 type { LensExtensionId } from "@k8slens/legacy-extensions"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { IObservableMapInitialValues } from "mobx"; +import { action } from "mobx"; +import type { LensExtensionState } from "./state.injectable"; +import enabledExtensionsStateInjectable from "./state.injectable"; + +export type UpdateExtensionsState = (state: IObservableMapInitialValues) => void; + +const updateExtensionsStateInjectable = getInjectable({ + id: "update-extensions-state", + instantiate: (di): UpdateExtensionsState => { + const state = di.inject(enabledExtensionsStateInjectable); + + return action((newState) => state.merge(newState)); + }, +}); + +export default updateExtensionsStateInjectable; diff --git a/packages/core/src/features/extensions/enabled/main/load-storage.injectable.ts b/packages/core/src/features/extensions/enabled/main/load-storage.injectable.ts new file mode 100644 index 0000000000..e3c301627b --- /dev/null +++ b/packages/core/src/features/extensions/enabled/main/load-storage.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application"; +import { getInjectable } from "@ogre-tools/injectable"; +import enabledExtensionsPersistentStorageInjectable from "../common/storage.injectable"; + +const loadEnabledExtensionsStorageInjectable = getInjectable({ + id: "load-enabled-extensions-storage", + instantiate: (di) => ({ + run: () => { + const storage = di.inject(enabledExtensionsPersistentStorageInjectable); + + storage.loadAndStartSyncing(); + }, + }), + injectionToken: beforeApplicationIsLoadingInjectionToken, +}); + +export default loadEnabledExtensionsStorageInjectable; diff --git a/packages/core/src/features/extensions/enabled/main/v6.5.0-migration.injectable.ts b/packages/core/src/features/extensions/enabled/main/v6.5.0-migration.injectable.ts new file mode 100644 index 0000000000..57cdc12df2 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/main/v6.5.0-migration.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { isObject } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { enabledExtensionsMigrationDeclarationInjectionToken } from "../common/migrations"; + +const enabledExtensionsMigrationV650Injectable = getInjectable({ + id: "enabled-extensions-migration-v650", + instantiate: () => ({ + version: "6.5.0", + run: (store) => { + const extensions = store.get("extensions"); + + if (!isObject(extensions)) { + store.set("extensions", undefined); + } else { + store.set("extensions", Object.entries(extensions)); + } + }, + }), + injectionToken: enabledExtensionsMigrationDeclarationInjectionToken, +}); + +export default enabledExtensionsMigrationV650Injectable; diff --git a/packages/core/src/features/extensions/enabled/renderer/load-storage.injectable.ts b/packages/core/src/features/extensions/enabled/renderer/load-storage.injectable.ts new file mode 100644 index 0000000000..50be3cd88d --- /dev/null +++ b/packages/core/src/features/extensions/enabled/renderer/load-storage.injectable.ts @@ -0,0 +1,21 @@ +/** + * 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 { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import enabledExtensionsPersistentStorageInjectable from "../common/storage.injectable"; + +const loadEnabledExtensionsStorageInjectable = getInjectable({ + id: "load-enabled-extensions-storage", + instantiate: (di) => ({ + run: () => { + const storage = di.inject(enabledExtensionsPersistentStorageInjectable); + + storage.loadAndStartSyncing(); + }, + }), + injectionToken: beforeFrameStartsSecondInjectionToken, +}); + +export default loadEnabledExtensionsStorageInjectable; diff --git a/packages/core/src/main/protocol-handler/__test__/router.test.ts b/packages/core/src/main/protocol-handler/__test__/router.test.ts index 47933aa5dd..b38954fb23 100644 --- a/packages/core/src/main/protocol-handler/__test__/router.test.ts +++ b/packages/core/src/main/protocol-handler/__test__/router.test.ts @@ -10,11 +10,9 @@ import { delay, noop } from "@k8slens/utilities"; import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable"; -import enabledExtensionsStateInjectable from "../../../extensions/enabled-extensions-state.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import { LensExtension } from "../../../extensions/lens-extension"; import type { ObservableMap } from "mobx"; -import { computed } from "mobx"; import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; @@ -23,6 +21,8 @@ import pathExistsInjectable from "../../../common/fs/path-exists.injectable"; import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable"; import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable"; import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import type { LensExtensionState } from "../../../features/extensions/enabled/common/state.injectable"; +import enabledExtensionsStateInjectable from "../../../features/extensions/enabled/common/state.injectable"; function throwIfDefined(val: any): void { if (val != null) { @@ -33,7 +33,7 @@ function throwIfDefined(val: any): void { describe("protocol router tests", () => { let extensionInstances: ObservableMap; let lpr: LensProtocolRouterMain; - let enabledExtensions: Set; + let enabledExtensions: ObservableMap; let broadcastMessageMock: jest.Mock; beforeEach(async () => { @@ -44,13 +44,7 @@ describe("protocol router tests", () => { di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); }); di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); }); - enabledExtensions = new Set(); - - di.override(enabledExtensionsStateInjectable, () => ({ - isEnabled: ({ id, isBundled }) => isBundled || enabledExtensions.has(id), - enabledExtensions: computed(() => []), - mergeState: noop, - })); + enabledExtensions = di.inject(enabledExtensionsStateInjectable); di.permitSideEffects(getConfigurationFileModelInjectable); @@ -97,7 +91,7 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "@mirantis/minikube" }); lpr.addInternalHandler("/", noop); @@ -177,7 +171,7 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "@foobar/icecream" }); try { expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined(); @@ -216,7 +210,7 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "@foobar/icecream" }); } { @@ -242,12 +236,9 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "icecream" }); } - enabledExtensions.add("@foobar/icecream"); - enabledExtensions.add("icecream"); - try { expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined(); } catch (error) { diff --git a/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts b/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts index 79cd732575..92fbdd8737 100644 --- a/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts +++ b/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts @@ -5,22 +5,21 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterMain } from "./lens-protocol-router-main"; -import enabledExtensionsStateInjectable from "../../../extensions/enabled-extensions-state.injectable"; import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import isExtensionEnabledInjectable from "../../../features/extensions/enabled/common/is-enabled.injectable"; const lensProtocolRouterMainInjectable = getInjectable({ id: "lens-protocol-router-main", - instantiate: (di) => - new LensProtocolRouterMain({ - extensionLoader: di.inject(extensionLoaderInjectable), - extensionsStore: di.inject(enabledExtensionsStateInjectable), - showApplicationWindow: di.inject(showApplicationWindowInjectable), - broadcastMessage: di.inject(broadcastMessageInjectable), - logger: di.inject(loggerInjectable), - }), + instantiate: (di) => new LensProtocolRouterMain({ + extensionLoader: di.inject(extensionLoaderInjectable), + isExtensionEnabled: di.inject(isExtensionEnabledInjectable), + showApplicationWindow: di.inject(showApplicationWindowInjectable), + broadcastMessage: di.inject(broadcastMessageInjectable), + logger: di.inject(loggerInjectable), + }), }); export default lensProtocolRouterMainInjectable; diff --git a/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts b/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts index b074d8f13f..aa9525b31a 100644 --- a/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts +++ b/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts @@ -5,17 +5,17 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer"; -import enabledExtensionsStateInjectable from "../../../extensions/enabled-extensions-state.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import showErrorNotificationInjectable from "../../components/notifications/show-error-notification.injectable"; import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable"; +import isExtensionEnabledInjectable from "../../../features/extensions/enabled/common/is-enabled.injectable"; const lensProtocolRouterRendererInjectable = getInjectable({ id: "lens-protocol-router-renderer", instantiate: (di) => new LensProtocolRouterRenderer({ extensionLoader: di.inject(extensionLoaderInjectable), - extensionsStore: di.inject(enabledExtensionsStateInjectable), + isExtensionEnabled: di.inject(isExtensionEnabledInjectable), logger: di.inject(loggerInjectable), showErrorNotification: di.inject(showErrorNotificationInjectable), showShortInfoNotification: di.inject(showShortInfoNotificationInjectable),