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

Auto-clean extension handlers on deactivation

- remove handlers when an extension is deactivated or removed

- make sure that the found extension when routing a request is currently
  enabled (as a backup)

- added documentation about the above behaviour to the guide

- tweaked the naming convension so that it is clearer that the router
  uses extension names as not IDs (which currently are folder paths)

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-01-19 14:49:15 -05:00
parent 8430b67d55
commit e7ee7fe2b0
8 changed files with 53 additions and 28 deletions

View File

@ -45,3 +45,9 @@ On the other hand, the subpath `"/display/notification"` would be routed to #3.
The URI is routed to the most specific matching `pathSchema`. The URI is routed to the most specific matching `pathSchema`.
This way the `"/"` (root) `pathSchema` acts as a sort of catch all or default route if no other route matches. This way the `"/"` (root) `pathSchema` acts as a sort of catch all or default route if no other route matches.
### Cleaning Up
Currently there is not way to remove a protocol handler once it has been registered.
Handlers will not be called if the extension is deactivated or uninstalled.
This means that the handlers should be added (or re-added as the case may be) on every activation of an extension instance.

View File

@ -1,4 +1,3 @@
import { LensExtensionId } from "../../extensions/lens-extension";
import { hasOwnProperties, hasOwnProperty, Singleton } from "../utils"; import { hasOwnProperties, hasOwnProperty, Singleton } from "../utils";
const ProtocolHandlerIpcPrefix = "protocol-handler"; const ProtocolHandlerIpcPrefix = "protocol-handler";
@ -22,7 +21,7 @@ export enum HandlerType {
interface ExtensionParams { interface ExtensionParams {
handlerType: HandlerType.EXTENSION, handlerType: HandlerType.EXTENSION,
extensionId: string, extensionName: string,
} }
interface InternalParams { interface InternalParams {
@ -37,7 +36,7 @@ export type RegisterParams = BaseParams & {
}; };
export interface DeregisterParams { export interface DeregisterParams {
extensionId: string, extensionName: string,
} }
export type BackChannelParams = BaseParams & { export type BackChannelParams = BaseParams & {
@ -49,8 +48,8 @@ export abstract class LensProtocolRouter extends Singleton {
public static readonly LoggingPrefix = "[PROTOCOL ROUTER]"; public static readonly LoggingPrefix = "[PROTOCOL ROUTER]";
public abstract on(urlSchema: string, handler: RouteHandler): void; public abstract on(urlSchema: string, handler: RouteHandler): void;
public abstract extensionOn(id: LensExtensionId, urlSchema: string, handler: RouteHandler): void; public abstract extensionOn(extName: string, urlSchema: string, handler: RouteHandler): void;
public abstract removeExtensionHandlers(id: LensExtensionId): void; public abstract removeExtensionHandlers(extName: string): void;
} }
/** /**
@ -75,15 +74,15 @@ function validateBaseParams(args: unknown): args is BaseParams {
} }
if (handlerType === HandlerType.EXTENSION) { if (handlerType === HandlerType.EXTENSION) {
if (!hasOwnProperty(args, "extensionId")) { if (!hasOwnProperty(args, "extensionName")) {
return false; return false;
} }
// or handlerType must be HandlerType.EXTENSION // or handlerType must be HandlerType.EXTENSION
const { extensionId } = args; const { extensionName } = args;
// but if for an extension then the extensionId is required, must be a stirng, and must be non-empty // but if for an extension then the extensionName is required, must be a stirng, and must be non-empty
return Boolean(extensionId && typeof extensionId === "string"); return Boolean(extensionName && typeof extensionName === "string");
} }
// reject all other values of handlerType // reject all other values of handlerType
@ -121,16 +120,16 @@ export function validateRegisterParams(args: unknown): args is RegisterParams {
* @param args a deserialized value * @param args a deserialized value
*/ */
export function validateDeregisterParams(args: unknown): args is DeregisterParams { export function validateDeregisterParams(args: unknown): args is DeregisterParams {
if (args == null || typeof args !== "object") { if (!args || typeof args !== "object") {
// it must be an object // it must be an object
return false; return false;
} }
if (!hasOwnProperties(args, "extensionId")) { if (!hasOwnProperties(args, "extensionName")) {
return false; return false;
} }
if (typeof args.extensionId !== "string" || args.extensionId.length === 0) { if (typeof args.extensionName !== "string" || args.extensionName.length === 0) {
// ipcChannel is required, must be a string, must be non-empty // ipcChannel is required, must be a string, must be non-empty
return false; return false;
} }
@ -143,7 +142,7 @@ export function validateDeregisterParams(args: unknown): args is DeregisterParam
* @param args a deserialized value * @param args a deserialized value
*/ */
export function validateRouteParams(args: unknown): args is RouteParams { export function validateRouteParams(args: unknown): args is RouteParams {
if (args == null || typeof args !== "object") { if (!args || typeof args !== "object") {
// it must be an object // it must be an object
return false; return false;
} }

View File

@ -102,7 +102,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) {

View File

@ -27,7 +27,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
protected state = observable.map<LensExtensionId, LensExtensionState>(); protected state = observable.map<LensExtensionId, LensExtensionState>();
isEnabled(extId: LensExtensionId) { isEnabled(extId: LensExtensionId): boolean {
const state = this.state.get(extId); const state = this.state.get(extId);
// By default false, so that copied extensions are disabled by default. // By default false, so that copied extensions are disabled by default.
@ -35,6 +35,10 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
return Boolean(state?.enabled); return Boolean(state?.enabled);
} }
isEnabledByName(extName: string): boolean {
return this.enabledExtensions.includes(extName);
}
@action @action
mergeState(extensionsState: Record<LensExtensionId, LensExtensionState>) { mergeState(extensionsState: Record<LensExtensionId, LensExtensionState>) {
this.state.merge(extensionsState); this.state.merge(extensionsState);

View File

@ -19,6 +19,14 @@ export class LensMainExtension extends LensExtension {
await windowManager.navigate(pageUrl, frameId); await windowManager.navigate(pageUrl, frameId);
} }
async disable() {
const lprm = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
lprm.removeExtensionHandlers(this.name);
return super.disable();
}
/** /**
* Registers a handler to be called when a `lens://` link is called. * Registers a handler to be called when a `lens://` link is called.
* @param pathSchema The path schema for the route. * @param pathSchema The path schema for the route.

View File

@ -30,6 +30,14 @@ export class LensRendererExtension extends LensExtension {
navigate(pageUrl); navigate(pageUrl);
} }
async disable() {
const lprm = LensProtocolRouterRenderer.getInstance<LensProtocolRouterRenderer>();
lprm.removeExtensionHandlers(this.name);
return super.disable();
}
/** /**
* Defines if extension is enabled for a given cluster. Defaults to `true`. * Defines if extension is enabled for a given cluster. Defaults to `true`.
*/ */

View File

@ -7,6 +7,7 @@ import * as proto from "../../common/protocol-handler";
import { LensExtensionId } from "../../extensions/lens-extension"; import { LensExtensionId } from "../../extensions/lens-extension";
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import { WindowManager } from "../window-manager"; import { WindowManager } from "../window-manager";
import { extensionsStore } from "../../extensions/extensions-store";
const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH"; const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH";
const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH"; const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH";
@ -68,7 +69,7 @@ function registerIpcHandler(event: Electron.IpcMainEvent, ...ipcArgs: unknown[])
case proto.HandlerType.INTERNAL: case proto.HandlerType.INTERNAL:
return lprm.on(args.pathSchema, produceNotifyRenderer(args.handlerId)); return lprm.on(args.pathSchema, produceNotifyRenderer(args.handlerId));
case proto.HandlerType.EXTENSION: case proto.HandlerType.EXTENSION:
return lprm.extensionOn(args.extensionId, args.pathSchema, produceNotifyRenderer(args.handlerId)); return lprm.extensionOn(args.extensionName, args.pathSchema, produceNotifyRenderer(args.handlerId));
} }
} }
@ -79,7 +80,7 @@ function deregisterIpcHandler(event: Electron.IpcMainEvent, ...ipcArgs: unknown[
return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: ipc call to "${proto.ProtocolHandlerDeregister}" invalid arguments`, { ipcArgs }); return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: ipc call to "${proto.ProtocolHandlerDeregister}" invalid arguments`, { ipcArgs });
} }
LensProtocolRouterMain.getInstance<LensProtocolRouterMain>().removeExtensionHandlers(args.extensionId); LensProtocolRouterMain.getInstance<LensProtocolRouterMain>().removeExtensionHandlers(args.extensionName);
} }
export class LensProtocolRouterMain extends proto.LensProtocolRouter { export class LensProtocolRouterMain extends proto.LensProtocolRouter {
@ -128,7 +129,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
let routes = this.extentionRoutes.get(name); let routes = this.extentionRoutes.get(name);
if (!routes) { if (!routes || !extensionsStore.isEnabledByName(name)) {
if (this.missingExtensionHandlers.length === 0) { if (this.missingExtensionHandlers.length === 0) {
throw new proto.RoutingError(proto.RoutingErrorType.MISSING_EXTENSION, url); throw new proto.RoutingError(proto.RoutingErrorType.MISSING_EXTENSION, url);
} }

View File

@ -4,7 +4,7 @@ import { autobind } from "../utils";
import * as uuid from "uuid"; import * as uuid from "uuid";
import logger from "../../main/logger"; import logger from "../../main/logger";
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter { export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
// Map between extension IDs and a Map betweeen generated UUIDs and the handlers // Map between extension names and a Map betweeen generated UUIDs and the handlers
private extensionHandlers = new Map<string, Map<string, proto.RouteHandler>>(); private extensionHandlers = new Map<string, Map<string, proto.RouteHandler>>();
// Map between generated UUIDs and the handlers // Map between generated UUIDs and the handlers
private internalHandlers = new Map<string, proto.RouteHandler>(); private internalHandlers = new Map<string, proto.RouteHandler>();
@ -37,8 +37,8 @@ export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
} }
case proto.HandlerType.EXTENSION: { case proto.HandlerType.EXTENSION: {
const { handlerId, params, extensionId } = args; const { handlerId, params, extensionName } = args;
const handler = this.extensionHandlers.get(handlerId)?.get(extensionId); const handler = this.extensionHandlers.get(handlerId)?.get(extensionName);
if (!handler) { if (!handler) {
return void logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ipc call to "${proto.ProtocolHandlerBackChannel}" unknown handlerId or unknown extensionId`, { args }); return void logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ipc call to "${proto.ProtocolHandlerBackChannel}" unknown handlerId or unknown extensionId`, { args });
@ -63,30 +63,30 @@ export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
ipcRenderer.send(proto.ProtocolHandlerRegister, args); ipcRenderer.send(proto.ProtocolHandlerRegister, args);
} }
public extensionOn(extensionId: string, pathSchema: string, handler: proto.RouteHandler): void { public extensionOn(extensionName: string, pathSchema: string, handler: proto.RouteHandler): void {
const handlerId = uuid.v4(); const handlerId = uuid.v4();
const args: proto.RegisterParams = { const args: proto.RegisterParams = {
handlerType: proto.HandlerType.EXTENSION, handlerType: proto.HandlerType.EXTENSION,
extensionId, extensionName,
pathSchema, pathSchema,
handlerId, handlerId,
}; };
this.extensionHandlers this.extensionHandlers
.set(extensionId, this.extensionHandlers.get(extensionId) ?? new Map()) .set(extensionName, this.extensionHandlers.get(extensionName) ?? new Map())
.get(extensionId) .get(extensionName)
.set(handlerId, handler); .set(handlerId, handler);
ipcRenderer.send(proto.ProtocolHandlerRegister, args); ipcRenderer.send(proto.ProtocolHandlerRegister, args);
} }
public removeExtensionHandlers(extensionId: string): void { public removeExtensionHandlers(extensionName: string): void {
const args: proto.DeregisterParams = { const args: proto.DeregisterParams = {
extensionId, extensionName,
}; };
ipcRenderer.send(proto.ProtocolHandlerDeregister, args); ipcRenderer.send(proto.ProtocolHandlerDeregister, args);
this.extensionHandlers.delete(extensionId); this.extensionHandlers.delete(extensionName);
} }
} }