mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce clearer boundry between extensions
- Bundled extensions are always enabled, and are always compatible - Have bundled extensions be loaded asyncronously to support typescript dynamic import (which is typed) as opposed to require Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
d8acff67d1
commit
027e57196a
@ -6,4 +6,6 @@
|
|||||||
// @experimental
|
// @experimental
|
||||||
export { applicationInformationToken } from "./vars/application-information-token";
|
export { applicationInformationToken } from "./vars/application-information-token";
|
||||||
export type { ApplicationInformation } from "./vars/application-information-token";
|
export type { ApplicationInformation } from "./vars/application-information-token";
|
||||||
|
export type { BundledExtension } from "../extensions/extension-discovery/bundled-extension-token";
|
||||||
|
export type { BundledLensExtensionManifest } from "../extensions/lens-extension";
|
||||||
export { bundledExtensionInjectionToken } from "../extensions/extension-discovery/bundled-extension-token";
|
export { bundledExtensionInjectionToken } from "../extensions/extension-discovery/bundled-extension-token";
|
||||||
|
|||||||
@ -209,7 +209,7 @@ export abstract class LensProtocolRouter {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.dependencies.extensionsStore.isEnabled(extension)) {
|
if (!extension.isBundled && !this.dependencies.extensionsStore.isEnabled(extension.id)) {
|
||||||
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@ -114,38 +114,36 @@ describe("ExtensionLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renderer updates extension after ipc broadcast", async () => {
|
it("renderer updates extension after ipc broadcast", async () => {
|
||||||
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
expect(extensionLoader.userExtensions.get().size).toBe(0);
|
||||||
|
|
||||||
await extensionLoader.init();
|
await extensionLoader.init();
|
||||||
await delay(10);
|
await delay(10);
|
||||||
|
|
||||||
// Assert the extensions after the extension broadcast event
|
// Assert the extensions after the extension broadcast event
|
||||||
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`
|
expect(extensionLoader.userExtensions.get()).toEqual(new Map([
|
||||||
Map {
|
["manifest/path", {
|
||||||
"manifest/path" => Object {
|
absolutePath: "/test/1",
|
||||||
"absolutePath": "/test/1",
|
id: "manifest/path",
|
||||||
"id": "manifest/path",
|
isBundled: false,
|
||||||
"isBundled": false,
|
isEnabled: true,
|
||||||
"isEnabled": true,
|
manifest: {
|
||||||
"manifest": Object {
|
name: "TestExtension",
|
||||||
"name": "TestExtension",
|
version: "1.0.0",
|
||||||
"version": "1.0.0",
|
|
||||||
},
|
|
||||||
"manifestPath": "manifest/path",
|
|
||||||
},
|
},
|
||||||
"manifest/path3" => Object {
|
manifestPath: "manifest/path",
|
||||||
"absolutePath": "/test/3",
|
}],
|
||||||
"id": "manifest/path3",
|
["manifest/path3", {
|
||||||
"isBundled": false,
|
absolutePath: "/test/3",
|
||||||
"isEnabled": true,
|
id: "manifest/path3",
|
||||||
"manifest": Object {
|
isBundled: false,
|
||||||
"name": "TestExtension3",
|
isEnabled: true,
|
||||||
"version": "3.0.0",
|
manifest: {
|
||||||
},
|
name: "TestExtension3",
|
||||||
"manifestPath": "manifest/path3",
|
version: "3.0.0",
|
||||||
},
|
},
|
||||||
}
|
manifestPath: "manifest/path3",
|
||||||
`);
|
}],
|
||||||
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { LensExtensionConstructor, LensExtensionManifest } from "../lens-extension";
|
import type { BundledLensExtensionManifest, BundledLensExtensionContructor } from "../lens-extension";
|
||||||
|
|
||||||
export interface BundledExtension {
|
export interface BundledExtension {
|
||||||
readonly manifest: LensExtensionManifest;
|
readonly manifest: BundledLensExtensionManifest;
|
||||||
main: () => LensExtensionConstructor | null;
|
main: () => Promise<BundledLensExtensionContructor | null>;
|
||||||
renderer: () => LensExtensionConstructor | null;
|
renderer: () => Promise<BundledLensExtensionContructor | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bundledExtensionInjectionToken = getInjectionToken<BundledExtension>({
|
export const bundledExtensionInjectionToken = getInjectionToken<BundledExtension>({
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc
|
|||||||
import { isErrnoException, toJS } from "../../common/utils";
|
import { isErrnoException, 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 { ExtensionLoader } from "../extension-loader";
|
||||||
import type { 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";
|
||||||
import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
import { requestInitialExtensionDiscovery } from "../../renderer/ipc";
|
||||||
@ -58,22 +58,31 @@ interface Dependencies {
|
|||||||
getRelativePath: GetRelativePath;
|
getRelativePath: GetRelativePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstalledExtension {
|
export interface BaseInstalledExtension {
|
||||||
id: LensExtensionId;
|
readonly id: LensExtensionId;
|
||||||
|
|
||||||
readonly manifest: LensExtensionManifest;
|
|
||||||
|
|
||||||
// Absolute path to the non-symlinked source folder,
|
// Absolute path to the non-symlinked source folder,
|
||||||
// e.g. "/Users/user/.k8slens/extensions/helloworld"
|
// e.g. "/Users/user/.k8slens/extensions/helloworld"
|
||||||
readonly absolutePath: string;
|
readonly absolutePath: string;
|
||||||
|
|
||||||
// Absolute to the symlinked package.json file
|
// Absolute to the symlinked package.json file
|
||||||
readonly manifestPath: string;
|
readonly manifestPath: string;
|
||||||
readonly isBundled: boolean; // defined in project root's package.json
|
}
|
||||||
|
|
||||||
|
export interface BundledInstalledExtension extends BaseInstalledExtension {
|
||||||
|
readonly manifest: BundledLensExtensionManifest;
|
||||||
|
readonly isBundled: true;
|
||||||
|
readonly isCompatible: true;
|
||||||
|
readonly isEnabled: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalInstalledExtension extends BaseInstalledExtension {
|
||||||
|
readonly manifest: LensExtensionManifest;
|
||||||
|
readonly isBundled: false;
|
||||||
readonly isCompatible: boolean;
|
readonly isCompatible: boolean;
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InstalledExtension = BundledInstalledExtension | ExternalInstalledExtension;
|
||||||
|
|
||||||
const logModule = "[EXTENSION-DISCOVERY]";
|
const logModule = "[EXTENSION-DISCOVERY]";
|
||||||
|
|
||||||
export const manifestFilename = "package.json";
|
export const manifestFilename = "package.json";
|
||||||
@ -88,10 +97,6 @@ interface ExtensionDiscoveryChannelMessage {
|
|||||||
*/
|
*/
|
||||||
const isDirectoryLike = (lstat: Stats) => lstat.isDirectory() || lstat.isSymbolicLink();
|
const isDirectoryLike = (lstat: Stats) => lstat.isDirectory() || lstat.isSymbolicLink();
|
||||||
|
|
||||||
interface LoadFromFolderOptions {
|
|
||||||
isBundled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExtensionDiscoveryEvents {
|
interface ExtensionDiscoveryEvents {
|
||||||
add: (ext: InstalledExtension) => void;
|
add: (ext: InstalledExtension) => void;
|
||||||
remove: (extId: LensExtensionId) => void;
|
remove: (extId: LensExtensionId) => void;
|
||||||
@ -286,7 +291,7 @@ 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.getExtension(extensionId);
|
const extension = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtensionById(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 });
|
||||||
@ -345,24 +350,26 @@ export class ExtensionDiscovery {
|
|||||||
* Returns InstalledExtension from path to package.json file.
|
* Returns InstalledExtension from path to package.json file.
|
||||||
* Also updates this.packagesJson.
|
* Also updates this.packagesJson.
|
||||||
*/
|
*/
|
||||||
protected async getByManifest(manifestPath: string, { isBundled = false } = {}): Promise<InstalledExtension | null> {
|
protected async loadExtensionFromFolder(folderPath: string): Promise<ExternalInstalledExtension | null> {
|
||||||
|
const manifestPath = this.dependencies.joinPaths(folderPath, manifestFilename);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest;
|
const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest;
|
||||||
const id = isBundled ? manifestPath : this.getInstalledManifestPath(manifest.name);
|
const id = this.getInstalledManifestPath(manifest.name);
|
||||||
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
|
const isEnabled = this.dependencies.extensionsStore.isEnabled(id);
|
||||||
const extensionDir = this.dependencies.getDirnameOfPath(manifestPath);
|
const extensionDir = this.dependencies.getDirnameOfPath(manifestPath);
|
||||||
const npmPackage = this.dependencies.joinPaths(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
const npmPackage = this.dependencies.joinPaths(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
||||||
const absolutePath = this.dependencies.isProduction && await this.dependencies.pathExists(npmPackage)
|
const absolutePath = this.dependencies.isProduction && await this.dependencies.pathExists(npmPackage)
|
||||||
? npmPackage
|
? npmPackage
|
||||||
: extensionDir;
|
: extensionDir;
|
||||||
const isCompatible = isBundled || this.dependencies.isCompatibleExtension(manifest);
|
const isCompatible = this.dependencies.isCompatibleExtension(manifest);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
absolutePath,
|
absolutePath,
|
||||||
manifestPath: id,
|
manifestPath: id,
|
||||||
manifest,
|
manifest,
|
||||||
isBundled,
|
isBundled: false,
|
||||||
isEnabled,
|
isEnabled,
|
||||||
isCompatible,
|
isCompatible,
|
||||||
};
|
};
|
||||||
@ -378,14 +385,14 @@ export class ExtensionDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ensureExtensions(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
async ensureExtensions(): Promise<Map<LensExtensionId, ExternalInstalledExtension>> {
|
||||||
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
const userExtensions = await this.loadFromFolder(this.localFolderPath);
|
||||||
|
|
||||||
return this.extensions = new Map(userExtensions.map(extension => [extension.id, extension]));
|
return this.extensions = new Map(userExtensions.map(extension => [extension.id, extension]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
async loadFromFolder(folderPath: string): Promise<ExternalInstalledExtension[]> {
|
||||||
const extensions: InstalledExtension[] = [];
|
const extensions: ExternalInstalledExtension[] = [];
|
||||||
const paths = await this.dependencies.readDirectory(folderPath);
|
const paths = await this.dependencies.readDirectory(folderPath);
|
||||||
|
|
||||||
for (const fileName of paths) {
|
for (const fileName of paths) {
|
||||||
@ -418,16 +425,6 @@ export class ExtensionDiscovery {
|
|||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads extension from absolute path, updates this.packagesJson to include it and returns the extension.
|
|
||||||
* @param folderPath Folder path to extension
|
|
||||||
*/
|
|
||||||
async loadExtensionFromFolder(folderPath: string, { isBundled = false }: LoadFromFolderOptions = {}): Promise<InstalledExtension | null> {
|
|
||||||
const manifestPath = this.dependencies.joinPaths(folderPath, manifestFilename);
|
|
||||||
|
|
||||||
return this.getByManifest(manifestPath, { isBundled });
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): ExtensionDiscoveryChannelMessage {
|
toJSON(): ExtensionDiscoveryChannelMessage {
|
||||||
return toJS({
|
return toJS({
|
||||||
isLoaded: this.isLoaded,
|
isLoaded: this.isLoaded,
|
||||||
|
|||||||
@ -4,10 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { InstalledExtension } from "../extension-discovery/extension-discovery";
|
import type { BundledInstalledExtension, ExternalInstalledExtension } from "../extension-discovery/extension-discovery";
|
||||||
import type { LensExtension, LensExtensionConstructor } from "../lens-extension";
|
import type { BundledLensExtensionContructor, LensExtension, LensExtensionConstructor } from "../lens-extension";
|
||||||
|
|
||||||
export type CreateExtensionInstance = (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension;
|
export interface CreateExtensionInstance {
|
||||||
|
(ExtensionClass: LensExtensionConstructor, extension: ExternalInstalledExtension): LensExtension;
|
||||||
|
(ExtensionClass: BundledLensExtensionContructor, extension: BundledInstalledExtension): LensExtension;
|
||||||
|
}
|
||||||
|
|
||||||
export const createExtensionInstanceInjectionToken = getInjectionToken<CreateExtensionInstance>({
|
export const createExtensionInstanceInjectionToken = getInjectionToken<CreateExtensionInstance>({
|
||||||
id: "create-extension-instance-token",
|
id: "create-extension-instance-token",
|
||||||
|
|||||||
@ -6,10 +6,10 @@
|
|||||||
import { ipcMain, ipcRenderer } from "electron";
|
import { ipcMain, ipcRenderer } from "electron";
|
||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
import type { ObservableMap } from "mobx";
|
import type { ObservableMap } from "mobx";
|
||||||
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
import { runInAction, action, computed, observable, reaction, when } from "mobx";
|
||||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
|
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
|
||||||
import { isDefined, toJS } from "../../common/utils";
|
import { isDefined, iter, toJS } from "../../common/utils";
|
||||||
import type { InstalledExtension } from "../extension-discovery/extension-discovery";
|
import type { ExternalInstalledExtension, InstalledExtension } from "../extension-discovery/extension-discovery";
|
||||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
||||||
import type { LensExtensionState } from "../extensions-store/extensions-store";
|
import type { LensExtensionState } from "../extensions-store/extensions-store";
|
||||||
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
|
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
|
||||||
@ -61,51 +61,21 @@ export class ExtensionLoader {
|
|||||||
*/
|
*/
|
||||||
protected readonly nonInstancesByName = observable.set<string>();
|
protected readonly nonInstancesByName = observable.set<string>();
|
||||||
|
|
||||||
/**
|
protected readonly instancesByName = computed(() => new Map((
|
||||||
* This is updated by the `observe` in the constructor. DO NOT write directly to it
|
iter.chain(this.dependencies.extensionInstances.entries())
|
||||||
*/
|
.map(([, instance]) => [instance.name, instance])
|
||||||
protected readonly instancesByName = observable.map<string, LensExtension>();
|
)));
|
||||||
|
|
||||||
private readonly onRemoveExtensionId = new EventEmitter<[string]>();
|
private readonly onRemoveExtensionId = new EventEmitter<[string]>();
|
||||||
|
|
||||||
@observable isLoaded = false;
|
readonly isLoaded = observable.box(false);
|
||||||
|
|
||||||
get whenLoaded() {
|
constructor(protected readonly dependencies: Dependencies) {}
|
||||||
return when(() => this.isLoaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(protected readonly dependencies: Dependencies) {
|
readonly userExtensions = computed(() => new Map((
|
||||||
makeObservable(this);
|
this.extensions.toJSON()
|
||||||
|
.filter(([, extension]) => !extension.isBundled)
|
||||||
observe(this.dependencies.extensionInstances, change => {
|
)));
|
||||||
switch (change.type) {
|
|
||||||
case "add":
|
|
||||||
if (this.instancesByName.has(change.newValue.name)) {
|
|
||||||
throw new TypeError("Extension names must be unique");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.instancesByName.set(change.newValue.name, change.newValue);
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
this.instancesByName.delete(change.oldValue.name);
|
|
||||||
break;
|
|
||||||
case "update":
|
|
||||||
throw new Error("Extension instances shouldn't be updated");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
|
|
||||||
const extensions = this.toJSON();
|
|
||||||
|
|
||||||
extensions.forEach((ext, extId) => {
|
|
||||||
if (ext.isBundled) {
|
|
||||||
extensions.delete(extId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return extensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extension instance by its manifest name
|
* Get the extension instance by its manifest name
|
||||||
@ -121,21 +91,20 @@ export class ExtensionLoader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.instancesByName.get(name);
|
return this.instancesByName.get().get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform userExtensions to a state object for storing into ExtensionsStore
|
readonly storeState = computed(() => Object.fromEntries((
|
||||||
@computed get storeState() {
|
iter.chain(this.userExtensions.get().entries())
|
||||||
return Object.fromEntries(
|
.map(([extId, extension]) => [
|
||||||
Array.from(this.userExtensions)
|
extId,
|
||||||
.map(([extId, extension]) => [extId, {
|
{
|
||||||
enabled: extension.isEnabled,
|
enabled: extension.isEnabled,
|
||||||
name: extension.manifest.name,
|
name: extension.manifest.name,
|
||||||
}]),
|
},
|
||||||
);
|
])
|
||||||
}
|
)));
|
||||||
|
|
||||||
@action
|
|
||||||
async init() {
|
async init() {
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
await this.initMain();
|
await this.initMain();
|
||||||
@ -143,7 +112,7 @@ export class ExtensionLoader {
|
|||||||
await this.initRenderer();
|
await this.initRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([this.whenLoaded]);
|
await when(() => this.isLoaded.get());
|
||||||
|
|
||||||
// broadcasting extensions between main/renderer processes
|
// broadcasting extensions between main/renderer processes
|
||||||
reaction(() => this.toJSON(), () => this.broadcastExtensions(), {
|
reaction(() => this.toJSON(), () => this.broadcastExtensions(), {
|
||||||
@ -151,8 +120,7 @@ export class ExtensionLoader {
|
|||||||
});
|
});
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
() => this.storeState,
|
() => this.storeState.get(),
|
||||||
|
|
||||||
(state) => {
|
(state) => {
|
||||||
this.dependencies.updateExtensionsState(state);
|
this.dependencies.updateExtensionsState(state);
|
||||||
},
|
},
|
||||||
@ -203,17 +171,19 @@ export class ExtensionLoader {
|
|||||||
const extension = this.extensions.get(lensExtensionId);
|
const extension = this.extensions.get(lensExtensionId);
|
||||||
|
|
||||||
assert(extension, `Must register extension ${lensExtensionId} with before enabling it`);
|
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;
|
extension.isEnabled = isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initMain() {
|
protected async initMain() {
|
||||||
this.isLoaded = true;
|
runInAction(() => {
|
||||||
|
this.isLoaded.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
await this.autoInitExtensions();
|
await this.autoInitExtensions();
|
||||||
|
|
||||||
ipcMainHandle(extensionLoaderFromMainChannel, () => {
|
ipcMainHandle(extensionLoaderFromMainChannel, () => [...this.toJSON()]);
|
||||||
return Array.from(this.toJSON());
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMainOn(extensionLoaderFromRendererChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
ipcMainOn(extensionLoaderFromRendererChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||||
this.syncExtensions(extensions);
|
this.syncExtensions(extensions);
|
||||||
@ -222,7 +192,9 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
protected async initRenderer() {
|
protected async initRenderer() {
|
||||||
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
|
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
|
||||||
this.isLoaded = true;
|
runInAction(() => {
|
||||||
|
this.isLoaded.set(true);
|
||||||
|
});
|
||||||
this.syncExtensions(extensions);
|
this.syncExtensions(extensions);
|
||||||
|
|
||||||
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
|
||||||
@ -258,10 +230,10 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async loadBundledExtensions() {
|
protected async loadBundledExtensions() {
|
||||||
return this.dependencies.bundledExtensions
|
const bundledExtensions = await Promise.all((this.dependencies.bundledExtensions
|
||||||
.map(extension => {
|
.map(async extension => {
|
||||||
try {
|
try {
|
||||||
const LensExtensionClass = extension[this.dependencies.extensionEntryPointName]();
|
const LensExtensionClass = await extension[this.dependencies.extensionEntryPointName]();
|
||||||
|
|
||||||
if (!LensExtensionClass) {
|
if (!LensExtensionClass) {
|
||||||
return null;
|
return null;
|
||||||
@ -294,7 +266,9 @@ export class ExtensionLoader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(isDefined);
|
));
|
||||||
|
|
||||||
|
return bundledExtensions.filter(isDefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadExtensions(extensions: ExtensionBeingActivated[]): Promise<ExtensionLoading[]> {
|
protected async loadExtensions(extensions: ExtensionBeingActivated[]): Promise<ExtensionLoading[]> {
|
||||||
@ -335,6 +309,7 @@ export class ExtensionLoader {
|
|||||||
// 4. Return ExtensionLoading[]
|
// 4. Return ExtensionLoading[]
|
||||||
|
|
||||||
return [...installedExtensions.entries()]
|
return [...installedExtensions.entries()]
|
||||||
|
.filter((entry): entry is [string, ExternalInstalledExtension] => !entry[1].isBundled)
|
||||||
.map(([extId, extension]) => {
|
.map(([extId, extension]) => {
|
||||||
const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
||||||
|
|
||||||
@ -394,7 +369,7 @@ export class ExtensionLoader {
|
|||||||
return loadedExtensions;
|
return loadedExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
protected requireExtension(extension: ExternalInstalledExtension): LensExtensionConstructor | null {
|
||||||
const extRelativePath = extension.manifest[this.dependencies.extensionEntryPointName];
|
const extRelativePath = extension.manifest[this.dependencies.extensionEntryPointName];
|
||||||
|
|
||||||
if (!extRelativePath) {
|
if (!extRelativePath) {
|
||||||
@ -414,7 +389,7 @@ export class ExtensionLoader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtension(extId: LensExtensionId) {
|
getExtensionById(extId: LensExtensionId) {
|
||||||
return this.extensions.get(extId);
|
return this.extensions.get(extId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,11 +18,6 @@ export interface LensExtensionState {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IsEnabledExtensionDescriptor {
|
|
||||||
id: string;
|
|
||||||
isBundled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
||||||
constructor(deps: BaseStoreDependencies) {
|
constructor(deps: BaseStoreDependencies) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
@ -39,12 +34,12 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
.map(({ name }) => name);
|
.map(({ name }) => name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected state = observable.map<LensExtensionId, LensExtensionState>();
|
protected readonly state = observable.map<LensExtensionId, LensExtensionState>();
|
||||||
|
|
||||||
isEnabled({ id, isBundled }: IsEnabledExtensionDescriptor): 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.
|
||||||
// If user installs the extension from the UI, the Extensions component will specifically enable it.
|
// If user installs the extension from the UI, the Extensions component will specifically enable it.
|
||||||
return isBundled || Boolean(this.state.get(id)?.enabled);
|
return this.state.get(extId)?.enabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeState = action((extensionsState: Record<LensExtensionId, LensExtensionState> | [LensExtensionId, LensExtensionState][]) => {
|
mergeState = action((extensionsState: Record<LensExtensionId, LensExtensionState> | [LensExtensionId, LensExtensionState][]) => {
|
||||||
|
|||||||
@ -3,19 +3,24 @@
|
|||||||
* 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 type { InstalledExtension } from "./extension-discovery/extension-discovery";
|
import type { BundledInstalledExtension, ExternalInstalledExtension, InstalledExtension } from "./extension-discovery/extension-discovery";
|
||||||
import { action, computed, makeObservable, observable } from "mobx";
|
import { action, computed, makeObservable, observable } from "mobx";
|
||||||
import type { PackageJson } from "type-fest";
|
|
||||||
import { disposer } from "../common/utils";
|
import { disposer } from "../common/utils";
|
||||||
import type { LensExtensionDependencies } from "./lens-extension-set-dependencies";
|
import type { LensExtensionDependencies } from "./lens-extension-set-dependencies";
|
||||||
import type { ProtocolHandlerRegistration } from "../common/protocol-handler/registration";
|
import type { ProtocolHandlerRegistration } from "../common/protocol-handler/registration";
|
||||||
|
import type { PackageJson } from "type-fest";
|
||||||
|
|
||||||
export type LensExtensionId = string; // path to manifest (package.json)
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
export type LensExtensionConstructor = new (ext: ExternalInstalledExtension) => LensExtension;
|
||||||
|
export type BundledLensExtensionContructor = new (ext: BundledInstalledExtension) => LensExtension;
|
||||||
|
|
||||||
export interface LensExtensionManifest extends PackageJson {
|
export interface BundledLensExtensionManifest extends PackageJson {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
publishConfig?: Partial<Record<string, string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LensExtensionManifest extends BundledLensExtensionManifest {
|
||||||
main?: string; // path to %ext/dist/main.js
|
main?: string; // path to %ext/dist/main.js
|
||||||
renderer?: string; // path to %ext/dist/renderer.js
|
renderer?: string; // path to %ext/dist/renderer.js
|
||||||
/**
|
/**
|
||||||
@ -24,8 +29,7 @@ export interface LensExtensionManifest extends PackageJson {
|
|||||||
*/
|
*/
|
||||||
engines: {
|
engines: {
|
||||||
lens: string; // "semver"-package format
|
lens: string; // "semver"-package format
|
||||||
npm?: string;
|
[x: string]: string | undefined;
|
||||||
node?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Specify extension name used for persisting data.
|
// Specify extension name used for persisting data.
|
||||||
@ -65,14 +69,12 @@ export class LensExtension<
|
|||||||
[Disposers] = disposer();
|
[Disposers] = disposer();
|
||||||
|
|
||||||
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||||
makeObservable(this);
|
|
||||||
|
|
||||||
// id is the name of the manifest
|
// id is the name of the manifest
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.manifest = manifest as LensExtensionManifest;
|
||||||
this.manifest = manifest;
|
|
||||||
this.manifestPath = manifestPath;
|
this.manifestPath = manifestPath;
|
||||||
this.isBundled = !!isBundled;
|
this.isBundled = !!isBundled;
|
||||||
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const createExtensionInstanceInjectable = getInjectable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (ExtensionClass, extension) => {
|
return (ExtensionClass, extension) => {
|
||||||
const instance = new ExtensionClass(extension) as LensMainExtension;
|
const instance = new ExtensionClass(extension as any) as LensMainExtension;
|
||||||
|
|
||||||
(instance as Writable<LensMainExtension>)[lensExtensionDependencies] = deps;
|
(instance as Writable<LensMainExtension>)[lensExtensionDependencies] = deps;
|
||||||
|
|
||||||
|
|||||||
@ -6,23 +6,18 @@
|
|||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
||||||
import { delay, noop } from "../../../common/utils";
|
import { noop } from "../../../common/utils";
|
||||||
import type { ExtensionsStore, IsEnabledExtensionDescriptor } from "../../../extensions/extensions-store/extensions-store";
|
|
||||||
import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main";
|
import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main";
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
|
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
|
||||||
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import { LensExtension } from "../../../extensions/lens-extension";
|
import { LensExtension } from "../../../extensions/lens-extension";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
import type { ObservableMap } from "mobx";
|
import type { ObservableMap } from "mobx";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
|
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
|
||||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
|
||||||
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
|
|
||||||
import pathExistsInjectable from "../../../common/fs/path-exists.injectable";
|
|
||||||
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
|
|
||||||
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
|
|
||||||
|
|
||||||
function throwIfDefined(val: any): void {
|
function throwIfDefined(val: any): void {
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
@ -39,20 +34,13 @@ describe("protocol router tests", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
|
|
||||||
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
|
|
||||||
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
|
|
||||||
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
|
|
||||||
|
|
||||||
enabledExtensions = new Set();
|
enabledExtensions = new Set();
|
||||||
|
|
||||||
di.override(extensionsStoreInjectable, () => ({
|
di.override(extensionsStoreInjectable, () => ({
|
||||||
isEnabled: ({ id, isBundled }: IsEnabledExtensionDescriptor) => isBundled || enabledExtensions.has(id),
|
isEnabled: (id) => enabledExtensions.has(id),
|
||||||
} as unknown as ExtensionsStore));
|
}));
|
||||||
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
|
||||||
|
|
||||||
broadcastMessageMock = jest.fn();
|
broadcastMessageMock = jest.fn();
|
||||||
di.override(broadcastMessageInjectable, () => broadcastMessageMock);
|
di.override(broadcastMessageInjectable, () => broadcastMessageMock);
|
||||||
@ -60,7 +48,9 @@ describe("protocol router tests", () => {
|
|||||||
extensionInstances = di.inject(extensionInstancesInjectable);
|
extensionInstances = di.inject(extensionInstancesInjectable);
|
||||||
lpr = di.inject(lensProtocolRouterMainInjectable);
|
lpr = di.inject(lensProtocolRouterMainInjectable);
|
||||||
|
|
||||||
lpr.rendererLoaded = true;
|
runInAction(() => {
|
||||||
|
lpr.rendererLoaded.set(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should broadcast invalid protocol on non-lens URLs", async () => {
|
it("should broadcast invalid protocol on non-lens URLs", async () => {
|
||||||
@ -73,7 +63,19 @@ describe("protocol router tests", () => {
|
|||||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInvalid, "invalid host", "lens://foobar");
|
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerInvalid, "invalid host", "lens://foobar");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not throw when has valid host", async () => {
|
it("should broadcast internal route when called with valid host", async () => {
|
||||||
|
lpr.addInternalHandler("/", noop);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await lpr.route("lens://app")).toBeUndefined();
|
||||||
|
} catch (error) {
|
||||||
|
expect(throwIfDefined(error)).not.toThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should broadcast external route when called with valid host", async () => {
|
||||||
const extId = uuid.v4();
|
const extId = uuid.v4();
|
||||||
const ext = new LensExtension({
|
const ext = new LensExtension({
|
||||||
id: extId,
|
id: extId,
|
||||||
@ -97,22 +99,12 @@ describe("protocol router tests", () => {
|
|||||||
extensionInstances.set(extId, ext);
|
extensionInstances.set(extId, ext);
|
||||||
enabledExtensions.add(extId);
|
enabledExtensions.add(extId);
|
||||||
|
|
||||||
lpr.addInternalHandler("/", noop);
|
|
||||||
|
|
||||||
try {
|
|
||||||
expect(await lpr.route("lens://app")).toBeUndefined();
|
|
||||||
} catch (error) {
|
|
||||||
expect(throwIfDefined(error)).not.toThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
|
expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(throwIfDefined(error)).not.toThrow();
|
expect(throwIfDefined(error)).not.toThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
await delay(50);
|
|
||||||
expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched");
|
|
||||||
expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
|
expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,7 +175,6 @@ describe("protocol router tests", () => {
|
|||||||
expect(throwIfDefined(error)).not.toThrow();
|
expect(throwIfDefined(error)).not.toThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
await delay(50);
|
|
||||||
expect(called).toBe("foob");
|
expect(called).toBe("foob");
|
||||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched");
|
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched");
|
||||||
});
|
});
|
||||||
@ -252,7 +243,6 @@ describe("protocol router tests", () => {
|
|||||||
expect(throwIfDefined(error)).not.toThrow();
|
expect(throwIfDefined(error)).not.toThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
await delay(50);
|
|
||||||
|
|
||||||
expect(called).toBe(1);
|
expect(called).toBe(1);
|
||||||
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched");
|
expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched");
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import * as proto from "../../../common/protocol-handler";
|
import * as proto from "../../../common/protocol-handler";
|
||||||
import URLParse from "url-parse";
|
import URLParse from "url-parse";
|
||||||
import type { LensExtension } from "../../../extensions/lens-extension";
|
import type { LensExtension } from "../../../extensions/lens-extension";
|
||||||
import { observable, when, makeObservable } from "mobx";
|
import { observable, when } from "mobx";
|
||||||
import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler";
|
import type { LensProtocolRouterDependencies, RouteAttempt } from "../../../common/protocol-handler";
|
||||||
import { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
import { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
|
||||||
import { disposer, noop } from "../../../common/utils";
|
import { disposer, noop } from "../../../common/utils";
|
||||||
@ -39,17 +39,15 @@ export interface LensProtocolRouterMainDependencies extends LensProtocolRouterDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
||||||
private missingExtensionHandlers: FallbackHandler[] = [];
|
private readonly missingExtensionHandlers: FallbackHandler[] = [];
|
||||||
|
|
||||||
// TODO: This is used to solve out-of-place temporal dependency. Remove, and solve otherwise.
|
// TODO: This is used to solve out-of-place temporal dependency. Remove, and solve otherwise.
|
||||||
@observable rendererLoaded = false;
|
readonly rendererLoaded = observable.box(false);
|
||||||
|
|
||||||
protected disposers = disposer();
|
protected readonly disposers = disposer();
|
||||||
|
|
||||||
constructor(protected readonly dependencies: LensProtocolRouterMainDependencies) {
|
constructor(protected readonly dependencies: LensProtocolRouterMainDependencies) {
|
||||||
super(dependencies);
|
super(dependencies);
|
||||||
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public cleanup() {
|
public cleanup() {
|
||||||
@ -118,8 +116,13 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
|||||||
protected _routeToInternal(url: URLParse<Record<string, string | undefined>>): RouteAttempt {
|
protected _routeToInternal(url: URLParse<Record<string, string | undefined>>): RouteAttempt {
|
||||||
const rawUrl = url.toString(); // for sending to renderer
|
const rawUrl = url.toString(); // for sending to renderer
|
||||||
const attempt = super._routeToInternal(url);
|
const attempt = super._routeToInternal(url);
|
||||||
|
const broadcastToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt);
|
||||||
|
|
||||||
this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt)));
|
if (this.rendererLoaded.get()) {
|
||||||
|
broadcastToRenderer();
|
||||||
|
} else {
|
||||||
|
this.disposers.push(when(() => this.rendererLoaded.get(), broadcastToRenderer));
|
||||||
|
}
|
||||||
|
|
||||||
return attempt;
|
return attempt;
|
||||||
}
|
}
|
||||||
@ -135,8 +138,13 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
|
|||||||
* argument.
|
* argument.
|
||||||
*/
|
*/
|
||||||
const attempt = await super._routeToExtension(new URLParse(url.toString(), true));
|
const attempt = await super._routeToExtension(new URLParse(url.toString(), true));
|
||||||
|
const broadcastToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt);
|
||||||
|
|
||||||
this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt)));
|
if (this.rendererLoaded.get()) {
|
||||||
|
broadcastToRenderer();
|
||||||
|
} else {
|
||||||
|
this.disposers.push(when(() => this.rendererLoaded.get(), broadcastToRenderer));
|
||||||
|
}
|
||||||
|
|
||||||
return attempt;
|
return attempt;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const flagRendererAsLoadedInjectable = getInjectable({
|
|||||||
run: () => {
|
run: () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
||||||
lensProtocolRouterMain.rendererLoaded = true;
|
lensProtocolRouterMain.rendererLoaded.set(true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const flagRendererAsNotLoadedInjectable = getInjectable({
|
|||||||
run: () => {
|
run: () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
||||||
lensProtocolRouterMain.rendererLoaded = false;
|
lensProtocolRouterMain.rendererLoaded.set(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -86,7 +86,7 @@ const attemptInstall = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extensionFolder = getExtensionDestFolder(name);
|
const extensionFolder = getExtensionDestFolder(name);
|
||||||
const installedExtension = extensionLoader.getExtension(validatedRequest.id);
|
const installedExtension = extensionLoader.getExtensionById(validatedRequest.id);
|
||||||
|
|
||||||
if (installedExtension) {
|
if (installedExtension) {
|
||||||
const { version: oldVersion } = installedExtension.manifest;
|
const { version: oldVersion } = installedExtension.manifest;
|
||||||
|
|||||||
@ -73,7 +73,7 @@ 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.has(id));
|
await when(() => extensionLoader.userExtensions.get().has(id));
|
||||||
|
|
||||||
// Enable installed extensions by default.
|
// Enable installed extensions by default.
|
||||||
extensionLoader.setIsEnabled(id, true);
|
extensionLoader.setIsEnabled(id, true);
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
export type DisableExtension = (extId: LensExtensionId) => void;
|
||||||
|
|
||||||
|
const disableExtensionInjectable = getInjectable({
|
||||||
|
id: "disable-extension",
|
||||||
|
|
||||||
|
instantiate: (di): DisableExtension => {
|
||||||
|
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||||
|
|
||||||
|
return (extId) => {
|
||||||
|
const ext = extensionLoader.getExtensionById(extId);
|
||||||
|
|
||||||
|
if (ext && !ext.isBundled) {
|
||||||
|
ext.isEnabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default disableExtensionInjectable;
|
||||||
@ -1,18 +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 { disableExtension } from "./disable-extension";
|
|
||||||
|
|
||||||
const disableExtensionInjectable = getInjectable({
|
|
||||||
id: "disable-extension",
|
|
||||||
|
|
||||||
instantiate: (di) =>
|
|
||||||
disableExtension({
|
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default disableExtensionInjectable;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
extensionLoader: ExtensionLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const disableExtension =
|
|
||||||
({ extensionLoader }: Dependencies) =>
|
|
||||||
(id: LensExtensionId) => {
|
|
||||||
const extension = extensionLoader.getExtension(id);
|
|
||||||
|
|
||||||
if (extension) {
|
|
||||||
extension.isEnabled = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
|
|
||||||
|
export type EnableExtension = (extId: LensExtensionId) => void;
|
||||||
|
|
||||||
|
const enableExtensionInjectable = getInjectable({
|
||||||
|
id: "enable-extension",
|
||||||
|
|
||||||
|
instantiate: (di): EnableExtension => {
|
||||||
|
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||||
|
|
||||||
|
return (extId) => {
|
||||||
|
const ext = extensionLoader.getExtensionById(extId);
|
||||||
|
|
||||||
|
if (ext && !ext.isBundled) {
|
||||||
|
ext.isEnabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default enableExtensionInjectable;
|
||||||
@ -1,18 +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 { enableExtension } from "./enable-extension";
|
|
||||||
|
|
||||||
const enableExtensionInjectable = getInjectable({
|
|
||||||
id: "enable-extension",
|
|
||||||
|
|
||||||
instantiate: (di) =>
|
|
||||||
enableExtension({
|
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default enableExtensionInjectable;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
extensionLoader: ExtensionLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enableExtension =
|
|
||||||
({ extensionLoader }: Dependencies) =>
|
|
||||||
(id: LensExtensionId) => {
|
|
||||||
const extension = extensionLoader.getExtension(id);
|
|
||||||
|
|
||||||
if (extension) {
|
|
||||||
extension.isEnabled = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -24,8 +24,8 @@ import { docsUrl } from "../../../common/vars";
|
|||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
|
||||||
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
|
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
|
||||||
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
|
import enableExtensionInjectable from "./enable-extension.injectable";
|
||||||
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
import disableExtensionInjectable from "./disable-extension.injectable";
|
||||||
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
|
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
|
||||||
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
|
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
|
||||||
import type { InstallExtensionFromInput } from "./install-extension-from-input.injectable";
|
import type { InstallExtensionFromInput } from "./install-extension-from-input.injectable";
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { Icon } from "../icon";
|
|||||||
import { List } from "../list/list";
|
import { List } from "../list/list";
|
||||||
import { MenuActions, MenuItem } from "../menu";
|
import { MenuActions, MenuItem } from "../menu";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames, toJS } from "../../utils";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Row } from "react-table";
|
import type { Row } from "react-table";
|
||||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||||
@ -45,7 +45,14 @@ function getStatus(extension: InstalledExtension) {
|
|||||||
return extension.isEnabled ? "Enabled" : "Disabled";
|
return extension.isEnabled ? "Enabled" : "Disabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extensionInstallationStateStore, extensions, uninstall, enable, disable }: Dependencies & InstalledExtensionsProps) => {
|
const NonInjectedInstalledExtensions = observer(({
|
||||||
|
extensionDiscovery,
|
||||||
|
extensionInstallationStateStore,
|
||||||
|
extensions,
|
||||||
|
uninstall,
|
||||||
|
enable,
|
||||||
|
disable,
|
||||||
|
}: Dependencies & InstalledExtensionsProps) => {
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -138,7 +145,7 @@ const NonInjectedInstalledExtensions = observer(({ extensionDiscovery, extension
|
|||||||
</MenuActions>
|
</MenuActions>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}), [extensions, extensionInstallationStateStore.anyUninstalling],
|
}), [toJS(extensions), extensionInstallationStateStore.anyUninstalling],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!extensionDiscovery.isLoaded) {
|
if (!extensionDiscovery.isLoaded) {
|
||||||
|
|||||||
@ -27,7 +27,7 @@ const uninstallExtensionInjectable = getInjectable({
|
|||||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||||
|
|
||||||
return async (extensionId: LensExtensionId): Promise<boolean> => {
|
return async (extensionId: LensExtensionId): Promise<boolean> => {
|
||||||
const ext = extensionLoader.getExtension(extensionId);
|
const ext = extensionLoader.getExtensionById(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 +45,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.has(extensionId));
|
await when(() => !extensionLoader.userExtensions.get().has(extensionId));
|
||||||
|
|
||||||
showSuccessNotification(
|
showSuccessNotification(
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const userExtensionsInjectable = getInjectable({
|
|||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||||
|
|
||||||
return computed(() => [...extensionLoader.userExtensions.values()]);
|
return computed(() => [...extensionLoader.userExtensions.get().values()]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const createExtensionInstanceInjectable = getInjectable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (ExtensionClass, extension) => {
|
return (ExtensionClass, extension) => {
|
||||||
const instance = new ExtensionClass(extension) as LensRendererExtension;
|
const instance = new ExtensionClass(extension as any) as LensRendererExtension;
|
||||||
|
|
||||||
(instance as Writable<LensRendererExtension>)[lensExtensionDependencies] = deps;
|
(instance as Writable<LensRendererExtension>)[lensExtensionDependencies] = deps;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user