mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fully convert ExtensionLoader to be injectable
- To fix unit tests Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
90c37143bd
commit
7f86a89cc2
@ -4,5 +4,3 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const extensionDiscoveryStateChannel = "extension-discovery:state";
|
export const extensionDiscoveryStateChannel = "extension-discovery:state";
|
||||||
export const extensionLoaderFromMainChannel = "extension-loader:main:state";
|
|
||||||
export const extensionLoaderFromRendererChannel = "extension-loader:renderer:state";
|
|
||||||
|
|||||||
@ -11,12 +11,11 @@ import { pathToRegexp } from "path-to-regexp";
|
|||||||
import type Url from "url-parse";
|
import type Url from "url-parse";
|
||||||
import { RoutingError, RoutingErrorType } from "./error";
|
import { RoutingError, RoutingErrorType } from "./error";
|
||||||
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
|
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
|
||||||
import type { ExtensionLoader } from "../../extensions/extension-loader";
|
|
||||||
import type { LensExtension } from "../../extensions/lens-extension";
|
import type { LensExtension } from "../../extensions/lens-extension";
|
||||||
import type { RouteHandler, RouteParams } from "./registration";
|
import type { RouteHandler, RouteParams } from "./registration";
|
||||||
import { when } from "mobx";
|
import { when } from "mobx";
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
|
import type { FindExtensionInstanceByName } from "../../features/extensions/loader/common/find-instance-by-name.injectable";
|
||||||
|
|
||||||
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
|
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
|
||||||
export const ProtocolHandlerIpcPrefix = "protocol-handler";
|
export const ProtocolHandlerIpcPrefix = "protocol-handler";
|
||||||
@ -64,9 +63,9 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LensProtocolRouterDependencies {
|
export interface LensProtocolRouterDependencies {
|
||||||
readonly extensionLoader: ExtensionLoader;
|
|
||||||
readonly extensionsStore: ExtensionsStore;
|
readonly extensionsStore: ExtensionsStore;
|
||||||
readonly logger: Logger;
|
readonly logger: Logger;
|
||||||
|
findExtensionInstanceByName: FindExtensionInstanceByName;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class LensProtocolRouter {
|
export abstract class LensProtocolRouter {
|
||||||
@ -184,13 +183,8 @@ export abstract class LensProtocolRouter {
|
|||||||
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
|
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
|
||||||
const name = [publisher, partialName].filter(isDefined).join("/");
|
const name = [publisher, partialName].filter(isDefined).join("/");
|
||||||
|
|
||||||
const extensionLoader = this.dependencies.extensionLoader;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/**
|
await when(() => this.dependencies.findExtensionInstanceByName(name) !== "not-installed", {
|
||||||
* Note, if `getInstanceByName` returns `null` that means we won't be getting an instance
|
|
||||||
*/
|
|
||||||
await when(() => extensionLoader.getInstanceByName(name) !== void 0, {
|
|
||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -201,10 +195,10 @@ export abstract class LensProtocolRouter {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extension = extensionLoader.getInstanceByName(name);
|
const extension = this.dependencies.findExtensionInstanceByName(name);
|
||||||
|
|
||||||
if (!extension) {
|
if (typeof extension === "string") {
|
||||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but does not have a class for ${ipcRenderer ? "renderer" : "main"}`);
|
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but ${extension}`);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,172 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 "../../renderer/utils";
|
|
||||||
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";
|
|
||||||
|
|
||||||
const manifestPath = "manifest/path";
|
|
||||||
const manifestPath2 = "manifest/path2";
|
|
||||||
const manifestPath3 = "manifest/path3";
|
|
||||||
|
|
||||||
describe("ExtensionLoader", () => {
|
|
||||||
let extensionLoader: ExtensionLoader;
|
|
||||||
let updateExtensionStateMock: jest.Mock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
|
||||||
di.override(currentlyInClusterFrameInjectable, () => false);
|
|
||||||
|
|
||||||
di.override(ipcRendererInjectable, () => ({
|
|
||||||
invoke: jest.fn(async (channel: string) => {
|
|
||||||
if (channel === "extension-loader:main:state") {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
manifestPath,
|
|
||||||
{
|
|
||||||
manifest: {
|
|
||||||
name: "TestExtension",
|
|
||||||
version: "1.0.0",
|
|
||||||
},
|
|
||||||
id: manifestPath,
|
|
||||||
absolutePath: "/test/1",
|
|
||||||
manifestPath,
|
|
||||||
isBundled: false,
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
manifestPath2,
|
|
||||||
{
|
|
||||||
manifest: {
|
|
||||||
name: "TestExtension2",
|
|
||||||
version: "2.0.0",
|
|
||||||
},
|
|
||||||
id: manifestPath2,
|
|
||||||
absolutePath: "/test/2",
|
|
||||||
manifestPath: manifestPath2,
|
|
||||||
isBundled: false,
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}),
|
|
||||||
|
|
||||||
on: (channel: string, listener: (event: any, ...args: any[]) => void) => {
|
|
||||||
if (channel === "extension-loader:main:state") {
|
|
||||||
// First initialize with extensions 1 and 2
|
|
||||||
// and then broadcast event to remove extension 2 and add extension number 3
|
|
||||||
setTimeout(() => {
|
|
||||||
listener({}, [
|
|
||||||
[
|
|
||||||
manifestPath,
|
|
||||||
{
|
|
||||||
manifest: {
|
|
||||||
name: "TestExtension",
|
|
||||||
version: "1.0.0",
|
|
||||||
},
|
|
||||||
id: manifestPath,
|
|
||||||
absolutePath: "/test/1",
|
|
||||||
manifestPath,
|
|
||||||
isBundled: false,
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
manifestPath3,
|
|
||||||
{
|
|
||||||
manifest: {
|
|
||||||
name: "TestExtension3",
|
|
||||||
version: "3.0.0",
|
|
||||||
},
|
|
||||||
id: manifestPath3,
|
|
||||||
absolutePath: "/test/3",
|
|
||||||
manifestPath: manifestPath3,
|
|
||||||
isBundled: false,
|
|
||||||
isEnabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}) as unknown as IpcRenderer);
|
|
||||||
|
|
||||||
updateExtensionStateMock = jest.fn();
|
|
||||||
|
|
||||||
di.override(updateExtensionsStateInjectable, () => updateExtensionStateMock);
|
|
||||||
|
|
||||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renderer updates extension after ipc broadcast", async () => {
|
|
||||||
expect(extensionLoader.userExtensions.get().size).toBe(0);
|
|
||||||
|
|
||||||
await extensionLoader.init();
|
|
||||||
await delay(10);
|
|
||||||
|
|
||||||
// Assert the extensions after the extension broadcast event
|
|
||||||
expect(extensionLoader.userExtensions.get()).toEqual(new Map([
|
|
||||||
["manifest/path", {
|
|
||||||
absolutePath: "/test/1",
|
|
||||||
id: "manifest/path",
|
|
||||||
isBundled: false,
|
|
||||||
isEnabled: true,
|
|
||||||
manifest: {
|
|
||||||
name: "TestExtension",
|
|
||||||
version: "1.0.0",
|
|
||||||
},
|
|
||||||
manifestPath: "manifest/path",
|
|
||||||
}],
|
|
||||||
["manifest/path3", {
|
|
||||||
absolutePath: "/test/3",
|
|
||||||
id: "manifest/path3",
|
|
||||||
isBundled: false,
|
|
||||||
isEnabled: true,
|
|
||||||
manifest: {
|
|
||||||
name: "TestExtension3",
|
|
||||||
version: "3.0.0",
|
|
||||||
},
|
|
||||||
manifestPath: "manifest/path3",
|
|
||||||
}],
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
|
||||||
await extensionLoader.init();
|
|
||||||
|
|
||||||
expect(updateExtensionStateMock).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
extensionLoader.setIsEnabled("manifest/path", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updateExtensionStateMock).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
"manifest/path": {
|
|
||||||
enabled: false,
|
|
||||||
name: "TestExtension",
|
|
||||||
},
|
|
||||||
|
|
||||||
"manifest/path2": {
|
|
||||||
enabled: true,
|
|
||||||
name: "TestExtension2",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { ExtensionDiscovery } from "./extension-discovery";
|
import { ExtensionDiscovery } from "./extension-discovery";
|
||||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
|
||||||
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
||||||
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
|
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
@ -28,12 +27,12 @@ import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
|||||||
import removePathInjectable from "../../common/fs/remove.injectable";
|
import removePathInjectable from "../../common/fs/remove.injectable";
|
||||||
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable";
|
||||||
import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable";
|
import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable";
|
||||||
|
import installedExtensionsInjectable from "../../features/extensions/common/installed-extensions.injectable";
|
||||||
|
|
||||||
const extensionDiscoveryInjectable = getInjectable({
|
const extensionDiscoveryInjectable = getInjectable({
|
||||||
id: "extension-discovery",
|
id: "extension-discovery",
|
||||||
|
|
||||||
instantiate: (di) => new ExtensionDiscovery({
|
instantiate: (di) => new ExtensionDiscovery({
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||||
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
||||||
@ -57,6 +56,7 @@ const extensionDiscoveryInjectable = getInjectable({
|
|||||||
getRelativePath: di.inject(getRelativePathInjectable),
|
getRelativePath: di.inject(getRelativePathInjectable),
|
||||||
joinPaths: di.inject(joinPathsInjectable),
|
joinPaths: di.inject(joinPathsInjectable),
|
||||||
homeDirectoryPath: di.inject(homeDirectoryPathInjectable),
|
homeDirectoryPath: di.inject(homeDirectoryPathInjectable),
|
||||||
|
installedExtensions: di.inject(installedExtensionsInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
import type { ObservableMap } from "mobx";
|
||||||
import { makeObservable, observable, reaction, when } from "mobx";
|
import { makeObservable, observable, reaction, when } from "mobx";
|
||||||
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
|
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
|
||||||
import { isErrnoException, toJS } from "../../common/utils";
|
import { isErrnoException, iter, toJS } from "../../common/utils";
|
||||||
import type { ExtensionsStore } from "../extensions-store/extensions-store";
|
import type { ExtensionsStore } from "../extensions-store/extensions-store";
|
||||||
import type { ExtensionLoader } from "../extension-loader";
|
|
||||||
import type { BundledLensExtensionManifest, LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
import type { BundledLensExtensionManifest, LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
||||||
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
|
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
|
||||||
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
||||||
@ -32,7 +32,6 @@ import type { RemovePath } from "../../common/fs/remove.injectable";
|
|||||||
import type TypedEventEmitter from "typed-emitter";
|
import type TypedEventEmitter from "typed-emitter";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
readonly extensionLoader: ExtensionLoader;
|
|
||||||
readonly extensionsStore: ExtensionsStore;
|
readonly extensionsStore: ExtensionsStore;
|
||||||
readonly extensionInstallationStateStore: ExtensionInstallationStateStore;
|
readonly extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||||
readonly extensionPackageRootDirectory: string;
|
readonly extensionPackageRootDirectory: string;
|
||||||
@ -41,6 +40,7 @@ interface Dependencies {
|
|||||||
readonly isProduction: boolean;
|
readonly isProduction: boolean;
|
||||||
readonly fileSystemSeparator: string;
|
readonly fileSystemSeparator: string;
|
||||||
readonly homeDirectoryPath: string;
|
readonly homeDirectoryPath: string;
|
||||||
|
readonly installedExtensions: ObservableMap<LensExtensionId, InstalledExtension>;
|
||||||
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
||||||
installExtension: (name: string) => Promise<void>;
|
installExtension: (name: string) => Promise<void>;
|
||||||
readJsonFile: ReadJson;
|
readJsonFile: ReadJson;
|
||||||
@ -60,11 +60,6 @@ interface Dependencies {
|
|||||||
|
|
||||||
export interface BaseInstalledExtension {
|
export interface BaseInstalledExtension {
|
||||||
readonly id: LensExtensionId;
|
readonly id: LensExtensionId;
|
||||||
// Absolute path to the non-symlinked source folder,
|
|
||||||
// e.g. "/Users/user/.k8slens/extensions/helloworld"
|
|
||||||
readonly absolutePath: string;
|
|
||||||
// Absolute to the symlinked package.json file
|
|
||||||
readonly manifestPath: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BundledInstalledExtension extends BaseInstalledExtension {
|
export interface BundledInstalledExtension extends BaseInstalledExtension {
|
||||||
@ -78,6 +73,11 @@ export interface ExternalInstalledExtension extends BaseInstalledExtension {
|
|||||||
readonly manifest: LensExtensionManifest;
|
readonly manifest: LensExtensionManifest;
|
||||||
readonly isBundled: false;
|
readonly isBundled: false;
|
||||||
readonly isCompatible: boolean;
|
readonly isCompatible: boolean;
|
||||||
|
// Absolute path to the non-symlinked source folder,
|
||||||
|
// e.g. "/Users/user/.k8slens/extensions/helloworld"
|
||||||
|
readonly absolutePath: string;
|
||||||
|
// Absolute to the symlinked package.json file
|
||||||
|
readonly manifestPath: string;
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ const isDirectoryLike = (lstat: Stats) => lstat.isDirectory() || lstat.isSymboli
|
|||||||
|
|
||||||
interface ExtensionDiscoveryEvents {
|
interface ExtensionDiscoveryEvents {
|
||||||
add: (ext: InstalledExtension) => void;
|
add: (ext: InstalledExtension) => void;
|
||||||
remove: (extId: LensExtensionId) => void;
|
remove: (ext: InstalledExtension) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,7 +116,6 @@ export class ExtensionDiscovery {
|
|||||||
protected bundledFolderPath!: string;
|
protected bundledFolderPath!: string;
|
||||||
|
|
||||||
private loadStarted = false;
|
private loadStarted = false;
|
||||||
private extensions: Map<string, InstalledExtension> = new Map();
|
|
||||||
|
|
||||||
// True if extensions have been loaded from the disk after app startup
|
// True if extensions have been loaded from the disk after app startup
|
||||||
@observable isLoaded = false;
|
@observable isLoaded = false;
|
||||||
@ -177,12 +176,9 @@ export class ExtensionDiscovery {
|
|||||||
* Watches for added/removed local extensions.
|
* Watches for added/removed local extensions.
|
||||||
* Dependencies are installed automatically after an extension folder is copied.
|
* Dependencies are installed automatically after an extension folder is copied.
|
||||||
*/
|
*/
|
||||||
async watchExtensions(): Promise<void> {
|
watchExtensions() {
|
||||||
this.dependencies.logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
|
this.dependencies.logger.info(`${logModule} watching extension add/remove in ${this.localFolderPath}`);
|
||||||
|
|
||||||
// Wait until .load() has been called and has been resolved
|
|
||||||
await this.whenLoaded;
|
|
||||||
|
|
||||||
this.dependencies.watch(this.localFolderPath, {
|
this.dependencies.watch(this.localFolderPath, {
|
||||||
// For adding and removing symlinks to work, the depth has to be 1.
|
// For adding and removing symlinks to work, the depth has to be 1.
|
||||||
depth: 1,
|
depth: 1,
|
||||||
@ -224,7 +220,7 @@ export class ExtensionDiscovery {
|
|||||||
// Install dependencies for the new extension
|
// Install dependencies for the new extension
|
||||||
await this.dependencies.installExtension(extension.absolutePath);
|
await this.dependencies.installExtension(extension.absolutePath);
|
||||||
|
|
||||||
this.extensions.set(extension.id, extension);
|
this.dependencies.installedExtensions.set(extension.id, extension);
|
||||||
this.dependencies.logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
this.dependencies.logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
||||||
this.events.emit("add", extension);
|
this.events.emit("add", extension);
|
||||||
}
|
}
|
||||||
@ -251,28 +247,23 @@ export class ExtensionDiscovery {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const extension of this.extensions.values()) {
|
const extension = iter.find(
|
||||||
if (extension.absolutePath !== filePath) {
|
this.dependencies.installedExtensions.values(),
|
||||||
continue;
|
(ext) => !ext.isBundled && ext.absolutePath === filePath,
|
||||||
}
|
);
|
||||||
|
|
||||||
const extensionName = extension.manifest.name;
|
if (!extension) {
|
||||||
|
this.dependencies.logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`);
|
||||||
// If the extension is deleted manually while the application is running, also remove the symlink
|
|
||||||
await this.removeSymlinkByPackageName(extensionName);
|
|
||||||
|
|
||||||
// The path to the manifest file is the lens extension id
|
|
||||||
// Note: that we need to use the symlinked path
|
|
||||||
const lensExtensionId = extension.manifestPath;
|
|
||||||
|
|
||||||
this.extensions.delete(extension.id);
|
|
||||||
this.dependencies.logger.info(`${logModule} removed extension ${extensionName}`);
|
|
||||||
this.events.emit("remove", lensExtensionId);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dependencies.logger.warn(`${logModule} extension ${extensionFolderName} not found, can't remove`);
|
// If the extension is deleted manually while the application is running, also remove the symlink
|
||||||
|
await this.removeSymlinkByPackageName(extension.manifest.name);
|
||||||
|
|
||||||
|
this.dependencies.installedExtensions.delete(extension.id);
|
||||||
|
this.dependencies.logger.info(`${logModule} removed extension ${extension.manifest.name}`);
|
||||||
|
this.events.emit("remove", extension);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,12 +282,16 @@ export class ExtensionDiscovery {
|
|||||||
* @param extensionId The ID of the extension to uninstall.
|
* @param extensionId The ID of the extension to uninstall.
|
||||||
*/
|
*/
|
||||||
async uninstallExtension(extensionId: LensExtensionId): Promise<void> {
|
async uninstallExtension(extensionId: LensExtensionId): Promise<void> {
|
||||||
const extension = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtensionById(extensionId);
|
const extension = this.dependencies.installedExtensions.get(extensionId);
|
||||||
|
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
return void this.dependencies.logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId });
|
return void this.dependencies.logger.warn(`${logModule} could not uninstall extension, not found`, { id: extensionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extension.isBundled) {
|
||||||
|
return void this.dependencies.logger.warn(`${logModule} could not uninstall extension, is bundled`, { id: extensionId });
|
||||||
|
}
|
||||||
|
|
||||||
const { manifest, absolutePath } = extension;
|
const { manifest, absolutePath } = extension;
|
||||||
|
|
||||||
this.dependencies.logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
this.dependencies.logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
||||||
@ -307,7 +302,7 @@ export class ExtensionDiscovery {
|
|||||||
await this.dependencies.removePath(absolutePath);
|
await this.dependencies.removePath(absolutePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
async load() {
|
||||||
if (this.loadStarted) {
|
if (this.loadStarted) {
|
||||||
// The class is simplified by only supporting .load() to be called once
|
// The class is simplified by only supporting .load() to be called once
|
||||||
throw new Error("ExtensionDiscovery.load() can be only be called once");
|
throw new Error("ExtensionDiscovery.load() can be only be called once");
|
||||||
@ -323,11 +318,10 @@ export class ExtensionDiscovery {
|
|||||||
await this.dependencies.ensureDirectory(this.nodeModulesPath);
|
await this.dependencies.ensureDirectory(this.nodeModulesPath);
|
||||||
await this.dependencies.ensureDirectory(this.localFolderPath);
|
await this.dependencies.ensureDirectory(this.localFolderPath);
|
||||||
|
|
||||||
const extensions = await this.ensureExtensions();
|
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
||||||
|
|
||||||
|
this.dependencies.installedExtensions.replace(userExtensions.map(ext => [ext.id, ext]));
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
|
||||||
return extensions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -385,12 +379,6 @@ export class ExtensionDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ensureExtensions(): Promise<Map<LensExtensionId, ExternalInstalledExtension>> {
|
|
||||||
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
|
||||||
|
|
||||||
return this.extensions = new Map(userExtensions.map(extension => [extension.id, extension]));
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadFromFolder(folderPath: string): Promise<ExternalInstalledExtension[]> {
|
async loadFromFolder(folderPath: string): Promise<ExternalInstalledExtension[]> {
|
||||||
const extensions: ExternalInstalledExtension[] = [];
|
const extensions: ExternalInstalledExtension[] = [];
|
||||||
const paths = await this.dependencies.readDirectory(folderPath);
|
const paths = await this.dependencies.readDirectory(folderPath);
|
||||||
|
|||||||
@ -1,32 +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 { ExtensionLoader } from "./extension-loader";
|
|
||||||
import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable";
|
|
||||||
import extensionInstancesInjectable from "./extension-instances.injectable";
|
|
||||||
import type { LensExtension } from "../lens-extension";
|
|
||||||
import extensionInjectable from "./extension/extension.injectable";
|
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
|
||||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
|
||||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
|
||||||
import { bundledExtensionInjectionToken } from "../extension-discovery/bundled-extension-token";
|
|
||||||
import { extensionEntryPointNameInjectionToken } from "./entry-point-name";
|
|
||||||
|
|
||||||
const extensionLoaderInjectable = getInjectable({
|
|
||||||
id: "extension-loader",
|
|
||||||
|
|
||||||
instantiate: (di) => new ExtensionLoader({
|
|
||||||
updateExtensionsState: di.inject(updateExtensionsStateInjectable),
|
|
||||||
extensionInstances: di.inject(extensionInstancesInjectable),
|
|
||||||
getExtension: (instance: LensExtension) => di.inject(extensionInjectable, instance),
|
|
||||||
bundledExtensions: di.injectMany(bundledExtensionInjectionToken),
|
|
||||||
extensionEntryPointName: di.inject(extensionEntryPointNameInjectionToken),
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
joinPaths: di.inject(joinPathsInjectable),
|
|
||||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default extensionLoaderInjectable;
|
|
||||||
@ -1,395 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ipcMain, ipcRenderer } from "electron";
|
|
||||||
import { isEqual } from "lodash";
|
|
||||||
import type { ObservableMap } from "mobx";
|
|
||||||
import { runInAction, action, computed, observable, reaction, when } from "mobx";
|
|
||||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
|
|
||||||
import { isDefined, iter, toJS } from "../../common/utils";
|
|
||||||
import type { BundledInstalledExtension, ExternalInstalledExtension, InstalledExtension } from "../extension-discovery/extension-discovery";
|
|
||||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
|
||||||
import type { LensExtensionState } from "../extensions-store/extensions-store";
|
|
||||||
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
|
|
||||||
import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
|
|
||||||
import assert from "assert";
|
|
||||||
import { EventEmitter } from "../../common/event-emitter";
|
|
||||||
import type { Extension } from "./extension/extension.injectable";
|
|
||||||
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 { BundledExtension } from "../extension-discovery/bundled-extension-token";
|
|
||||||
|
|
||||||
const logModule = "[EXTENSIONS-LOADER]";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
readonly extensionInstances: ObservableMap<LensExtensionId, LensExtension>;
|
|
||||||
readonly bundledExtensions: BundledExtension[];
|
|
||||||
readonly logger: Logger;
|
|
||||||
readonly extensionEntryPointName: "main" | "renderer";
|
|
||||||
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void;
|
|
||||||
getExtension: (instance: LensExtension) => Extension;
|
|
||||||
joinPaths: JoinPaths;
|
|
||||||
getDirnameOfPath: GetDirnameOfPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExtensionBeingActivated {
|
|
||||||
instance: LensExtension;
|
|
||||||
installedExtension: InstalledExtension;
|
|
||||||
activated: Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExtensionLoading {
|
|
||||||
isBundled: boolean;
|
|
||||||
loaded: Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads installed extensions to the Lens application
|
|
||||||
*/
|
|
||||||
export class ExtensionLoader {
|
|
||||||
protected readonly extensions = observable.map<LensExtensionId, InstalledExtension>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the set of extensions that don't come with either
|
|
||||||
* - Main.LensExtension when running in the main process
|
|
||||||
* - Renderer.LensExtension when running in the renderer process
|
|
||||||
*/
|
|
||||||
protected readonly nonInstancesByName = observable.set<string>();
|
|
||||||
|
|
||||||
protected readonly instancesByName = computed(() => new Map((
|
|
||||||
iter.chain(this.dependencies.extensionInstances.entries())
|
|
||||||
.map(([, instance]) => [instance.name, instance])
|
|
||||||
)));
|
|
||||||
|
|
||||||
private readonly onRemoveExtensionId = new EventEmitter<[string]>();
|
|
||||||
|
|
||||||
readonly isLoaded = observable.box(false);
|
|
||||||
|
|
||||||
constructor(protected readonly dependencies: Dependencies) {}
|
|
||||||
|
|
||||||
readonly userExtensions = computed(() => new Map((
|
|
||||||
this.extensions.toJSON()
|
|
||||||
.filter(([, extension]) => !extension.isBundled)
|
|
||||||
)));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extension instance by its manifest name
|
|
||||||
* @param name The name of the extension
|
|
||||||
* @returns one of the following:
|
|
||||||
* - the instance of `Main.LensExtension` on the main process if created
|
|
||||||
* - the instance of `Renderer.LensExtension` on the renderer process if created
|
|
||||||
* - `null` if no class definition is provided for the current process
|
|
||||||
* - `undefined` if the name is not known about
|
|
||||||
*/
|
|
||||||
getInstanceByName(name: string): LensExtension | null | undefined {
|
|
||||||
if (this.nonInstancesByName.has(name)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.instancesByName.get().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly storeState = computed(() => Object.fromEntries((
|
|
||||||
iter.chain(this.userExtensions.get().entries())
|
|
||||||
.map(([extId, extension]) => [
|
|
||||||
extId,
|
|
||||||
{
|
|
||||||
enabled: extension.isEnabled,
|
|
||||||
name: extension.manifest.name,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
)));
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
if (ipcMain) {
|
|
||||||
await this.initMain();
|
|
||||||
} else {
|
|
||||||
await this.initRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
await when(() => this.isLoaded.get());
|
|
||||||
|
|
||||||
// broadcasting extensions between main/renderer processes
|
|
||||||
reaction(() => this.toJSON(), () => this.broadcastExtensions(), {
|
|
||||||
fireImmediately: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
reaction(
|
|
||||||
() => this.storeState.get(),
|
|
||||||
(state) => {
|
|
||||||
this.dependencies.updateExtensionsState(state);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
initExtensions(extensions: Map<LensExtensionId, InstalledExtension>) {
|
|
||||||
this.extensions.replace(extensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
addExtension(extension: InstalledExtension) {
|
|
||||||
this.extensions.set(extension.id, extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
removeInstance(lensExtensionId: LensExtensionId) {
|
|
||||||
this.dependencies.logger.info(`${logModule} deleting extension instance ${lensExtensionId}`);
|
|
||||||
const instance = this.dependencies.extensionInstances.get(lensExtensionId);
|
|
||||||
|
|
||||||
if (!instance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
instance.disable();
|
|
||||||
|
|
||||||
const extension = this.dependencies.getExtension(instance);
|
|
||||||
|
|
||||||
extension.deregister();
|
|
||||||
|
|
||||||
this.onRemoveExtensionId.emit(instance.id);
|
|
||||||
this.dependencies.extensionInstances.delete(lensExtensionId);
|
|
||||||
this.nonInstancesByName.delete(instance.name);
|
|
||||||
} catch (error) {
|
|
||||||
this.dependencies.logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeExtension(lensExtensionId: LensExtensionId) {
|
|
||||||
this.removeInstance(lensExtensionId);
|
|
||||||
|
|
||||||
if (!this.extensions.delete(lensExtensionId)) {
|
|
||||||
throw new Error(`Can't remove extension ${lensExtensionId}, doesn't exist.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsEnabled(lensExtensionId: LensExtensionId, isEnabled: boolean) {
|
|
||||||
const extension = this.extensions.get(lensExtensionId);
|
|
||||||
|
|
||||||
assert(extension, `Must register extension ${lensExtensionId} with before enabling it`);
|
|
||||||
assert(!extension.isBundled, `Cannot change the enabled state of a bundled extension`);
|
|
||||||
|
|
||||||
extension.isEnabled = isEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async initMain() {
|
|
||||||
runInAction(() => {
|
|
||||||
this.isLoaded.set(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.autoInitExtensions();
|
|
||||||
|
|
||||||
ipcMainHandle(extensionLoaderFromMainChannel, () => [...this.toJSON()]);
|
|
||||||
|
|
||||||
ipcMainOn(extensionLoaderFromRendererChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
|
||||||
this.syncExtensions(extensions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async initRenderer() {
|
|
||||||
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
|
|
||||||
runInAction(() => {
|
|
||||||
this.isLoaded.set(true);
|
|
||||||
});
|
|
||||||
this.syncExtensions(extensions);
|
|
||||||
|
|
||||||
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
|
||||||
|
|
||||||
// Remove deleted extensions in renderer side only
|
|
||||||
this.extensions.forEach((_, lensExtensionId) => {
|
|
||||||
if (!receivedExtensionIds.includes(lensExtensionId)) {
|
|
||||||
this.removeExtension(lensExtensionId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
requestExtensionLoaderInitialState().then(extensionListHandler);
|
|
||||||
ipcRendererOn(extensionLoaderFromMainChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
|
||||||
extensionListHandler(extensions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcastExtensions() {
|
|
||||||
const channel = ipcRenderer
|
|
||||||
? extensionLoaderFromRendererChannel
|
|
||||||
: extensionLoaderFromMainChannel;
|
|
||||||
|
|
||||||
broadcastMessage(channel, Array.from(this.extensions));
|
|
||||||
}
|
|
||||||
|
|
||||||
syncExtensions(extensions: [LensExtensionId, InstalledExtension][]) {
|
|
||||||
extensions.forEach(([lensExtensionId, extension]) => {
|
|
||||||
if (!isEqual(this.extensions.get(lensExtensionId), extension)) {
|
|
||||||
this.extensions.set(lensExtensionId, extension);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async loadBundledExtensions() {
|
|
||||||
const bundledExtensions = await Promise.all((this.dependencies.bundledExtensions
|
|
||||||
.map(async extension => {
|
|
||||||
try {
|
|
||||||
const LensExtensionClass = await extension[this.dependencies.extensionEntryPointName]();
|
|
||||||
|
|
||||||
if (!LensExtensionClass) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const installedExtension: BundledInstalledExtension = {
|
|
||||||
absolutePath: "irrelevant",
|
|
||||||
id: extension.manifest.name,
|
|
||||||
isBundled: true,
|
|
||||||
isCompatible: true,
|
|
||||||
isEnabled: true,
|
|
||||||
manifest: extension.manifest,
|
|
||||||
manifestPath: "irrelevant",
|
|
||||||
};
|
|
||||||
const instance = new LensExtensionClass(installedExtension);
|
|
||||||
|
|
||||||
this.dependencies.extensionInstances.set(extension.manifest.name, instance);
|
|
||||||
|
|
||||||
return {
|
|
||||||
instance,
|
|
||||||
installedExtension,
|
|
||||||
activated: instance.activate(),
|
|
||||||
} as ExtensionBeingActivated;
|
|
||||||
} catch (err) {
|
|
||||||
this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: extension, err });
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
));
|
|
||||||
|
|
||||||
return bundledExtensions.filter(isDefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async loadExtensions(extensions: ExtensionBeingActivated[]): Promise<ExtensionLoading[]> {
|
|
||||||
// We first need to wait until each extension's `onActivate` is resolved or rejected,
|
|
||||||
// as this might register new catalog categories. Afterwards we can safely .enable the extension.
|
|
||||||
await Promise.all(
|
|
||||||
extensions.map(extension =>
|
|
||||||
// If extension activation fails, log error
|
|
||||||
extension.activated.catch((error) => {
|
|
||||||
this.dependencies.logger.error(`${logModule}: activation extension error`, { ext: extension.installedExtension, error });
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
extensions.forEach(({ instance }) => {
|
|
||||||
const extension = this.dependencies.getExtension(instance);
|
|
||||||
|
|
||||||
extension.register();
|
|
||||||
});
|
|
||||||
|
|
||||||
return extensions.map(extension => {
|
|
||||||
const loaded = extension.instance.enable().catch((err) => {
|
|
||||||
this.dependencies.logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
isBundled: extension.installedExtension.isBundled,
|
|
||||||
loaded,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async loadUserExtensions(installedExtensions: Map<string, InstalledExtension>) {
|
|
||||||
// Steps of the function:
|
|
||||||
// 1. require and call .activate for each Extension
|
|
||||||
// 2. Wait until every extension's onActivate has been resolved
|
|
||||||
// 3. Call .enable for each extension
|
|
||||||
// 4. Return ExtensionLoading[]
|
|
||||||
|
|
||||||
return [...installedExtensions.entries()]
|
|
||||||
.filter((entry): entry is [string, ExternalInstalledExtension] => !entry[1].isBundled)
|
|
||||||
.map(([extId, installedExtension]) => {
|
|
||||||
const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(installedExtension.manifest.name);
|
|
||||||
|
|
||||||
if (installedExtension.isCompatible && installedExtension.isEnabled && !alreadyInit) {
|
|
||||||
try {
|
|
||||||
const LensExtensionClass = this.requireExtension(installedExtension);
|
|
||||||
|
|
||||||
if (!LensExtensionClass) {
|
|
||||||
this.nonInstancesByName.add(installedExtension.manifest.name);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = new LensExtensionClass(installedExtension);
|
|
||||||
|
|
||||||
this.dependencies.extensionInstances.set(extId, instance);
|
|
||||||
|
|
||||||
return {
|
|
||||||
instance,
|
|
||||||
installedExtension,
|
|
||||||
activated: instance.activate(),
|
|
||||||
} as ExtensionBeingActivated;
|
|
||||||
} catch (err) {
|
|
||||||
this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: installedExtension, err });
|
|
||||||
}
|
|
||||||
} else if (!installedExtension.isEnabled && alreadyInit) {
|
|
||||||
this.removeInstance(extId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(isDefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
async autoInitExtensions() {
|
|
||||||
this.dependencies.logger.info(`${logModule}: auto initializing extensions`);
|
|
||||||
|
|
||||||
const bundledExtensions = await this.loadBundledExtensions();
|
|
||||||
const userExtensions = await this.loadUserExtensions(this.toJSON());
|
|
||||||
const loadedExtensions = await this.loadExtensions([
|
|
||||||
...bundledExtensions,
|
|
||||||
...userExtensions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Setup reaction to load extensions on JSON changes
|
|
||||||
reaction(() => this.toJSON(), installedExtensions => {
|
|
||||||
void (async () => {
|
|
||||||
const userExtensions = await this.loadUserExtensions(installedExtensions);
|
|
||||||
|
|
||||||
await this.loadExtensions(userExtensions);
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
return loadedExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected requireExtension(extension: ExternalInstalledExtension): LensExtensionConstructor | null {
|
|
||||||
const extRelativePath = extension.manifest[this.dependencies.extensionEntryPointName];
|
|
||||||
|
|
||||||
if (!extRelativePath) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extAbsolutePath = this.dependencies.joinPaths(this.dependencies.getDirnameOfPath(extension.manifestPath), extRelativePath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return require(/* webpackIgnore: true */ extAbsolutePath).default;
|
|
||||||
} catch (error) {
|
|
||||||
const message = (error instanceof Error ? error.stack : undefined) || error;
|
|
||||||
|
|
||||||
this.dependencies.logger.error(`${logModule}: can't load ${this.dependencies.extensionEntryPointName} for "${extension.manifest.name}": ${message}`, { extension });
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getExtensionById(extId: LensExtensionId) {
|
|
||||||
return this.extensions.get(extId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getInstanceById(extId: LensExtensionId) {
|
|
||||||
return this.dependencies.extensionInstances.get(extId);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): Map<LensExtensionId, InstalledExtension> {
|
|
||||||
return toJS(this.extensions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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 extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
|
|
||||||
|
|
||||||
const updateExtensionsStateInjectable = getInjectable({
|
|
||||||
id: "update-extensions-state",
|
|
||||||
instantiate: (di) => di.inject(extensionsStoreInjectable).mergeState,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default updateExtensionsStateInjectable;
|
|
||||||
@ -34,7 +34,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
.map(({ name }) => name);
|
.map(({ name }) => name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly state = observable.map<LensExtensionId, LensExtensionState>();
|
readonly state = observable.map<LensExtensionId, LensExtensionState>();
|
||||||
|
|
||||||
isEnabled(extId: LensExtensionId): boolean {
|
isEnabled(extId: LensExtensionId): boolean {
|
||||||
// By default false, so that copied extensions are disabled by default.
|
// By default false, so that copied extensions are disabled by default.
|
||||||
@ -42,10 +42,6 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
return this.state.get(extId)?.enabled ?? false;
|
return this.state.get(extId)?.enabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeState = action((extensionsState: Record<LensExtensionId, LensExtensionState> | [LensExtensionId, LensExtensionState][]) => {
|
|
||||||
this.state.merge(extensionsState);
|
|
||||||
});
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
||||||
this.state.merge(extensions);
|
this.state.merge(extensions);
|
||||||
|
|||||||
@ -4,12 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BundledInstalledExtension, ExternalInstalledExtension, InstalledExtension } from "./extension-discovery/extension-discovery";
|
import type { BundledInstalledExtension, ExternalInstalledExtension, InstalledExtension } from "./extension-discovery/extension-discovery";
|
||||||
import { action, computed, makeObservable, observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { disposer } from "../common/utils";
|
import { disposer } from "../common/utils";
|
||||||
import type { ProtocolHandlerRegistration } from "../common/protocol-handler/registration";
|
import type { ProtocolHandlerRegistration } from "../common/protocol-handler/registration";
|
||||||
import type { PackageJson } from "type-fest";
|
import type { PackageJson } from "type-fest";
|
||||||
import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store";
|
import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store";
|
||||||
import type { Logger } from "../common/logger";
|
import type { Logger } from "../common/logger";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
export type LensExtensionId = string; // path to manifest (package.json)
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type LensExtensionConstructor = new (ext: ExternalInstalledExtension) => LensExtension;
|
export type LensExtensionConstructor = new (ext: ExternalInstalledExtension) => LensExtension;
|
||||||
@ -19,6 +20,10 @@ export interface BundledLensExtensionManifest extends PackageJson {
|
|||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
publishConfig?: Partial<Record<string, string>>;
|
publishConfig?: Partial<Record<string, string>>;
|
||||||
|
|
||||||
|
// Specify extension name used for persisting data.
|
||||||
|
// Useful if extension is renamed but the data should not be lost.
|
||||||
|
storeName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LensExtensionDependencies {
|
export interface LensExtensionDependencies {
|
||||||
@ -37,35 +42,39 @@ export interface LensExtensionManifest extends BundledLensExtensionManifest {
|
|||||||
lens: string; // "semver"-package format
|
lens: string; // "semver"-package format
|
||||||
[x: string]: string | undefined;
|
[x: string]: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Specify extension name used for persisting data.
|
|
||||||
// Useful if extension is renamed but the data should not be lost.
|
|
||||||
storeName?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Disposers = Symbol("disposers");
|
export const Disposers = Symbol("disposers");
|
||||||
|
|
||||||
export class LensExtension {
|
export class LensExtension {
|
||||||
readonly id: LensExtensionId;
|
get id() {
|
||||||
readonly manifest: LensExtensionManifest;
|
return this.extension.id;
|
||||||
readonly manifestPath: string;
|
}
|
||||||
readonly isBundled: boolean;
|
|
||||||
|
get manifest() {
|
||||||
|
return this.extension.manifest as LensExtensionManifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
get manifestPath() {
|
||||||
|
assert(!this.extension.isBundled, "LensExtension.manifestPath doesn't exist for bundled extensions");
|
||||||
|
|
||||||
|
return this.extension.manifestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isBundled() {
|
||||||
|
return this.extension.isBundled;
|
||||||
|
}
|
||||||
|
|
||||||
get sanitizedExtensionId() {
|
get sanitizedExtensionId() {
|
||||||
return sanitizeExtensionName(this.name);
|
return sanitizeExtensionName(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
protected readonly dependencies: LensExtensionDependencies;
|
|
||||||
|
|
||||||
protocolHandlers: ProtocolHandlerRegistration[] = [];
|
protocolHandlers: ProtocolHandlerRegistration[] = [];
|
||||||
|
|
||||||
@observable private _isEnabled = false;
|
private readonly _isEnabled = observable.box(false);
|
||||||
|
|
||||||
@computed get isEnabled() {
|
get isEnabled() {
|
||||||
return this._isEnabled;
|
return this._isEnabled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,13 +82,13 @@ export class LensExtension {
|
|||||||
*/
|
*/
|
||||||
[Disposers] = disposer();
|
[Disposers] = disposer();
|
||||||
|
|
||||||
constructor(deps: LensExtensionDependencies, { id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
declare protected readonly dependencies: LensExtensionDependencies;
|
||||||
|
|
||||||
|
constructor(deps: LensExtensionDependencies, private readonly extension: InstalledExtension) {
|
||||||
this.dependencies = deps;
|
this.dependencies = deps;
|
||||||
this.id = id;
|
|
||||||
this.manifest = manifest as LensExtensionManifest;
|
|
||||||
this.manifestPath = manifestPath;
|
|
||||||
this.isBundled = isBundled;
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
@ -111,23 +120,21 @@ export class LensExtension {
|
|||||||
return this.dependencies.fileSystemProvisionerStore.requestDirectory(this.storeName);
|
return this.dependencies.fileSystemProvisionerStore.requestDirectory(this.storeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
async enable() {
|
async enable() {
|
||||||
if (this._isEnabled) {
|
if (this._isEnabled.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isEnabled = true;
|
this._isEnabled.set(true);
|
||||||
this.dependencies.logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
this.dependencies.logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
async disable() {
|
async disable() {
|
||||||
if (!this._isEnabled) {
|
if (!this._isEnabled.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isEnabled = false;
|
this._isEnabled.set(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.onDeactivate();
|
await this.onDeactivate();
|
||||||
|
|||||||
@ -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 type { InstalledExtension } from "../../../extensions/common-api";
|
||||||
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
import installedExtensionsInjectable from "./installed-extensions.injectable";
|
||||||
|
|
||||||
|
export type GetInstalledExtension = (id: LensExtensionId) => InstalledExtension | undefined;
|
||||||
|
|
||||||
|
const getInstalledExtensionInjectable = getInjectable({
|
||||||
|
id: "get-installed-extension",
|
||||||
|
instantiate: (di): GetInstalledExtension => {
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
|
||||||
|
return (id) => installedExtensions.get(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getInstalledExtensionInjectable;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 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 { observable } from "mobx";
|
||||||
|
import type { InstalledExtension } from "../../../extensions/common-api";
|
||||||
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
const installedExtensionsInjectable = getInjectable({
|
||||||
|
id: "installed-extensions",
|
||||||
|
instantiate: () => observable.map<LensExtensionId, InstalledExtension>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installedExtensionsInjectable;
|
||||||
@ -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 { computed } from "mobx";
|
||||||
|
import installedExtensionsInjectable from "./installed-extensions.injectable";
|
||||||
|
|
||||||
|
const installedUserExtensionsInjectable = getInjectable({
|
||||||
|
id: "installed-user-extensions",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
|
||||||
|
return computed(() => new Map((
|
||||||
|
installedExtensions.toJSON()
|
||||||
|
.filter(([, ext]) => !ext.isBundled)
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installedUserExtensionsInjectable;
|
||||||
@ -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 { reaction } from "mobx";
|
||||||
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import type { ExtensionLoading } from "./finalize-extension-loading.injectable";
|
||||||
|
import finalizeExtensionLoadingInjectable from "./finalize-extension-loading.injectable";
|
||||||
|
import loadBundledExtensionsInjectable from "./load-bundled-extensions.injectable";
|
||||||
|
import loadUserExtensionsInjectable from "./load-user-extensions.injectable";
|
||||||
|
import extensionLoadingLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export type AutoInitExtensions = () => Promise<ExtensionLoading[]>;
|
||||||
|
|
||||||
|
const autoInitExtensionsInjectable = getInjectable({
|
||||||
|
id: "auto-init-extensions",
|
||||||
|
instantiate: (di): AutoInitExtensions => {
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
const logger = di.inject(extensionLoadingLoggerInjectable);
|
||||||
|
const loadBundledExtensions = di.inject(loadBundledExtensionsInjectable);
|
||||||
|
const loadUserExtensions = di.inject(loadUserExtensionsInjectable);
|
||||||
|
const finalizeExtensionLoading = di.inject(finalizeExtensionLoadingInjectable);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
logger.info("auto initializing extensions");
|
||||||
|
|
||||||
|
const bundledExtensions = await loadBundledExtensions();
|
||||||
|
const userExtensions = await loadUserExtensions(installedExtensions.toJSON());
|
||||||
|
const loadedExtensions = await finalizeExtensionLoading([
|
||||||
|
...bundledExtensions,
|
||||||
|
...userExtensions,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Setup reaction to load extensions on JSON changes
|
||||||
|
reaction(() => installedExtensions.toJSON(), installedExtensions => {
|
||||||
|
void (async () => {
|
||||||
|
const userExtensions = await loadUserExtensions(installedExtensions);
|
||||||
|
|
||||||
|
await finalizeExtensionLoading(userExtensions);
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
return loadedExtensions;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default autoInitExtensionsInjectable;
|
||||||
@ -12,12 +12,8 @@ export const loadedExtensionsChannel: RequestChannel<void, [LensExtensionId, Ins
|
|||||||
id: "loaded-extensions",
|
id: "loaded-extensions",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const extensionStateUpdateChannel: MessageChannel<[LensExtensionId, InstalledExtension]> = {
|
export const extensionStateUpdatesChannel: MessageChannel<[LensExtensionId, InstalledExtension][]> = {
|
||||||
id: "add-extension",
|
id: "extensions-updated",
|
||||||
};
|
|
||||||
|
|
||||||
export const removeExtensionChannel: MessageChannel<LensExtensionId> = {
|
|
||||||
id: "remove-extension",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bundledExtensionsLoadedChannel: MessageChannel<void> = {
|
export const bundledExtensionsLoadedChannel: MessageChannel<void> = {
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { isEqual } from "lodash";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import { getMessageChannelListenerInjectable } from "../../../../common/utils/channel/message-channel-listener-injection-token";
|
||||||
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import { extensionStateUpdatesChannel } from "./channels";
|
||||||
|
|
||||||
|
const installedExtensionUpdatesListenerInjectable = getMessageChannelListenerInjectable({
|
||||||
|
channel: extensionStateUpdatesChannel,
|
||||||
|
id: "main",
|
||||||
|
handler: (di) => {
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
|
||||||
|
return (newState) => runInAction(() => {
|
||||||
|
for (const [extensionId, installedExtension] of newState) {
|
||||||
|
const oldInstalled = installedExtensions.get(extensionId);
|
||||||
|
|
||||||
|
if (!oldInstalled || !isEqual(oldInstalled, installedExtension)) {
|
||||||
|
installedExtensions.set(extensionId, installedExtension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installedExtensionUpdatesListenerInjectable;
|
||||||
@ -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 extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
|
||||||
|
import type { LensExtension } from "../../../../extensions/lens-extension";
|
||||||
|
import extensionLoadingLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export interface ExtensionLoading {
|
||||||
|
isBundled: boolean;
|
||||||
|
loaded: Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FinalizeExtensionLoading = (instances: LensExtension[]) => Promise<ExtensionLoading[]>;
|
||||||
|
|
||||||
|
const finalizeExtensionLoadingInjectable = getInjectable({
|
||||||
|
id: "finalize-extension-loading",
|
||||||
|
instantiate: (di): FinalizeExtensionLoading => {
|
||||||
|
const logger = di.inject(extensionLoadingLoggerInjectable);
|
||||||
|
|
||||||
|
return async (instances) => {
|
||||||
|
// We first need to wait until each extension's `onActivate` is resolved or rejected,
|
||||||
|
// as this might register new catalog categories. Afterwards we can safely .enable the extension.
|
||||||
|
await Promise.all((
|
||||||
|
instances
|
||||||
|
.map(async instance => {
|
||||||
|
try {
|
||||||
|
await instance.activate();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`activation extension error`, { extId: instance.id, error });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
for (const extension of instances) {
|
||||||
|
di.inject(extensionInjectable, extension).register();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances.map(ext => ({
|
||||||
|
isBundled: ext.isBundled,
|
||||||
|
loaded: (async () => {
|
||||||
|
try {
|
||||||
|
await ext.enable();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`failed to enable`, { ext, err });
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default finalizeExtensionLoadingInjectable;
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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 { iter } from "../../../../common/utils";
|
||||||
|
import extensionInstancesInjectable from "../../../../extensions/extension-loader/extension-instances.injectable";
|
||||||
|
import type { LensExtension } from "../../../../extensions/lens-extension";
|
||||||
|
import extensionsWithoutInstancesByNameInjectable from "./non-instances-by-name.injectable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find an extension by its name. If found it will be returned.
|
||||||
|
*
|
||||||
|
* If the extension is installed but doesn't provide an instance for this environment then
|
||||||
|
* `"not-this-environment"` will be returned. If the extension isn't installed then `"not-installed"`
|
||||||
|
* will be returned
|
||||||
|
*/
|
||||||
|
export type FindExtensionInstanceByName = (name: string) => LensExtension | "not-this-environment" | "not-installed";
|
||||||
|
|
||||||
|
const findExtensionInstanceByNameInjectable = getInjectable({
|
||||||
|
id: "find-extension-instance-by-name",
|
||||||
|
instantiate: (di): FindExtensionInstanceByName => {
|
||||||
|
const extensionsWithoutInstancesByName = di.inject(extensionsWithoutInstancesByNameInjectable);
|
||||||
|
const extensionInstances = di.inject(extensionInstancesInjectable);
|
||||||
|
|
||||||
|
return (name) => {
|
||||||
|
if (extensionsWithoutInstancesByName.has(name)) {
|
||||||
|
return "not-this-environment";
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = iter.find(extensionInstances.values(), instance => instance.name === name);
|
||||||
|
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "not-installed";
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default findExtensionInstanceByNameInjectable;
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* 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 getDirnameOfPathInjectable from "../../../../common/path/get-dirname.injectable";
|
||||||
|
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||||
|
import type { InstalledExtension } from "../../../../extensions/common-api";
|
||||||
|
import { extensionEntryPointNameInjectionToken } from "../../../../extensions/extension-loader/entry-point-name";
|
||||||
|
import type { LensExtensionConstructor } from "../../../../extensions/lens-extension";
|
||||||
|
import extensionLoadingLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export type ImportInstalledExtension = (extension: InstalledExtension) => Promise<LensExtensionConstructor | null>;
|
||||||
|
|
||||||
|
const importInstalledExtensionInjectable = getInjectable({
|
||||||
|
id: "import-installed-extension",
|
||||||
|
instantiate: (di): ImportInstalledExtension => {
|
||||||
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||||
|
const logger = di.inject(extensionLoadingLoggerInjectable);
|
||||||
|
const extensionEntryPointName = di.inject(extensionEntryPointNameInjectionToken);
|
||||||
|
|
||||||
|
return async (extension) => {
|
||||||
|
const extRelativePath = extension.manifest[extensionEntryPointName];
|
||||||
|
|
||||||
|
if (!extRelativePath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extAbsolutePath = joinPaths(getDirnameOfPath(extension.manifestPath), extRelativePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const LensExtensionClass = (await import(extAbsolutePath)).default;
|
||||||
|
|
||||||
|
if (typeof LensExtensionClass === "function") {
|
||||||
|
return LensExtensionClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(`the ${extensionEntryPointName} entry point for "${extension.manifest.name}" is invalid`);
|
||||||
|
} catch (error) {
|
||||||
|
const message = (error instanceof Error ? error.stack : undefined) || error;
|
||||||
|
|
||||||
|
logger.error(`can't load ${extensionEntryPointName} for "${extension.manifest.name}": ${message}`, { extension });
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default importInstalledExtensionInjectable;
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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 { bundledExtensionInjectionToken } from "../../../../common/library";
|
||||||
|
import { isDefined } from "../../../../common/utils";
|
||||||
|
import { extensionEntryPointNameInjectionToken } from "../../../../extensions/extension-loader/entry-point-name";
|
||||||
|
import extensionInstancesInjectable from "../../../../extensions/extension-loader/extension-instances.injectable";
|
||||||
|
import type { LensExtension } from "../../../../extensions/lens-extension";
|
||||||
|
import extensionLoadingLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export type LoadBundledExtensions = () => Promise<LensExtension[]>;
|
||||||
|
|
||||||
|
const loadBundledExtensionsInjectable = getInjectable({
|
||||||
|
id: "load-bundled-extensions",
|
||||||
|
instantiate: (di): LoadBundledExtensions => {
|
||||||
|
const bundledExtensions = di.injectMany(bundledExtensionInjectionToken);
|
||||||
|
const extensionEntryPointName = di.inject(extensionEntryPointNameInjectionToken);
|
||||||
|
const extensionInstances = di.inject(extensionInstancesInjectable);
|
||||||
|
const logger = di.inject(extensionLoadingLoggerInjectable);
|
||||||
|
|
||||||
|
return async () => (
|
||||||
|
(await Promise.all(bundledExtensions
|
||||||
|
.map(async extension => {
|
||||||
|
try {
|
||||||
|
const LensExtensionClass = await extension[extensionEntryPointName]();
|
||||||
|
|
||||||
|
if (!LensExtensionClass) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new LensExtensionClass({
|
||||||
|
id: extension.manifest.name,
|
||||||
|
isBundled: true,
|
||||||
|
isCompatible: true,
|
||||||
|
isEnabled: true,
|
||||||
|
manifest: extension.manifest,
|
||||||
|
});
|
||||||
|
|
||||||
|
extensionInstances.set(extension.manifest.name, instance);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`error loading extension`, { ext: extension, err });
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
.filter(isDefined)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default loadBundledExtensionsInjectable;
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 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 { isDefined } from "../../../../common/utils";
|
||||||
|
import type { ExternalInstalledExtension } from "../../../../extensions/extension-discovery/extension-discovery";
|
||||||
|
import extensionInstancesInjectable from "../../../../extensions/extension-loader/extension-instances.injectable";
|
||||||
|
import type { LensExtension, LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
import importInstalledExtensionInjectable from "./import-installed-extension.injectable";
|
||||||
|
import extensionLoadingLoggerInjectable from "./logger.injectable";
|
||||||
|
import extensionsWithoutInstancesByNameInjectable from "./non-instances-by-name.injectable";
|
||||||
|
import removeExtensionInstanceInjectable from "./remove-instance.injectable";
|
||||||
|
|
||||||
|
export type LoadUserExtensions = (installedExtensions: [LensExtensionId, ExternalInstalledExtension][]) => Promise<LensExtension[]>;
|
||||||
|
|
||||||
|
const loadUserExtensionsInjectable = getInjectable({
|
||||||
|
id: "load-user-extensions",
|
||||||
|
instantiate: (di): LoadUserExtensions => {
|
||||||
|
const importInstalledExtension = di.inject(importInstalledExtensionInjectable);
|
||||||
|
const removeExtensionInstance = di.inject(removeExtensionInstanceInjectable);
|
||||||
|
const extensionsWithoutInstancesByName = di.inject(extensionsWithoutInstancesByNameInjectable);
|
||||||
|
const extensionInstances = di.inject(extensionInstancesInjectable);
|
||||||
|
const logger = di.inject(extensionLoadingLoggerInjectable);
|
||||||
|
|
||||||
|
return async (installedExtensions) => {
|
||||||
|
const instances = await Promise.all((
|
||||||
|
installedExtensions
|
||||||
|
.map(async ([extId, extension]) => {
|
||||||
|
const alreadyInit = extensionInstances.has(extId) || extensionsWithoutInstancesByName.has(extension.manifest.name);
|
||||||
|
|
||||||
|
if (extension.isCompatible && extension.isEnabled && !alreadyInit) {
|
||||||
|
try {
|
||||||
|
const LensExtensionClass = await importInstalledExtension(extension);
|
||||||
|
|
||||||
|
if (!LensExtensionClass) {
|
||||||
|
extensionsWithoutInstancesByName.add(extension.manifest.name);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new LensExtensionClass(extension);
|
||||||
|
|
||||||
|
extensionInstances.set(extId, instance);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`error loading extension`, { ext: extension, err });
|
||||||
|
}
|
||||||
|
} else if (!extension.isEnabled && alreadyInit) {
|
||||||
|
removeExtensionInstance(extId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
return instances.filter(isDefined);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default loadUserExtensionsInjectable;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 prefixedLoggerInjectable from "../../../../common/logger/prefixed-logger.injectable";
|
||||||
|
|
||||||
|
const extensionLoadingLoggerInjectable = getInjectable({
|
||||||
|
id: "extension-loading-logger",
|
||||||
|
instantiate: (di) => di.inject(prefixedLoggerInjectable, "EXTENSION-LOADER"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionLoadingLoggerInjectable;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 { observable } from "mobx";
|
||||||
|
|
||||||
|
const extensionsWithoutInstancesByNameInjectable = getInjectable({
|
||||||
|
id: "extensions-without-instances-by-name",
|
||||||
|
instantiate: () => observable.set<string>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionsWithoutInstancesByNameInjectable;
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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 { action } from "mobx";
|
||||||
|
import extensionInstancesInjectable from "../../../../extensions/extension-loader/extension-instances.injectable";
|
||||||
|
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
|
||||||
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
import extensionLoadingLoggerInjectable from "./logger.injectable";
|
||||||
|
import extensionsWithoutInstancesByNameInjectable from "./non-instances-by-name.injectable";
|
||||||
|
|
||||||
|
export type RemoveExtensionInstance = (id: LensExtensionId) => void;
|
||||||
|
|
||||||
|
const removeExtensionInstanceInjectable = getInjectable({
|
||||||
|
id: "remove-extension-instance",
|
||||||
|
instantiate: (di): RemoveExtensionInstance => {
|
||||||
|
const logger = di.inject(extensionLoadingLoggerInjectable);
|
||||||
|
const extensionInstances = di.inject(extensionInstancesInjectable);
|
||||||
|
const extensionsWithoutInstancesByName = di.inject(extensionsWithoutInstancesByNameInjectable);
|
||||||
|
|
||||||
|
return action((id) => {
|
||||||
|
logger.info(`deleting extension instance ${id}`);
|
||||||
|
const instance = extensionInstances.get(id);
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
instance.disable();
|
||||||
|
di.inject(extensionInjectable, instance).deregister();
|
||||||
|
extensionInstances.delete(id);
|
||||||
|
extensionsWithoutInstancesByName.delete(instance.name);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`deactivation extension error`, { id, error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeExtensionInstanceInjectable;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens";
|
||||||
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import { loadedExtensionsChannel } from "../common/channels";
|
||||||
|
|
||||||
|
const handleLoadedExtensionRequestsInjectable = getRequestChannelListenerInjectable({
|
||||||
|
channel: loadedExtensionsChannel,
|
||||||
|
handler: (di) => {
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
|
||||||
|
return () => installedExtensions.toJSON();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default handleLoadedExtensionRequestsInjectable;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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 { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token";
|
||||||
|
import { onLoadOfApplicationInjectionToken } from "../../../../main/library";
|
||||||
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import { extensionStateUpdatesChannel } from "../common/channels";
|
||||||
|
|
||||||
|
const setupInstalledExtensionsBroadcastingInjectable = getInjectable({
|
||||||
|
id: "setup-installed-extensions-broadcasting",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "setup-installed-extensions-broadcasting",
|
||||||
|
run: () => {
|
||||||
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
|
||||||
|
autorun(() => sendMessageToChannel(extensionStateUpdatesChannel, installedExtensions.toJSON()));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: onLoadOfApplicationInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setupInstalledExtensionsBroadcastingInjectable;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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 extensionsStoreInjectable from "../../../../extensions/extensions-store/extensions-store.injectable";
|
||||||
|
import { onLoadOfApplicationInjectionToken } from "../../../../main/library";
|
||||||
|
import installedUserExtensionsInjectable from "../../common/user-extensions.injectable";
|
||||||
|
|
||||||
|
const syncExtensionEnabledStateWithStoreInjectable = getInjectable({
|
||||||
|
id: "sync-extension-enabled-state-with-store",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "sync-extension-enabled-state-with-store",
|
||||||
|
run: () => {
|
||||||
|
const extensionsStore = di.inject(extensionsStoreInjectable);
|
||||||
|
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
||||||
|
|
||||||
|
autorun(() => {
|
||||||
|
extensionsStore.state.merge((
|
||||||
|
[...installedUserExtensions.get().entries()]
|
||||||
|
.map(([extId, extension]) => [extId, {
|
||||||
|
enabled: extension.isEnabled,
|
||||||
|
name: extension.manifest.name,
|
||||||
|
}] as const)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: onLoadOfApplicationInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default syncExtensionEnabledStateWithStoreInjectable;
|
||||||
@ -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 { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
||||||
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import requestLoadedExtensionsInjectable from "./request-loaded-extensions.injectable";
|
||||||
|
|
||||||
|
const initializeInstalledExtensionsInjectable = getInjectable({
|
||||||
|
id: "initialize-installed-extensions",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "initialize-installed-extensions",
|
||||||
|
run: async () => {
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
const requestLoadedExtensions = di.inject(requestLoadedExtensionsInjectable);
|
||||||
|
|
||||||
|
installedExtensions.replace(await requestLoadedExtensions());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: beforeFrameStartsSecondInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default initializeInstalledExtensionsInjectable;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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 requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
||||||
|
import { loadedExtensionsChannel } from "../common/channels";
|
||||||
|
|
||||||
|
const requestLoadedExtensionsInjectable = getInjectable({
|
||||||
|
id: "request-loaded-extensions",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const requestFromChannel = di.inject(requestFromChannelInjectable);
|
||||||
|
|
||||||
|
return () => requestFromChannel(loadedExtensionsChannel);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestLoadedExtensionsInjectable;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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 { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token";
|
||||||
|
import { beforeFrameStartsFirstInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
||||||
|
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
|
||||||
|
import { extensionStateUpdatesChannel } from "../common/channels";
|
||||||
|
|
||||||
|
const setupInstalledExtensionsBroadcastingInjectable = getInjectable({
|
||||||
|
id: "setup-installed-extensions-broadcasting",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "setup-installed-extensions-broadcasting",
|
||||||
|
run: () => {
|
||||||
|
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
|
const installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
|
|
||||||
|
autorun(() => sendMessageToChannel(extensionStateUpdatesChannel, installedExtensions.toJSON()));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: beforeFrameStartsFirstInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setupInstalledExtensionsBroadcastingInjectable;
|
||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getMessageChannelListenerInjectable } from "../../../../common/utils/channel/message-channel-listener-injection-token";
|
import { getMessageChannelListenerInjectable } from "../../../../common/utils/channel/message-channel-listener-injection-token";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionInstancesInjectable from "../../../../extensions/extension-loader/extension-instances.injectable";
|
||||||
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
|
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
|
||||||
import { navigateForExtensionChannel } from "../common/channel";
|
import { navigateForExtensionChannel } from "../common/channel";
|
||||||
|
|
||||||
@ -11,13 +11,13 @@ const navigateForExtensionListenerInjectable = getMessageChannelListenerInjectab
|
|||||||
channel: navigateForExtensionChannel,
|
channel: navigateForExtensionChannel,
|
||||||
id: "main",
|
id: "main",
|
||||||
handler: (di) => {
|
handler: (di) => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
const extensionInstances = di.inject(extensionInstancesInjectable);
|
||||||
|
|
||||||
return ({ extId, pageId, params }) => {
|
return ({ extId, pageId, params }) => {
|
||||||
const extension = extensionLoader.getInstanceById(extId) as LensRendererExtension | undefined;
|
const extension = extensionInstances.get(extId);
|
||||||
|
|
||||||
if (extension) {
|
if (extension) {
|
||||||
extension.navigate(pageId, params);
|
(extension as LensRendererExtension).navigate(pageId, params);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,24 +3,23 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import { LensProtocolRouterMain } from "./lens-protocol-router-main";
|
import { LensProtocolRouterMain } from "./lens-protocol-router-main";
|
||||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||||
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
|
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
|
||||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
|
import findExtensionInstanceByNameInjectable from "../../../features/extensions/loader/common/find-instance-by-name.injectable";
|
||||||
|
|
||||||
const lensProtocolRouterMainInjectable = getInjectable({
|
const lensProtocolRouterMainInjectable = getInjectable({
|
||||||
id: "lens-protocol-router-main",
|
id: "lens-protocol-router-main",
|
||||||
|
|
||||||
instantiate: (di) =>
|
instantiate: (di) => new LensProtocolRouterMain({
|
||||||
new LensProtocolRouterMain({
|
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
showApplicationWindow: di.inject(showApplicationWindowInjectable),
|
||||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||||
showApplicationWindow: di.inject(showApplicationWindowInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
findExtensionInstanceByName: di.inject(findExtensionInstanceByNameInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
}),
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default lensProtocolRouterMainInjectable;
|
export default lensProtocolRouterMainInjectable;
|
||||||
|
|||||||
@ -3,11 +3,10 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
|
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import extensionDiscoveryInjectable from "../../../extensions/extension-discovery/extension-discovery.injectable";
|
import extensionDiscoveryInjectable from "../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
import autoInitExtensionsInjectable from "../../../features/extensions/loader/common/auto-init-extensions.injectable";
|
||||||
|
import removeExtensionInstanceInjectable from "../../../features/extensions/loader/common/remove-instance.injectable";
|
||||||
import showErrorPopupInjectable from "../../electron-app/features/show-error-popup.injectable";
|
import showErrorPopupInjectable from "../../electron-app/features/show-error-popup.injectable";
|
||||||
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
|
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
|
||||||
|
|
||||||
@ -17,8 +16,9 @@ const initializeExtensionsInjectable = getInjectable({
|
|||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
|
||||||
const showErrorPopup = di.inject(showErrorPopupInjectable);
|
const showErrorPopup = di.inject(showErrorPopupInjectable);
|
||||||
|
const removeExtensionInstance = di.inject(removeExtensionInstanceInjectable);
|
||||||
|
const autoInitExtensions = di.inject(autoInitExtensionsInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: "initialize-extensions",
|
id: "initialize-extensions",
|
||||||
@ -27,30 +27,18 @@ const initializeExtensionsInjectable = getInjectable({
|
|||||||
|
|
||||||
await extensionDiscovery.init();
|
await extensionDiscovery.init();
|
||||||
|
|
||||||
await extensionLoader.init();
|
await autoInitExtensions();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const extensions = await extensionDiscovery.load();
|
await extensionDiscovery.load();
|
||||||
|
extensionDiscovery.events.on("remove", (ext) => removeExtensionInstance(ext.id));
|
||||||
|
|
||||||
// Start watching after bundled extensions are loaded
|
// Start watching after bundled extensions are loaded
|
||||||
extensionDiscovery.watchExtensions();
|
extensionDiscovery.watchExtensions();
|
||||||
|
|
||||||
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
|
||||||
extensionDiscovery.events
|
|
||||||
.on("add", (extension: InstalledExtension) => {
|
|
||||||
extensionLoader.addExtension(extension);
|
|
||||||
})
|
|
||||||
.on("remove", (lensExtensionId: LensExtensionId) => {
|
|
||||||
extensionLoader.removeExtension(lensExtensionId);
|
|
||||||
});
|
|
||||||
|
|
||||||
extensionLoader.initExtensions(extensions);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorPopup(
|
showErrorPopup(
|
||||||
"Lens Error",
|
"Lens Error",
|
||||||
`Could not load extensions${
|
`Could not load extensions${error?.message ? `: ${error.message}` : ""}`,
|
||||||
error?.message ? `: ${error.message}` : ""
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@ -6,12 +6,10 @@
|
|||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { ExtensionDiscovery } from "../../../../extensions/extension-discovery/extension-discovery";
|
import type { ExtensionDiscovery, InstalledExtension } from "../../../../extensions/extension-discovery/extension-discovery";
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
import { ConfirmDialog } from "../../confirm-dialog";
|
||||||
import { Extensions } from "../extensions";
|
import { Extensions } from "../extensions";
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import type { DiRender } from "../../test-utils/renderFor";
|
import type { DiRender } from "../../test-utils/renderFor";
|
||||||
import { renderFor } from "../../test-utils/renderFor";
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable";
|
import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
@ -22,15 +20,18 @@ import type { InstallExtensionFromInput } from "../install-extension-from-input.
|
|||||||
import installExtensionFromInputInjectable from "../install-extension-from-input.injectable";
|
import installExtensionFromInputInjectable from "../install-extension-from-input.injectable";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
|
import type { ObservableMap } from "mobx";
|
||||||
import { observable, when } from "mobx";
|
import { observable, when } from "mobx";
|
||||||
import type { RemovePath } from "../../../../common/fs/remove.injectable";
|
import type { RemovePath } from "../../../../common/fs/remove.injectable";
|
||||||
import removePathInjectable from "../../../../common/fs/remove.injectable";
|
import removePathInjectable from "../../../../common/fs/remove.injectable";
|
||||||
import type { DownloadBinary } from "../../../../common/fetch/download-binary.injectable";
|
import type { DownloadBinary } from "../../../../common/fetch/download-binary.injectable";
|
||||||
import downloadBinaryInjectable from "../../../../common/fetch/download-binary.injectable";
|
import downloadBinaryInjectable from "../../../../common/fetch/download-binary.injectable";
|
||||||
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
|
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
|
||||||
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
|
import installedExtensionsInjectable from "../../../../features/extensions/common/installed-extensions.injectable";
|
||||||
|
|
||||||
describe("Extensions", () => {
|
describe("Extensions", () => {
|
||||||
let extensionLoader: ExtensionLoader;
|
let installedExtensions: ObservableMap<LensExtensionId, InstalledExtension>;
|
||||||
let extensionDiscovery: ExtensionDiscovery;
|
let extensionDiscovery: ExtensionDiscovery;
|
||||||
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
|
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
|
||||||
let extensionInstallationStateStore: ExtensionInstallationStateStore;
|
let extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||||
@ -56,11 +57,11 @@ describe("Extensions", () => {
|
|||||||
downloadBinary = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); });
|
downloadBinary = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); });
|
||||||
di.override(downloadBinaryInjectable, () => downloadBinary);
|
di.override(downloadBinaryInjectable, () => downloadBinary);
|
||||||
|
|
||||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
installedExtensions = di.inject(installedExtensionsInjectable);
|
||||||
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||||
extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||||
|
|
||||||
extensionLoader.addExtension({
|
installedExtensions.set("extensionId", {
|
||||||
id: "extensionId",
|
id: "extensionId",
|
||||||
manifest: {
|
manifest: {
|
||||||
name: "test",
|
name: "test",
|
||||||
|
|||||||
@ -3,154 +3,126 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import uninstallExtensionInjectable from "../uninstall-extension.injectable";
|
import uninstallExtensionInjectable from "../uninstall-extension.injectable";
|
||||||
import type { UnpackExtension } from "./unpack-extension.injectable";
|
|
||||||
import unpackExtensionInjectable from "./unpack-extension.injectable";
|
import unpackExtensionInjectable from "./unpack-extension.injectable";
|
||||||
import type { GetExtensionDestFolder } from "./get-extension-dest-folder.injectable";
|
|
||||||
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
||||||
import type { CreateTempFilesAndValidate } from "./create-temp-files-and-validate.injectable";
|
|
||||||
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate.injectable";
|
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
import type { Disposer } from "../../../../common/utils";
|
import type { Disposer } from "../../../../common/utils";
|
||||||
import { disposer } from "../../../../common/utils";
|
import { disposer } from "../../../../common/utils";
|
||||||
import type { ShowNotification } from "../../notifications";
|
|
||||||
import { Button } from "../../button";
|
import { Button } from "../../button";
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { remove as removeDir } from "fs-extra";
|
import { remove as removeDir } from "fs-extra";
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
|
||||||
import { ExtensionInstallationState } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
import { ExtensionInstallationState } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||||
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
|
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
|
||||||
|
import getInstalledExtensionInjectable from "../../../../features/extensions/common/get-installed-extension.injectable";
|
||||||
|
|
||||||
export interface InstallRequest {
|
export interface InstallRequest {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
extensionLoader: ExtensionLoader;
|
|
||||||
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
|
||||||
unpackExtension: UnpackExtension;
|
|
||||||
createTempFilesAndValidate: CreateTempFilesAndValidate;
|
|
||||||
getExtensionDestFolder: GetExtensionDestFolder;
|
|
||||||
installStateStore: ExtensionInstallationStateStore;
|
|
||||||
showErrorNotification: ShowNotification;
|
|
||||||
showInfoNotification: ShowNotification;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AttemptInstall = (request: InstallRequest, cleanup?: Disposer) => Promise<void>;
|
export type AttemptInstall = (request: InstallRequest, cleanup?: Disposer) => Promise<void>;
|
||||||
|
|
||||||
const attemptInstall = ({
|
|
||||||
extensionLoader,
|
|
||||||
uninstallExtension,
|
|
||||||
unpackExtension,
|
|
||||||
createTempFilesAndValidate,
|
|
||||||
getExtensionDestFolder,
|
|
||||||
installStateStore,
|
|
||||||
showErrorNotification,
|
|
||||||
showInfoNotification,
|
|
||||||
}: Dependencies): AttemptInstall =>
|
|
||||||
async (request, cleanup) => {
|
|
||||||
const dispose = disposer(
|
|
||||||
installStateStore.startPreInstall(),
|
|
||||||
cleanup,
|
|
||||||
);
|
|
||||||
|
|
||||||
const validatedRequest = await createTempFilesAndValidate(request);
|
|
||||||
|
|
||||||
if (!validatedRequest) {
|
|
||||||
return dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, version, description } = validatedRequest.manifest;
|
|
||||||
const curState = installStateStore.getInstallationState(validatedRequest.id);
|
|
||||||
|
|
||||||
if (curState !== ExtensionInstallationState.IDLE) {
|
|
||||||
dispose();
|
|
||||||
|
|
||||||
return void showErrorNotification(
|
|
||||||
<div className="flex column gaps">
|
|
||||||
<b>Extension Install Collision:</b>
|
|
||||||
<p>
|
|
||||||
{"The "}
|
|
||||||
<em>{name}</em>
|
|
||||||
{` extension is currently ${curState.toLowerCase()}.`}
|
|
||||||
</p>
|
|
||||||
<p>Will not proceed with this current install request.</p>
|
|
||||||
</div>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensionFolder = getExtensionDestFolder(name);
|
|
||||||
const installedExtension = extensionLoader.getExtensionById(validatedRequest.id);
|
|
||||||
|
|
||||||
if (installedExtension) {
|
|
||||||
const { version: oldVersion } = installedExtension.manifest;
|
|
||||||
|
|
||||||
// confirm to uninstall old version before installing new version
|
|
||||||
const removeNotification = showInfoNotification(
|
|
||||||
<div className="InstallingExtensionNotification flex gaps align-center">
|
|
||||||
<div className="flex column gaps">
|
|
||||||
<p>
|
|
||||||
{"Install extension "}
|
|
||||||
<b>{`${name}@${version}`}</b>
|
|
||||||
?
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{"Description: "}
|
|
||||||
<em>{description}</em>
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
className="remove-folder-warning"
|
|
||||||
onClick={() => shell.openPath(extensionFolder)}
|
|
||||||
>
|
|
||||||
<b>Warning:</b>
|
|
||||||
{` ${name}@${oldVersion} will be removed before installation.`}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
autoFocus
|
|
||||||
label="Install"
|
|
||||||
onClick={async () => {
|
|
||||||
removeNotification();
|
|
||||||
|
|
||||||
if (await uninstallExtension(validatedRequest.id)) {
|
|
||||||
await unpackExtension(validatedRequest, dispose);
|
|
||||||
} else {
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
onClose: dispose,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// clean up old data if still around
|
|
||||||
await removeDir(extensionFolder);
|
|
||||||
|
|
||||||
// install extension if not yet exists
|
|
||||||
await unpackExtension(validatedRequest, dispose);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const attemptInstallInjectable = getInjectable({
|
const attemptInstallInjectable = getInjectable({
|
||||||
id: "attempt-install",
|
id: "attempt-install",
|
||||||
instantiate: (di) => attemptInstall({
|
instantiate: (di): AttemptInstall => {
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
const uninstallExtension = di.inject(uninstallExtensionInjectable);
|
||||||
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
const unpackExtension = di.inject(unpackExtensionInjectable);
|
||||||
unpackExtension: di.inject(unpackExtensionInjectable),
|
const createTempFilesAndValidate = di.inject(createTempFilesAndValidateInjectable);
|
||||||
createTempFilesAndValidate: di.inject(createTempFilesAndValidateInjectable),
|
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
||||||
getExtensionDestFolder: di.inject(getExtensionDestFolderInjectable),
|
const installStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||||
installStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||||
showInfoNotification: di.inject(showInfoNotificationInjectable),
|
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
||||||
}),
|
|
||||||
|
return async (request, cleanup) => {
|
||||||
|
const dispose = disposer(
|
||||||
|
installStateStore.startPreInstall(),
|
||||||
|
cleanup,
|
||||||
|
);
|
||||||
|
|
||||||
|
const validatedRequest = await createTempFilesAndValidate(request);
|
||||||
|
|
||||||
|
if (!validatedRequest) {
|
||||||
|
return dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, version, description } = validatedRequest.manifest;
|
||||||
|
const curState = installStateStore.getInstallationState(validatedRequest.id);
|
||||||
|
|
||||||
|
if (curState !== ExtensionInstallationState.IDLE) {
|
||||||
|
dispose();
|
||||||
|
|
||||||
|
return void showErrorNotification(
|
||||||
|
<div className="flex column gaps">
|
||||||
|
<b>Extension Install Collision:</b>
|
||||||
|
<p>
|
||||||
|
{"The "}
|
||||||
|
<em>{name}</em>
|
||||||
|
{` extension is currently ${curState.toLowerCase()}.`}
|
||||||
|
</p>
|
||||||
|
<p>Will not proceed with this current install request.</p>
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionFolder = getExtensionDestFolder(name);
|
||||||
|
const installedExtension = getInstalledExtension(validatedRequest.id);
|
||||||
|
|
||||||
|
if (installedExtension) {
|
||||||
|
const { version: oldVersion } = installedExtension.manifest;
|
||||||
|
|
||||||
|
// confirm to uninstall old version before installing new version
|
||||||
|
const removeNotification = showInfoNotification(
|
||||||
|
<div className="InstallingExtensionNotification flex gaps align-center">
|
||||||
|
<div className="flex column gaps">
|
||||||
|
<p>
|
||||||
|
{"Install extension "}
|
||||||
|
<b>{`${name}@${version}`}</b>
|
||||||
|
?
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{"Description: "}
|
||||||
|
<em>{description}</em>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="remove-folder-warning"
|
||||||
|
onClick={() => shell.openPath(extensionFolder)}
|
||||||
|
>
|
||||||
|
<b>Warning:</b>
|
||||||
|
{` ${name}@${oldVersion} will be removed before installation.`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
autoFocus
|
||||||
|
label="Install"
|
||||||
|
onClick={async () => {
|
||||||
|
removeNotification();
|
||||||
|
|
||||||
|
if (await uninstallExtension(validatedRequest.id)) {
|
||||||
|
await unpackExtension(validatedRequest, dispose);
|
||||||
|
} else {
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
onClose: dispose,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// clean up old data if still around
|
||||||
|
await removeDir(extensionFolder);
|
||||||
|
|
||||||
|
// install extension if not yet exists
|
||||||
|
await unpackExtension(validatedRequest, dispose);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default attemptInstallInjectable;
|
export default attemptInstallInjectable;
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
|
||||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
import type { Disposer } from "../../../../common/utils";
|
import type { Disposer } from "../../../../common/utils";
|
||||||
@ -19,19 +18,22 @@ import extractTarInjectable from "../../../../common/fs/extract-tar.injectable";
|
|||||||
import loggerInjectable from "../../../../common/logger.injectable";
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
|
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
|
||||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||||
|
import installedUserExtensionsInjectable from "../../../../features/extensions/common/user-extensions.injectable";
|
||||||
|
import enableExtensionInjectable from "../enable-extension.injectable";
|
||||||
|
|
||||||
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
|
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
|
||||||
|
|
||||||
const unpackExtensionInjectable = getInjectable({
|
const unpackExtensionInjectable = getInjectable({
|
||||||
id: "unpack-extension",
|
id: "unpack-extension",
|
||||||
instantiate: (di): UnpackExtension => {
|
instantiate: (di): UnpackExtension => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
|
||||||
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||||
const extractTar = di.inject(extractTarInjectable);
|
const extractTar = di.inject(extractTarInjectable);
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
const showInfoNotification = di.inject(showInfoNotificationInjectable);
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
|
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
||||||
|
const enableExtension = di.inject(enableExtensionInjectable);
|
||||||
|
|
||||||
return async (request, disposeDownloading) => {
|
return async (request, disposeDownloading) => {
|
||||||
const {
|
const {
|
||||||
@ -73,10 +75,10 @@ const unpackExtensionInjectable = getInjectable({
|
|||||||
await fse.move(unpackedRootFolder, extensionFolder, { overwrite: true });
|
await fse.move(unpackedRootFolder, extensionFolder, { overwrite: true });
|
||||||
|
|
||||||
// wait for the loader has actually install it
|
// wait for the loader has actually install it
|
||||||
await when(() => extensionLoader.userExtensions.get().has(id));
|
await when(() => installedUserExtensions.get().has(id));
|
||||||
|
|
||||||
// Enable installed extensions by default.
|
// Enable installed extensions by default.
|
||||||
extensionLoader.setIsEnabled(id, true);
|
enableExtension(id);
|
||||||
|
|
||||||
showInfoNotification((
|
showInfoNotification((
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -3,24 +3,31 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
import { action } from "mobx";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
import getInstalledExtensionInjectable from "../../../features/extensions/common/get-installed-extension.injectable";
|
||||||
|
|
||||||
export type DisableExtension = (extId: LensExtensionId) => void;
|
export type DisableExtension = (id: LensExtensionId) => void;
|
||||||
|
|
||||||
const disableExtensionInjectable = getInjectable({
|
const disableExtensionInjectable = getInjectable({
|
||||||
id: "disable-extension",
|
id: "disable-extension",
|
||||||
|
|
||||||
instantiate: (di): DisableExtension => {
|
instantiate: (di): DisableExtension => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
||||||
|
|
||||||
return (extId) => {
|
return action((id) => {
|
||||||
const ext = extensionLoader.getExtensionById(extId);
|
const extension = getInstalledExtension(id);
|
||||||
|
|
||||||
if (ext && !ext.isBundled) {
|
if (!extension) {
|
||||||
ext.isEnabled = false;
|
throw new Error(`Missing extension with id="${id}"`);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
if (extension.isBundled) {
|
||||||
|
throw new Error("Cannot change the enabled state for bundled extensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension.isEnabled = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,24 +3,31 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
import { action } from "mobx";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
import getInstalledExtensionInjectable from "../../../features/extensions/common/get-installed-extension.injectable";
|
||||||
|
|
||||||
export type EnableExtension = (extId: LensExtensionId) => void;
|
export type EnableExtension = (id: LensExtensionId) => void;
|
||||||
|
|
||||||
const enableExtensionInjectable = getInjectable({
|
const enableExtensionInjectable = getInjectable({
|
||||||
id: "enable-extension",
|
id: "enable-extension",
|
||||||
|
|
||||||
instantiate: (di): EnableExtension => {
|
instantiate: (di): EnableExtension => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
||||||
|
|
||||||
return (extId) => {
|
return action((id) => {
|
||||||
const ext = extensionLoader.getExtensionById(extId);
|
const extension = getInstalledExtension(id);
|
||||||
|
|
||||||
if (ext && !ext.isBundled) {
|
if (!extension) {
|
||||||
ext.isEnabled = true;
|
throw new Error(`Missing extension with id="${id}"`);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
if (extension.isBundled) {
|
||||||
|
throw new Error("Cannot change the enabled state for bundled extensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension.isEnabled = true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
import extensionDiscoveryInjectable from "../../../extensions/extension-discovery/extension-discovery.injectable";
|
import extensionDiscoveryInjectable from "../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
@ -14,20 +13,23 @@ import { when } from "mobx";
|
|||||||
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
|
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
|
||||||
import showSuccessNotificationInjectable from "../notifications/show-success-notification.injectable";
|
import showSuccessNotificationInjectable from "../notifications/show-success-notification.injectable";
|
||||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||||
|
import installedUserExtensionsInjectable from "../../../features/extensions/common/user-extensions.injectable";
|
||||||
|
import getInstalledExtensionInjectable from "../../../features/extensions/common/get-installed-extension.injectable";
|
||||||
|
|
||||||
const uninstallExtensionInjectable = getInjectable({
|
const uninstallExtensionInjectable = getInjectable({
|
||||||
id: "uninstall-extension",
|
id: "uninstall-extension",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
|
||||||
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
|
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
|
||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
|
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
||||||
|
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
|
||||||
|
|
||||||
return async (extensionId: LensExtensionId): Promise<boolean> => {
|
return async (extensionId: LensExtensionId): Promise<boolean> => {
|
||||||
const ext = extensionLoader.getExtensionById(extensionId);
|
const ext = getInstalledExtension(extensionId);
|
||||||
|
|
||||||
if (!ext) {
|
if (!ext) {
|
||||||
logger.debug(`[EXTENSIONS]: cannot uninstall ${extensionId}, was not installed`);
|
logger.debug(`[EXTENSIONS]: cannot uninstall ${extensionId}, was not installed`);
|
||||||
@ -45,7 +47,7 @@ const uninstallExtensionInjectable = getInjectable({
|
|||||||
await extensionDiscovery.uninstallExtension(extensionId);
|
await extensionDiscovery.uninstallExtension(extensionId);
|
||||||
|
|
||||||
// wait for the ExtensionLoader to actually uninstall the extension
|
// wait for the ExtensionLoader to actually uninstall the extension
|
||||||
await when(() => !extensionLoader.userExtensions.get().has(extensionId));
|
await when(() => !installedUserExtensions.get().has(extensionId));
|
||||||
|
|
||||||
showSuccessNotification(
|
showSuccessNotification(
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -4,15 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
import installedUserExtensionsInjectable from "../../../../features/extensions/common/user-extensions.injectable";
|
||||||
|
|
||||||
const userExtensionsInjectable = getInjectable({
|
const userExtensionsInjectable = getInjectable({
|
||||||
id: "user-extensions",
|
id: "user-extensions",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
|
||||||
|
|
||||||
return computed(() => [...extensionLoader.userExtensions.get().values()]);
|
return computed(() => [...installedUserExtensions.get().values()]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionDiscoveryInjectable from "../../extensions/extension-discovery/extension-discovery.injectable";
|
import extensionDiscoveryInjectable from "../../extensions/extension-discovery/extension-discovery.injectable";
|
||||||
import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens";
|
import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens";
|
||||||
import initializeExtensionLoaderInjectable from "../extension-loader/init.injectable";
|
|
||||||
|
|
||||||
const initializeExtensionDiscoveryInjectable = getInjectable({
|
const initializeExtensionDiscoveryInjectable = getInjectable({
|
||||||
id: "initialize-extension-discovery",
|
id: "initialize-extension-discovery",
|
||||||
@ -16,7 +15,6 @@ const initializeExtensionDiscoveryInjectable = getInjectable({
|
|||||||
|
|
||||||
await extensionDiscovery.init();
|
await extensionDiscovery.init();
|
||||||
},
|
},
|
||||||
runAfter: di.inject(initializeExtensionLoaderInjectable),
|
|
||||||
}),
|
}),
|
||||||
injectionToken: beforeFrameStartsSecondInjectionToken,
|
injectionToken: beforeFrameStartsSecondInjectionToken,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,22 +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 extensionLoaderInjectable from "../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens";
|
|
||||||
|
|
||||||
const initializeExtensionLoaderInjectable = getInjectable({
|
|
||||||
id: "initialize-extension-loader",
|
|
||||||
instantiate: (di) => ({
|
|
||||||
id: "initialize-extension-loader",
|
|
||||||
run: async () => {
|
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
|
||||||
|
|
||||||
await extensionLoader.init();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
injectionToken: beforeFrameStartsSecondInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default initializeExtensionLoaderInjectable;
|
|
||||||
@ -9,9 +9,9 @@ import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.inject
|
|||||||
import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable";
|
import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
|
||||||
import loadExtensionsInjectable from "../../load-extensions.injectable";
|
|
||||||
import loggerInjectable from "../../../../common/logger.injectable";
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable";
|
||||||
|
import autoInitExtensionsInjectable from "../../../../features/extensions/loader/common/auto-init-extensions.injectable";
|
||||||
|
|
||||||
const initClusterFrameInjectable = getInjectable({
|
const initClusterFrameInjectable = getInjectable({
|
||||||
id: "init-cluster-frame",
|
id: "init-cluster-frame",
|
||||||
@ -23,7 +23,7 @@ const initClusterFrameInjectable = getInjectable({
|
|||||||
|
|
||||||
return initClusterFrame({
|
return initClusterFrame({
|
||||||
hostedCluster,
|
hostedCluster,
|
||||||
loadExtensions: di.inject(loadExtensionsInjectable),
|
loadExtensions: di.inject(autoInitExtensionsInjectable),
|
||||||
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||||
frameRoutingId: di.inject(frameRoutingIdInjectable),
|
frameRoutingId: di.inject(frameRoutingIdInjectable),
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
|
|||||||
@ -1,17 +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 extensionLoaderInjectable from "../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
|
|
||||||
const loadExtensionsInjectable = getInjectable({
|
|
||||||
id: "load-extensions",
|
|
||||||
instantiate: (di) => {
|
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
|
||||||
|
|
||||||
return () => extensionLoader.autoInitExtensions();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default loadExtensionsInjectable;
|
|
||||||
@ -7,16 +7,16 @@ import bindProtocolAddRouteHandlersInjectable from "../../protocol-handler/bind-
|
|||||||
import lensProtocolRouterRendererInjectable from "../../protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
|
import lensProtocolRouterRendererInjectable from "../../protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable";
|
||||||
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
|
||||||
import registerIpcListenersInjectable from "../../ipc/register-ipc-listeners.injectable";
|
import registerIpcListenersInjectable from "../../ipc/register-ipc-listeners.injectable";
|
||||||
import loadExtensionsInjectable from "../load-extensions.injectable";
|
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import { delay } from "../../../common/utils";
|
import { delay } from "../../../common/utils";
|
||||||
import { broadcastMessage } from "../../../common/ipc";
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
import sendBundledExtensionsLoadedInjectable from "../../../features/extensions/loader/renderer/send-bundled-extensions-loaded.injectable";
|
import sendBundledExtensionsLoadedInjectable from "../../../features/extensions/loader/renderer/send-bundled-extensions-loaded.injectable";
|
||||||
|
import autoInitExtensionsInjectable from "../../../features/extensions/loader/common/auto-init-extensions.injectable";
|
||||||
|
|
||||||
const initRootFrameInjectable = getInjectable({
|
const initRootFrameInjectable = getInjectable({
|
||||||
id: "init-root-frame",
|
id: "init-root-frame",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const loadExtensions = di.inject(loadExtensionsInjectable);
|
const autoInitExtensions = di.inject(autoInitExtensionsInjectable);
|
||||||
const registerIpcListeners = di.inject(registerIpcListenersInjectable);
|
const registerIpcListeners = di.inject(registerIpcListenersInjectable);
|
||||||
const bindProtocolAddRouteHandlers = di.inject(bindProtocolAddRouteHandlersInjectable);
|
const bindProtocolAddRouteHandlers = di.inject(bindProtocolAddRouteHandlersInjectable);
|
||||||
const lensProtocolRouterRenderer = di.inject(lensProtocolRouterRendererInjectable);
|
const lensProtocolRouterRenderer = di.inject(lensProtocolRouterRendererInjectable);
|
||||||
@ -31,7 +31,7 @@ const initRootFrameInjectable = getInjectable({
|
|||||||
// maximum time to let bundled extensions finish loading
|
// maximum time to let bundled extensions finish loading
|
||||||
const timeout = delay(10000);
|
const timeout = delay(10000);
|
||||||
|
|
||||||
const loadingExtensions = await loadExtensions();
|
const loadingExtensions = await autoInitExtensions();
|
||||||
|
|
||||||
const loadingBundledExtensions = loadingExtensions
|
const loadingBundledExtensions = loadingExtensions
|
||||||
.filter((e) => e.isBundled)
|
.filter((e) => e.isBundled)
|
||||||
|
|||||||
@ -6,9 +6,7 @@
|
|||||||
import { clusterActivateHandler, clusterDisconnectHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
import { clusterActivateHandler, clusterDisconnectHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
|
||||||
import type { ClusterId, ClusterState } from "../../common/cluster-types";
|
import type { ClusterId, ClusterState } from "../../common/cluster-types";
|
||||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
|
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
|
||||||
import { extensionDiscoveryStateChannel, extensionLoaderFromMainChannel } from "../../common/ipc/extension-handling";
|
import { extensionDiscoveryStateChannel } from "../../common/ipc/extension-handling";
|
||||||
import type { InstalledExtension } from "../../extensions/extension-discovery/extension-discovery";
|
|
||||||
import type { LensExtensionId } from "../../extensions/lens-extension";
|
|
||||||
import { toJS } from "../utils";
|
import { toJS } from "../utils";
|
||||||
import type { Location } from "history";
|
import type { Location } from "history";
|
||||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
@ -61,7 +59,3 @@ export function requestInitialClusterStates(): Promise<{ id: string; state: Clus
|
|||||||
export function requestInitialExtensionDiscovery(): Promise<{ isLoaded: boolean }> {
|
export function requestInitialExtensionDiscovery(): Promise<{ isLoaded: boolean }> {
|
||||||
return requestMain(extensionDiscoveryStateChannel);
|
return requestMain(extensionDiscoveryStateChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestExtensionLoaderInitialState(): Promise<[LensExtensionId, InstalledExtension][]> {
|
|
||||||
return requestMain(extensionLoaderFromMainChannel);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,22 +3,22 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer";
|
import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer";
|
||||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
import showErrorNotificationInjectable from "../../components/notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../../components/notifications/show-error-notification.injectable";
|
||||||
import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable";
|
import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable";
|
||||||
|
import findExtensionInstanceByNameInjectable from "../../../features/extensions/loader/common/find-instance-by-name.injectable";
|
||||||
|
|
||||||
const lensProtocolRouterRendererInjectable = getInjectable({
|
const lensProtocolRouterRendererInjectable = getInjectable({
|
||||||
id: "lens-protocol-router-renderer",
|
id: "lens-protocol-router-renderer",
|
||||||
|
|
||||||
instantiate: (di) => new LensProtocolRouterRenderer({
|
instantiate: (di) => new LensProtocolRouterRenderer({
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||||
showShortInfoNotification: di.inject(showShortInfoNotificationInjectable),
|
showShortInfoNotification: di.inject(showShortInfoNotificationInjectable),
|
||||||
|
findExtensionInstanceByName: di.inject(findExtensionInstanceByNameInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user