Introduce clearer boundry between extensions (#7164)

- 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:
Sebastian Malton authored and GitHub committed 2023-04-05 10:21:38 -04:00
1 parent 351f9d492f
commit 058494bc73
29 files changed
+440 -550

No files matched your search

@@ -24,7 +24,7 @@ const createExtensionInstanceInjectable = getInjectable({
};
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;
@@ -10,16 +10,12 @@ import { noop } from "@k8slens/utilities";
import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import { LensExtension } from "../../../extensions/lens-extension";
import type { ObservableMap } from "mobx";
import { runInAction } from "mobx";
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
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";
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import type { LensExtensionState } from "../../../features/extensions/enabled/common/state.injectable";
import enabledExtensionsStateInjectable from "../../../features/extensions/enabled/common/state.injectable";
@@ -39,16 +35,8 @@ describe("protocol router tests", () => {
beforeEach(async () => {
const di = getDiForUnitTesting();
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 = di.inject(enabledExtensionsStateInjectable);
di.permitSideEffects(getConfigurationFileModelInjectable);
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
broadcastMessageMock = jest.fn();
di.override(broadcastMessageInjectable, () => broadcastMessageMock);
@@ -56,7 +44,9 @@ describe("protocol router tests", () => {
extensionInstances = di.inject(extensionInstancesInjectable);
lpr = di.inject(lensProtocolRouterMainInjectable);
lpr.rendererLoaded = true;
runInAction(() => {
lpr.rendererLoaded.set(true);
});
});
it("should broadcast invalid protocol on non-lens URLs", async () => {
@@ -69,7 +59,19 @@ describe("protocol router tests", () => {
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 ext = new LensExtension({
id: extId,
@@ -6,7 +6,7 @@
import * as proto from "../../../common/protocol-handler";
import URLParse from "url-parse";
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 { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
import { disposer, noop } from "@k8slens/utilities";
@@ -39,17 +39,15 @@ export interface LensProtocolRouterMainDependencies extends LensProtocolRouterDe
}
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.
@observable rendererLoaded = false;
readonly rendererLoaded = observable.box(false);
protected disposers = disposer();
protected readonly disposers = disposer();
constructor(protected readonly dependencies: LensProtocolRouterMainDependencies) {
super(dependencies);
makeObservable(this);
}
public cleanup() {
@@ -118,13 +116,12 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
protected _routeToInternal(url: URLParse<Record<string, string | undefined>>): RouteAttempt {
const rawUrl = url.toString(); // for sending to renderer
const attempt = super._routeToInternal(url);
const broadcastToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt);
const sendRoutingToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt);
if (this.rendererLoaded) {
sendRoutingToRenderer();
if (this.rendererLoaded.get()) {
broadcastToRenderer();
} else {
this.disposers.push(when(() => this.rendererLoaded, sendRoutingToRenderer));
this.disposers.push(when(() => this.rendererLoaded.get(), broadcastToRenderer));
}
return attempt;
@@ -141,13 +138,12 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
* argument.
*/
const attempt = await super._routeToExtension(new URLParse(url.toString(), true));
const broadcastToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt);
const sendRoutingToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt);
if (this.rendererLoaded) {
sendRoutingToRenderer();
if (this.rendererLoaded.get()) {
broadcastToRenderer();
} else {
this.disposers.push(when(() => this.rendererLoaded, sendRoutingToRenderer));
this.disposers.push(when(() => this.rendererLoaded.get(), broadcastToRenderer));
}
return attempt;
@@ -16,7 +16,7 @@ const flagRendererAsLoadedInjectable = getInjectable({
runInAction(() => {
// Todo: remove this kludge which enables out-of-place temporal dependency.
lensProtocolRouterMain.rendererLoaded = true;
lensProtocolRouterMain.rendererLoaded.set(true);
});
},
}),
@@ -16,7 +16,7 @@ const flagRendererAsNotLoadedInjectable = getInjectable({
runInAction(() => {
// Todo: remove this kludge which enables out-of-place temporal dependency.
lensProtocolRouterMain.rendererLoaded = false;
lensProtocolRouterMain.rendererLoaded.set(false);
});
return undefined;