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:
parent
6db52e70dd
commit
7a44a2ebc0
@ -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",
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user