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

Add notification in renderer if lens protocol handler fails to find any routes (#2787)

- Add notifications on missing lens:// handlers, and invalid URIs

- add notifications on unknown entity IDs

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-06-01 08:28:38 -04:00 committed by GitHub
parent 36e8888ecb
commit 5fd2d5501c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 273 additions and 130 deletions

View File

@ -21,7 +21,7 @@
import { match, matchPath } from "react-router";
import { countBy } from "lodash";
import { Singleton } from "../utils";
import { iter, Singleton } from "../utils";
import { pathToRegexp } from "path-to-regexp";
import logger from "../../main/logger";
import type Url from "url-parse";
@ -35,7 +35,8 @@ import type { RouteHandler, RouteParams } from "../../extensions/registries/prot
export const ProtocolHandlerIpcPrefix = "protocol-handler";
export const ProtocolHandlerInternal = `${ProtocolHandlerIpcPrefix}:internal`;
export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`;
export const ProtocolHandlerExtension = `${ProtocolHandlerIpcPrefix}:extension`;
export const ProtocolHandlerInvalid = `${ProtocolHandlerIpcPrefix}:invalid`;
/**
* These two names are long and cumbersome by design so as to decrease the chances
@ -47,6 +48,34 @@ export const ProtocolHandlerExtension= `${ProtocolHandlerIpcPrefix}:extension`;
export const EXTENSION_PUBLISHER_MATCH = "LENS_INTERNAL_EXTENSION_PUBLISHER_MATCH";
export const EXTENSION_NAME_MATCH = "LENS_INTERNAL_EXTENSION_NAME_MATCH";
/**
* Returned from routing attempts
*/
export enum RouteAttempt {
/**
* A handler was found in the set of registered routes
*/
MATCHED = "matched",
/**
* A handler was not found within the set of registered routes
*/
MISSING = "missing",
/**
* The extension that was matched in the route was not activated
*/
MISSING_EXTENSION = "no-extension",
}
export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: RouteAttempt): RouteAttempt {
switch (mainAttempt) {
case RouteAttempt.MATCHED:
return RouteAttempt.MATCHED;
case RouteAttempt.MISSING:
case RouteAttempt.MISSING_EXTENSION:
return rendererAttempt;
}
}
export abstract class LensProtocolRouter extends Singleton {
// Map between path schemas and the handlers
protected internalRoutes = new Map<string, RouteHandler>();
@ -56,11 +85,12 @@ export abstract class LensProtocolRouter extends Singleton {
static readonly ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}(\@[A-Za-z0-9_]+)?/:${EXTENSION_NAME_MATCH}`;
/**
*
* Attempts to route the given URL to all internal routes that have been registered
* @param url the parsed URL that initiated the `lens://` protocol
* @returns true if a route has been found
*/
protected _routeToInternal(url: Url): void {
this._route(Array.from(this.internalRoutes.entries()), url);
protected _routeToInternal(url: Url): RouteAttempt {
return this._route(this.internalRoutes.entries(), url);
}
/**
@ -69,7 +99,7 @@ export abstract class LensProtocolRouter extends Singleton {
* @param routes the array of path schemas, handler pairs to match against
* @param url the url (in its current state)
*/
protected _findMatchingRoute(routes: [string, RouteHandler][], url: Url): null | [match<Record<string, string>>, RouteHandler] {
protected _findMatchingRoute(routes: Iterable<[string, RouteHandler]>, url: Url): null | [match<Record<string, string>>, RouteHandler] {
const matches: [match<Record<string, string>>, RouteHandler][] = [];
for (const [schema, handler] of routes) {
@ -96,7 +126,7 @@ export abstract class LensProtocolRouter extends Singleton {
* @param routes the array of (path schemas, handler) pairs to match against
* @param url the url (in its current state)
*/
protected _route(routes: [string, RouteHandler][], url: Url, extensionName?: string): void {
protected _route(routes: Iterable<[string, RouteHandler]>, url: Url, extensionName?: string): RouteAttempt {
const route = this._findMatchingRoute(routes, url);
if (!route) {
@ -106,7 +136,9 @@ export abstract class LensProtocolRouter extends Singleton {
data.extensionName = extensionName;
}
return void logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
logger.info(`${LensProtocolRouter.LoggingPrefix}: No handler found`, data);
return RouteAttempt.MISSING;
}
const [match, handler] = route;
@ -121,6 +153,8 @@ export abstract class LensProtocolRouter extends Singleton {
}
handler(params);
return RouteAttempt.MATCHED;
}
/**
@ -174,23 +208,22 @@ export abstract class LensProtocolRouter extends Singleton {
* Note: this function modifies its argument, do not reuse
* @param url the protocol request URI that was "open"-ed
*/
protected async _routeToExtension(url: Url): Promise<void> {
protected async _routeToExtension(url: Url): Promise<RouteAttempt> {
const extension = await this._findMatchingExtensionByName(url);
if (typeof extension === "string") {
// failed to find an extension, it returned its name
return;
return RouteAttempt.MISSING_EXTENSION;
}
// remove the extension name from the path name so we don't need to match on it anymore
url.set("pathname", url.pathname.slice(extension.name.length + 1));
const handlers = extension
.protocolHandlers
.map<[string, RouteHandler]>(({ pathSchema, handler }) => [pathSchema, handler]);
try {
this._route(handlers, url, extension.name);
const handlers = iter.map(extension.protocolHandlers, ({ pathSchema, handler }) => [pathSchema, handler] as [string, RouteHandler]);
return this._route(handlers, url, extension.name);
} catch (error) {
if (error instanceof RoutingError) {
error.extensionName = extension.name;

View File

@ -93,8 +93,11 @@ if (!app.requestSingleInstanceLock()) {
for (const arg of process.argv) {
if (arg.toLowerCase().startsWith("lens://")) {
lprm.route(arg)
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }));
try {
lprm.route(arg);
} catch (error) {
logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg });
}
}
}
}
@ -104,8 +107,11 @@ app.on("second-instance", (event, argv) => {
for (const arg of argv) {
if (arg.toLowerCase().startsWith("lens://")) {
lprm.route(arg)
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }));
try {
lprm.route(arg);
} catch (error) {
logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg });
}
}
}
@ -252,9 +258,10 @@ autoUpdater.on("before-quit-for-update", () => blockQuit = false);
app.on("will-quit", (event) => {
// Quit app on Cmd+Q (MacOS)
logger.info("APP:QUIT");
appEventBus.emit({name: "app", action: "close"});
appEventBus.emit({ name: "app", action: "close" });
ClusterManager.getInstance(false)?.stop(); // close cluster connections
KubeconfigSyncManager.getInstance(false)?.stopSync();
LensProtocolRouterMain.getInstance(false)?.cleanup();
cleanup();
if (blockQuit) {
@ -268,10 +275,11 @@ app.on("open-url", (event, rawUrl) => {
// lens:// protocol handler
event.preventDefault();
LensProtocolRouterMain
.getInstance()
.route(rawUrl)
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
try {
LensProtocolRouterMain.getInstance().route(rawUrl);
} catch (error) {
logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl });
}
});
/**

View File

@ -23,7 +23,7 @@ import * as uuid from "uuid";
import { broadcastMessage } from "../../../common/ipc";
import { ProtocolHandlerExtension, ProtocolHandlerInternal } from "../../../common/protocol-handler";
import { noop } from "../../../common/utils";
import { delay, noop } from "../../../common/utils";
import { LensExtension } from "../../../extensions/main-api";
import { ExtensionLoader } from "../../../extensions/extension-loader";
import { ExtensionsStore } from "../../../extensions/extensions-store";
@ -56,27 +56,27 @@ describe("protocol router tests", () => {
LensProtocolRouterMain.resetInstance();
});
it("should throw on non-lens URLS", async () => {
it("should throw on non-lens URLS", () => {
try {
const lpr = LensProtocolRouterMain.getInstance();
expect(await lpr.route("https://google.ca")).toBeUndefined();
expect(lpr.route("https://google.ca")).toBeUndefined();
} catch (error) {
expect(error).toBeInstanceOf(Error);
}
});
it("should throw when host not internal or extension", async () => {
it("should throw when host not internal or extension", () => {
try {
const lpr = LensProtocolRouterMain.getInstance();
expect(await lpr.route("lens://foobar")).toBeUndefined();
expect(lpr.route("lens://foobar")).toBeUndefined();
} catch (error) {
expect(error).toBeInstanceOf(Error);
}
});
it.only("should not throw when has valid host", async () => {
it("should not throw when has valid host", async () => {
const extId = uuid.v4();
const ext = new LensExtension({
id: extId,
@ -102,38 +102,39 @@ describe("protocol router tests", () => {
lpr.addInternalHandler("/", noop);
try {
expect(await lpr.route("lens://app")).toBeUndefined();
expect(lpr.route("lens://app")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
try {
expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
expect(lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
expect(broadcastMessage).toHaveBeenNthCalledWith(1, ProtocolHandlerInternal, "lens://app/");
expect(broadcastMessage).toHaveBeenNthCalledWith(2, ProtocolHandlerExtension, "lens://extension/@mirantis/minikube");
await delay(50);
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app/", "matched");
expect(broadcastMessage).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched");
});
it("should call handler if matches", async () => {
it("should call handler if matches", () => {
const lpr = LensProtocolRouterMain.getInstance();
let called = false;
lpr.addInternalHandler("/page", () => { called = true; });
try {
expect(await lpr.route("lens://app/page")).toBeUndefined();
expect(lpr.route("lens://app/page")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
expect(called).toBe(true);
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page", "matched");
});
it("should call most exact handler", async () => {
it("should call most exact handler", () => {
const lpr = LensProtocolRouterMain.getInstance();
let called: any = 0;
@ -141,13 +142,13 @@ describe("protocol router tests", () => {
lpr.addInternalHandler("/page/:id", params => { called = params.pathname.id; });
try {
expect(await lpr.route("lens://app/page/foo")).toBeUndefined();
expect(lpr.route("lens://app/page/foo")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
expect(called).toBe("foo");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo", "matched");
});
it("should call most exact handler for an extension", async () => {
@ -180,13 +181,14 @@ describe("protocol router tests", () => {
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
try {
expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
expect(lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
await delay(50);
expect(called).toBe("foob");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched");
});
it("should work with non-org extensions", async () => {
@ -245,13 +247,15 @@ describe("protocol router tests", () => {
(ExtensionsStore.getInstance() as any).state.set("icecream", { enabled: true, name: "icecream" });
try {
expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined();
expect(lpr.route("lens://extension/icecream/page")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
await delay(50);
expect(called).toBe(1);
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched");
});
it("should throw if urlSchema is invalid", () => {
@ -260,7 +264,7 @@ describe("protocol router tests", () => {
expect(() => lpr.addInternalHandler("/:@", noop)).toThrowError();
});
it("should call most exact handler with 3 found handlers", async () => {
it("should call most exact handler with 3 found handlers", () => {
const lpr = LensProtocolRouterMain.getInstance();
let called: any = 0;
@ -270,16 +274,16 @@ describe("protocol router tests", () => {
lpr.addInternalHandler("/page/bar", () => { called = 4; });
try {
expect(await lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
expect(lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
expect(called).toBe(3);
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
});
it("should call most exact handler with 2 found handlers", async () => {
it("should call most exact handler with 2 found handlers", () => {
const lpr = LensProtocolRouterMain.getInstance();
let called: any = 0;
@ -288,12 +292,12 @@ describe("protocol router tests", () => {
lpr.addInternalHandler("/page/bar", () => { called = 4; });
try {
expect(await lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
expect(lpr.route("lens://app/page/foo/bar/bat")).toBeUndefined();
} catch (error) {
expect(throwIfDefined(error)).not.toThrow();
}
expect(called).toBe(1);
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat");
expect(broadcastMessage).toBeCalledWith(ProtocolHandlerInternal, "lens://app/page/foo/bar/bat", "matched");
});
});

View File

@ -25,6 +25,8 @@ import Url from "url-parse";
import type { LensExtension } from "../../extensions/lens-extension";
import { broadcastMessage } from "../../common/ipc";
import { observable, when, makeObservable } from "mobx";
import { ProtocolHandlerInvalid, RouteAttempt } from "../../common/protocol-handler";
import { disposer } from "../../common/utils";
export interface FallbackHandler {
(name: string): Promise<boolean>;
@ -36,19 +38,25 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
@observable rendererLoaded = false;
@observable extensionsLoaded = false;
protected disposers = disposer();
constructor() {
super();
makeObservable(this);
}
public cleanup() {
this.disposers();
}
/**
* Find the most specific registered handler, if it exists, and invoke it.
*
* This will send an IPC message to the renderer router to do the same
* in the renderer.
*/
public async route(rawUrl: string): Promise<void> {
public route(rawUrl: string) {
try {
const url = new Url(rawUrl, true);
@ -60,16 +68,18 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
switch (url.host) {
case "app":
return this._routeToInternal(url);
this._routeToInternal(url);
break;
case "extension":
await when(() => this.extensionsLoaded);
return this._routeToExtension(url);
this.disposers.push(when(() => this.extensionsLoaded, () => this._routeToExtension(url)));
break;
default:
throw new proto.RoutingError(proto.RoutingErrorType.INVALID_HOST, url);
}
} catch (error) {
broadcastMessage(ProtocolHandlerInvalid, error.toString(), rawUrl);
if (error instanceof proto.RoutingError) {
logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url });
} else {
@ -102,17 +112,16 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
return "";
}
protected async _routeToInternal(url: Url): Promise<void> {
protected _routeToInternal(url: Url): RouteAttempt {
const rawUrl = url.toString(); // for sending to renderer
const attempt = super._routeToInternal(url);
super._routeToInternal(url);
this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt)));
await when(() => this.rendererLoaded);
return broadcastMessage(proto.ProtocolHandlerInternal, rawUrl);
return attempt;
}
protected async _routeToExtension(url: Url): Promise<void> {
protected async _routeToExtension(url: Url): Promise<RouteAttempt> {
const rawUrl = url.toString(); // for sending to renderer
/**
@ -122,10 +131,11 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
* Note: this needs to clone the url because _routeToExtension modifies its
* argument.
*/
await super._routeToExtension(new Url(url.toString(), true));
await when(() => this.rendererLoaded);
const attempt = await super._routeToExtension(new Url(url.toString(), true));
return broadcastMessage(proto.ProtocolHandlerExtension, rawUrl);
this.disposers.push(when(() => this.rendererLoaded, () => broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt)));
return attempt;
}
/**

View File

@ -37,7 +37,7 @@ export class Notifications extends React.Component {
static ok(message: NotificationMessage) {
notificationsStore.add({
message,
timeout: 2500,
timeout: 2_500,
status: NotificationStatus.OK
});
}
@ -45,12 +45,19 @@ export class Notifications extends React.Component {
static error(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
notificationsStore.add({
message,
timeout: 10000,
timeout: 10_000,
status: NotificationStatus.ERROR,
...customOpts
});
}
static shortInfo(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
this.info(message, {
timeout: 5_000,
...customOpts
});
}
static info(message: NotificationMessage, customOpts: Partial<Notification> = {}) {
return notificationsStore.add({
status: NotificationStatus.INFO,

View File

@ -19,6 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import { addClusterURL } from "../components/+add-cluster";
import { catalogURL } from "../components/+catalog";
import { attemptInstallByInfo, extensionsURL } from "../components/+extensions";
@ -30,6 +31,7 @@ import { entitySettingsURL } from "../components/+entity-settings";
import { catalogEntityRegistry } from "../api/catalog-entity-registry";
import { ClusterStore } from "../../common/cluster-store";
import { EXTENSION_NAME_MATCH, EXTENSION_PUBLISHER_MATCH, LensProtocolRouter } from "../../common/protocol-handler";
import { Notifications } from "../components/notifications";
export function bindProtocolAddRouteHandlers() {
LensProtocolRouterRenderer
@ -37,7 +39,16 @@ export function bindProtocolAddRouteHandlers() {
.addInternalHandler("/preferences", ({ search: { highlight }}) => {
navigate(preferencesURL({ fragment: highlight }));
})
.addInternalHandler("/", () => {
.addInternalHandler("/", ({ tail }) => {
if (tail) {
Notifications.shortInfo(
<p>
Unknown Action for <code>lens://app/{tail}</code>.{" "}
Are you on the latest version?
</p>
);
}
navigate(catalogURL());
})
.addInternalHandler("/landing", () => {
@ -59,7 +70,11 @@ export function bindProtocolAddRouteHandlers() {
if (entity) {
navigate(entitySettingsURL({ params: { entityId } }));
} else {
console.log("[APP-HANDLER]: catalog entity with given ID does not exist", { entityId });
Notifications.shortInfo(
<p>
Unknown catalog entity <code>{entityId}</code>.
</p>
);
}
})
// Handlers below are deprecated and only kept for backward compact purposes
@ -69,7 +84,11 @@ export function bindProtocolAddRouteHandlers() {
if (cluster) {
navigate(clusterViewURL({ params: { clusterId } }));
} else {
console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId });
Notifications.shortInfo(
<p>
Unknown catalog entity <code>{clusterId}</code>.
</p>
);
}
})
.addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId } }) => {
@ -78,7 +97,11 @@ export function bindProtocolAddRouteHandlers() {
if (cluster) {
navigate(entitySettingsURL({ params: { entityId: clusterId } }));
} else {
console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId });
Notifications.shortInfo(
<p>
Unknown catalog entity <code>{clusterId}</code>.
</p>
);
}
})
.addInternalHandler("/extensions", () => {

View File

@ -1,61 +0,0 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { ipcRenderer } from "electron";
import * as proto from "../../common/protocol-handler";
import logger from "../../main/logger";
import Url from "url-parse";
import { boundMethod } from "../utils";
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
/**
* This function is needed to be called early on in the renderers lifetime.
*/
public init(): void {
ipcRenderer
.on(proto.ProtocolHandlerInternal, this.ipcInternalHandler)
.on(proto.ProtocolHandlerExtension, this.ipcExtensionHandler);
}
@boundMethod
private ipcInternalHandler(event: Electron.IpcRendererEvent, ...args: any[]): void {
if (args.length !== 1) {
return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: unexpected number of args`, { args });
}
const [rawUrl] = args;
const url = new Url(rawUrl, true);
this._routeToInternal(url);
}
@boundMethod
private ipcExtensionHandler(event: Electron.IpcRendererEvent, ...args: any[]): void {
if (args.length !== 1) {
return void logger.warn(`${proto.LensProtocolRouter.LoggingPrefix}: unexpected number of args`, { args });
}
const [rawUrl] = args;
const url = new Url(rawUrl, true);
this._routeToExtension(url);
}
}

View File

@ -0,0 +1,119 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
import { ipcRenderer } from "electron";
import * as proto from "../../common/protocol-handler";
import Url from "url-parse";
import { onCorrect } from "../../common/ipc";
import { foldAttemptResults, ProtocolHandlerInvalid, RouteAttempt } from "../../common/protocol-handler";
import { Notifications } from "../components/notifications";
function verifyIpcArgs(args: unknown[]): args is [string, RouteAttempt] {
if (args.length !== 2) {
return false;
}
if (typeof args[0] !== "string") {
return false;
}
switch (args[1]) {
case RouteAttempt.MATCHED:
case RouteAttempt.MISSING:
case RouteAttempt.MISSING_EXTENSION:
return true;
default:
return false;
}
}
export class LensProtocolRouterRenderer extends proto.LensProtocolRouter {
/**
* This function is needed to be called early on in the renderers lifetime.
*/
public init(): void {
onCorrect({
channel: proto.ProtocolHandlerInternal,
source: ipcRenderer,
verifier: verifyIpcArgs,
listener: (event, rawUrl, mainAttemptResult) => {
const rendererAttempt = this._routeToInternal(new Url(rawUrl, true));
if (foldAttemptResults(mainAttemptResult, rendererAttempt) === RouteAttempt.MISSING) {
Notifications.shortInfo(
<p>
Unknown action <code>{rawUrl}</code>.{" "}
Are you on the latest version?
</p>
);
}
}
});
onCorrect({
channel: proto.ProtocolHandlerExtension,
source: ipcRenderer,
verifier: verifyIpcArgs,
listener: async (event, rawUrl, mainAttemptResult) => {
const rendererAttempt = await this._routeToExtension(new Url(rawUrl, true));
switch (foldAttemptResults(mainAttemptResult, rendererAttempt)) {
case RouteAttempt.MISSING:
Notifications.shortInfo(
<p>
Unknown action <code>{rawUrl}</code>.{" "}
Are you on the latest version of the extension?
</p>
);
break;
case RouteAttempt.MISSING_EXTENSION:
Notifications.shortInfo(
<p>
Missing extension for action <code>{rawUrl}</code>.{" "}
Not able to find extension in our known list.{" "}
Try installing it manually.
</p>
);
break;
}
}
});
onCorrect({
channel: ProtocolHandlerInvalid,
source: ipcRenderer,
listener: (event, error, rawUrl) => {
Notifications.error((
<>
<p>
Failed to route <code>{rawUrl}</code>.
</p>
<p>
<b>Error:</b> {error}
</p>
</>
));
},
verifier: (args): args is [string, string] => {
return args.length === 2 && typeof args[0] === "string";
}
});
}
}