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, {
|
"indent": ["error", 2, {
|
||||||
"SwitchCase": 1,
|
"SwitchCase": 1,
|
||||||
}],
|
}],
|
||||||
"no-invalid-this": "error",
|
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"unused-imports/no-unused-imports": "error",
|
"unused-imports/no-unused-imports": "error",
|
||||||
"unused-imports/no-unused-vars": [
|
"unused-imports/no-unused-vars": [
|
||||||
@ -96,7 +95,6 @@ module.exports = {
|
|||||||
"indent": ["error", 2, {
|
"indent": ["error", 2, {
|
||||||
"SwitchCase": 1,
|
"SwitchCase": 1,
|
||||||
}],
|
}],
|
||||||
"no-invalid-this": "error",
|
|
||||||
"quotes": ["error", "double", {
|
"quotes": ["error", "double", {
|
||||||
"avoidEscape": true,
|
"avoidEscape": true,
|
||||||
"allowTemplateLiterals": true,
|
"allowTemplateLiterals": true,
|
||||||
@ -162,7 +160,6 @@ module.exports = {
|
|||||||
"avoidEscape": true,
|
"avoidEscape": true,
|
||||||
"allowTemplateLiterals": true,
|
"allowTemplateLiterals": true,
|
||||||
}],
|
}],
|
||||||
"no-invalid-this": "error",
|
|
||||||
"semi": "off",
|
"semi": "off",
|
||||||
"@typescript-eslint/semi": ["error"],
|
"@typescript-eslint/semi": ["error"],
|
||||||
"object-shorthand": "error",
|
"object-shorthand": "error",
|
||||||
|
|||||||
@ -84,6 +84,18 @@ export class ExtensionLoader {
|
|||||||
this.extensions.replace(extensions);
|
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) {
|
addExtension(extension: InstalledExtension) {
|
||||||
this.extensions.set(extension.id, extension);
|
this.extensions.set(extension.id, extension);
|
||||||
}
|
}
|
||||||
@ -103,7 +115,6 @@ export class ExtensionLoader {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error });
|
logger.error(`${logModule}: deactivation extension error`, { lensExtensionId, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeExtension(lensExtensionId: LensExtensionId) {
|
removeExtension(lensExtensionId: LensExtensionId) {
|
||||||
|
|||||||
@ -46,20 +46,23 @@ export type RouteHandler = (params: RouteParams) => void;
|
|||||||
|
|
||||||
export type ExtensionId = string;
|
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.
|
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
|
||||||
export const lensProtocolChannel = "protocol-handler";
|
export const lensProtocolChannel = "protocol-handler";
|
||||||
|
|
||||||
interface ExtensionIdMatch {
|
interface ExtensionUrlMatch {
|
||||||
[EXT_ID_MATCH]: string;
|
[EXTENSION_PUBLISHER_MATCH]: string;
|
||||||
|
[EXTENSION_NAME_MATCH]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LensProtocolRouter extends Singleton {
|
export class LensProtocolRouter extends Singleton {
|
||||||
private extentionRoutes = new Map<ExtensionId, Map<string, RouteHandler>>();
|
private extentionRoutes = new Map<ExtensionId, Map<string, RouteHandler>>();
|
||||||
private internalRoutes = new 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() {
|
public init() {
|
||||||
subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => {
|
subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => {
|
||||||
@ -87,26 +90,39 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
case "internal":
|
case "internal":
|
||||||
return this._route(this.internalRoutes, url);
|
return this._route(this.internalRoutes, url);
|
||||||
case "extension":
|
case "extension":
|
||||||
return this._routeToExtension(url);
|
// Possible rejected promise is ignored
|
||||||
|
this._routeToExtension(url);
|
||||||
default:
|
default:
|
||||||
throw new RoutingError(RoutingErrorType.INVALID_HOST, url);
|
throw new RoutingError(RoutingErrorType.INVALID_HOST, url);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _routeToExtension(url: Url): void {
|
private async _routeToExtension(url: Url) {
|
||||||
const match = matchPath<ExtensionIdMatch>(url.pathname, LensProtocolRouter.ExtensionIDSchema);
|
const match = matchPath<ExtensionUrlMatch>(url.pathname, LensProtocolRouter.ExtensionUrlSchema);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new RoutingError(RoutingErrorType.NO_EXTENSION_ID, url);
|
throw new RoutingError(RoutingErrorType.NO_EXTENSION_ID, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { [EXT_ID_MATCH]: id } = match.params;
|
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
|
||||||
const routes = this.extentionRoutes.get(id);
|
const name = `${publisher}/${partialName}`;
|
||||||
|
|
||||||
|
logger.info(`[PROTOCOL ROUTER] Extension ${name} matched`);
|
||||||
|
|
||||||
|
const routes = this.extentionRoutes.get(name);
|
||||||
|
|
||||||
if (!routes) {
|
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);
|
throw new RoutingError(RoutingErrorType.MISSING_EXTENSION, url);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._route(routes, url, true);
|
this._route(routes, url, true);
|
||||||
}
|
}
|
||||||
@ -117,7 +133,7 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
if (matchExtension) {
|
if (matchExtension) {
|
||||||
const joinChar = schema.startsWith("/") ? "" : "/";
|
const joinChar = schema.startsWith("/") ? "" : "/";
|
||||||
|
|
||||||
schema = `${LensProtocolRouter.ExtensionIDSchema}${joinChar}${schema}`;
|
schema = `${LensProtocolRouter.ExtensionUrlSchema}${joinChar}${schema}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [matchPath(url.pathname, { path: schema }), handler];
|
return [matchPath(url.pathname, { path: schema }), handler];
|
||||||
@ -132,7 +148,7 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
|
|
||||||
const [match, handler] = route;
|
const [match, handler] = route;
|
||||||
|
|
||||||
delete match.params[EXT_ID_MATCH];
|
delete match.params[EXTENSION_NAME_MATCH];
|
||||||
handler({
|
handler({
|
||||||
pathname: match.params,
|
pathname: match.params,
|
||||||
search: url.query,
|
search: url.query,
|
||||||
@ -151,10 +167,14 @@ export class LensProtocolRouter extends Singleton {
|
|||||||
this.extentionRoutes.set(id, new Map());
|
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");
|
throw new TypeError("Invalid url path schema");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.extentionRoutes.get(id).set(urlSchema, handler);
|
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 { themeStore } from "./theme.store";
|
||||||
import protocolEndpoints from "./api/protocol-endpoints";
|
import protocolEndpoints from "./api/protocol-endpoints";
|
||||||
import { LensProtocolRouter } from "../main/protocol-handler";
|
import { LensProtocolRouter } from "../main/protocol-handler";
|
||||||
|
import logger from "../main/logger";
|
||||||
|
import { installFromNpm } from "./components/+extensions";
|
||||||
|
|
||||||
type AppComponent = React.ComponentType & {
|
type AppComponent = React.ComponentType & {
|
||||||
init?(): Promise<void>;
|
init?(): Promise<void>;
|
||||||
@ -38,7 +40,19 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
|
|
||||||
extensionLoader.init();
|
extensionLoader.init();
|
||||||
extensionDiscovery.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();
|
protocolEndpoints.registerHandlers();
|
||||||
|
|
||||||
// preload common stores
|
// preload common stores
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user