1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/extensions/extension-loader.ts
Lauri Nevala 85c4a10114
Implement KubeObjectStatusText extension API (#1188)
Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
2020-11-11 08:57:15 +02:00

156 lines
5.4 KiB
TypeScript

import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension"
import type { LensMainExtension } from "./lens-main-extension"
import type { LensRendererExtension } from "./lens-renderer-extension"
import type { InstalledExtension } from "./extension-manager";
import path from "path"
import { broadcastIpc } from "../common/ipc"
import { action, computed, observable, reaction, toJS, when } from "mobx"
import logger from "../main/logger"
import { app, ipcRenderer, remote } from "electron"
import * as registries from "./registries";
import { extensionsStore } from "./extensions-store";
// lazy load so that we get correct userData
export function extensionPackagesRoot() {
return path.join((app || remote.app).getPath("userData"))
}
export class ExtensionLoader {
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
protected instances = observable.map<LensExtensionId, LensExtension>();
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
constructor() {
if (ipcRenderer) {
ipcRenderer.on("extensions:loaded", (event, extensions: [LensExtensionId, InstalledExtension][]) => {
this.isLoaded = true;
extensions.forEach(([extId, ext]) => {
if (!this.extensions.has(extId)) {
this.extensions.set(extId, ext)
}
})
});
}
extensionsStore.manageState(this);
}
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
const extensions = this.extensions.toJS();
extensions.forEach((ext, extId) => {
if (ext.isBundled) {
extensions.delete(extId);
}
})
return extensions;
}
@action
async init(extensions: Map<LensExtensionId, InstalledExtension>) {
this.extensions.replace(extensions);
this.isLoaded = true;
this.loadOnMain();
this.broadcastExtensions();
}
loadOnMain() {
logger.info('[EXTENSIONS-LOADER]: load on main')
this.autoInitExtensions((extension: LensMainExtension) => [
registries.menuRegistry.add(...extension.appMenus)
]);
}
loadOnClusterManagerRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
this.autoInitExtensions((extension: LensRendererExtension) => [
registries.globalPageRegistry.add(...extension.globalPages),
registries.appPreferenceRegistry.add(...extension.appPreferences),
registries.clusterFeatureRegistry.add(...extension.clusterFeatures),
registries.statusBarRegistry.add(...extension.statusBarItems),
]);
}
loadOnClusterRenderer() {
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
this.autoInitExtensions((extension: LensRendererExtension) => [
registries.clusterPageRegistry.add(...extension.clusterPages),
registries.kubeObjectMenuRegistry.add(...extension.kubeObjectMenuItems),
registries.kubeObjectDetailRegistry.add(...extension.kubeObjectDetailItems),
registries.kubeObjectStatusRegistry.add(...extension.kubeObjectStatusTexts)
])
}
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
return reaction(() => this.toJSON(), installedExtensions => {
for (const [extId, ext] of installedExtensions) {
let instance = this.instances.get(extId);
if (ext.isEnabled && !instance) {
try {
const LensExtensionClass: LensExtensionConstructor = this.requireExtension(ext)
if (!LensExtensionClass) continue;
instance = new LensExtensionClass(ext);
instance.whenEnabled(() => register(instance));
instance.enable();
this.instances.set(extId, instance);
} catch (err) {
logger.error(`[EXTENSION-LOADER]: activation extension error`, { ext, err })
}
} else if (!ext.isEnabled && instance) {
try {
instance.disable();
this.instances.delete(extId);
} catch (err) {
logger.error(`[EXTENSION-LOADER]: deactivation extension error`, { ext, err })
}
}
}
}, {
fireImmediately: true,
})
}
protected requireExtension(extension: InstalledExtension) {
let extEntrypoint = ""
try {
if (ipcRenderer && extension.manifest.renderer) {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.renderer))
} else if (!ipcRenderer && extension.manifest.main) {
extEntrypoint = path.resolve(path.join(path.dirname(extension.manifestPath), extension.manifest.main))
}
if (extEntrypoint !== "") {
return __non_webpack_require__(extEntrypoint).default;
}
} catch (err) {
console.error(`[EXTENSION-LOADER]: can't load extension main at ${extEntrypoint}: ${err}`, { extension });
console.trace(err)
}
}
getExtension(extId: LensExtensionId): InstalledExtension {
return this.extensions.get(extId);
}
toJSON(): Map<LensExtensionId, InstalledExtension> {
return toJS(this.extensions, {
exportMapsAsObjects: false,
recurseEverything: true,
})
}
async broadcastExtensions(frameId?: number) {
await when(() => this.isLoaded);
broadcastIpc({
channel: "extensions:loaded",
frameId: frameId,
frameOnly: !!frameId,
args: [
Array.from(this.toJSON()),
],
})
}
}
export const extensionLoader = new ExtensionLoader();