1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Install missing extension if no extension route found and extension name is not installed

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
Panu Horsmalahti 2020-12-09 17:14:01 +02:00 committed by Sebastian Malton
parent 6db52e70dd
commit 7a44a2ebc0
4 changed files with 60 additions and 18 deletions

View File

@ -33,7 +33,6 @@ module.exports = {
"indent": ["error", 2, {
"SwitchCase": 1,
}],
"no-invalid-this": "error",
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
@ -96,7 +95,6 @@ module.exports = {
"indent": ["error", 2, {
"SwitchCase": 1,
}],
"no-invalid-this": "error",
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
@ -162,7 +160,6 @@ module.exports = {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"no-invalid-this": "error",
"semi": "off",
"@typescript-eslint/semi": ["error"],
"object-shorthand": "error",

View File

@ -84,6 +84,18 @@ export class ExtensionLoader {
this.extensions.replace(extensions);
}
isInstalled(name: string) {
for (const extensionEntry of this.extensions) {
const [, extension ] = extensionEntry;
if (extension.manifest.name === name) {
return true;
}
}
return false;
}
addExtension(extension: InstalledExtension) {
this.extensions.set(extension.id, extension);
}
@ -103,7 +115,6 @@ export class ExtensionLoader {
} catch (error) {
logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error });
}
}
removeExtension(lensExtensionId: LensExtensionId) {

View File

@ -46,20 +46,23 @@ export type RouteHandler = (params: RouteParams) => void;
export type ExtensionId = string;
const EXT_ID_MATCH = "LENS_INTERNAL_EXTENSION_ID_MATCH";
const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH";
const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH";
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
export const lensProtocolChannel = "protocol-handler";
interface ExtensionIdMatch {
[EXT_ID_MATCH]: string;
interface ExtensionUrlMatch {
[EXTENSION_PUBLISHER_MATCH]: string;
[EXTENSION_NAME_MATCH]: string;
}
export class LensProtocolRouter extends Singleton {
private extentionRoutes = new Map<ExtensionId, Map<string, RouteHandler>>();
private internalRoutes = new Map<string, RouteHandler>();
private missingExtensionHandler?: (name: string) => Promise<void>;
private static ExtensionIDSchema = `/:${EXT_ID_MATCH}`;
private static ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}/:${EXTENSION_NAME_MATCH}`;
public init() {
subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => {
@ -87,26 +90,39 @@ export class LensProtocolRouter extends Singleton {
case "internal":
return this._route(this.internalRoutes, url);
case "extension":
return this._routeToExtension(url);
// Possible rejected promise is ignored
this._routeToExtension(url);
default:
throw new RoutingError(RoutingErrorType.INVALID_HOST, url);
}
}
private _routeToExtension(url: Url): void {
const match = matchPath<ExtensionIdMatch>(url.pathname, LensProtocolRouter.ExtensionIDSchema);
private async _routeToExtension(url: Url) {
const match = matchPath<ExtensionUrlMatch>(url.pathname, LensProtocolRouter.ExtensionUrlSchema);
if (!match) {
throw new RoutingError(RoutingErrorType.NO_EXTENSION_ID, url);
}
const { [EXT_ID_MATCH]: id } = match.params;
const routes = this.extentionRoutes.get(id);
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
const name = `${publisher}/${partialName}`;
logger.info(`[PROTOCOL ROUTER] Extension ${name} matched`);
const routes = this.extentionRoutes.get(name);
if (!routes) {
if (this.missingExtensionHandler) {
await this.missingExtensionHandler(name);
// TODO: After installation we can continue to route to the extension..
// but this is difficult, since the promise resolves before extension installation is complete.
return;
} else {
throw new RoutingError(RoutingErrorType.MISSING_EXTENSION, url);
}
}
this._route(routes, url, true);
}
@ -117,7 +133,7 @@ export class LensProtocolRouter extends Singleton {
if (matchExtension) {
const joinChar = schema.startsWith("/") ? "" : "/";
schema = `${LensProtocolRouter.ExtensionIDSchema}${joinChar}${schema}`;
schema = `${LensProtocolRouter.ExtensionUrlSchema}${joinChar}${schema}`;
}
return [matchPath(url.pathname, { path: schema }), handler];
@ -132,7 +148,7 @@ export class LensProtocolRouter extends Singleton {
const [match, handler] = route;
delete match.params[EXT_ID_MATCH];
delete match.params[EXTENSION_NAME_MATCH];
handler({
pathname: match.params,
search: url.query,
@ -151,10 +167,14 @@ export class LensProtocolRouter extends Singleton {
this.extentionRoutes.set(id, new Map());
}
if (urlSchema.includes(`:${EXT_ID_MATCH}`)) {
if (urlSchema.includes(`:${EXTENSION_NAME_MATCH}`)) {
throw new TypeError("Invalid url path schema");
}
this.extentionRoutes.get(id).set(urlSchema, handler);
}
public onMissingExtension(handler: (name: string) => Promise<void>) {
this.missingExtensionHandler = handler;
}
}

View File

@ -19,6 +19,8 @@ import { LensApp } from "./lens-app";
import { themeStore } from "./theme.store";
import protocolEndpoints from "./api/protocol-endpoints";
import { LensProtocolRouter } from "../main/protocol-handler";
import logger from "../main/logger";
import { installFromNpm } from "./components/+extensions";
type AppComponent = React.ComponentType & {
init?(): Promise<void>;
@ -38,7 +40,19 @@ export async function bootstrap(App: AppComponent) {
extensionLoader.init();
extensionDiscovery.init();
LensProtocolRouter.getInstance<LensProtocolRouter>().init();
const lensProtocolRouter = LensProtocolRouter.getInstance<LensProtocolRouter>();
lensProtocolRouter.init();
lensProtocolRouter.onMissingExtension(async name => {
if (!extensionLoader.isInstalled(name)) {
logger.info(`[PROTOCOL ROUTER] Extension ${name} not installed, installing..`);
// TODO: This actually resolves before the extension installation is complete
return installFromNpm(name);
} else {
logger.info(`[PROTOCOL ROUTER] Extension already installed, but route is missing.`);
}
});
protocolEndpoints.registerHandlers();
// preload common stores