import logger from "../logger"; import * as proto from "../../common/protocol-handler"; import Url from "url-parse"; import { LensExtension } from "../../extensions/lens-extension"; import { broadcastMessage } from "../../common/ipc"; import { observable, when } from "mobx"; export interface FallbackHandler { (name: string): Promise; } export class LensProtocolRouterMain extends proto.LensProtocolRouter { private missingExtensionHandlers: FallbackHandler[] = []; @observable rendererLoaded = false; @observable extensionsLoaded = false; /** * 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 { try { const url = new Url(rawUrl, true); if (url.protocol.toLowerCase() !== "lens:") { throw new proto.RoutingError(proto.RoutingErrorType.INVALID_PROTOCOL, url); } logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); switch (url.host) { case "app": return this._routeToInternal(url); case "extension": await when(() => this.extensionsLoaded); return this._routeToExtension(url); default: throw new proto.RoutingError(proto.RoutingErrorType.INVALID_HOST, url); } } catch (error) { if (error instanceof proto.RoutingError) { logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url }); } else { logger.error(`${proto.LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl }); } } } protected async _executeMissingExtensionHandlers(extensionName: string): Promise { for (const handler of this.missingExtensionHandlers) { if (await handler(extensionName)) { return true; } } return false; } protected async _findMatchingExtensionByName(url: Url): Promise { const firstAttempt = await super._findMatchingExtensionByName(url); if (typeof firstAttempt !== "string") { return firstAttempt; } if (await this._executeMissingExtensionHandlers(firstAttempt)) { return super._findMatchingExtensionByName(url); } return ""; } protected async _routeToInternal(url: Url): Promise { const rawUrl = url.toString(); // for sending to renderer super._routeToInternal(url); await when(() => this.rendererLoaded); return broadcastMessage(proto.ProtocolHandlerInternal, rawUrl); } protected async _routeToExtension(url: Url): Promise { const rawUrl = url.toString(); // for sending to renderer /** * This needs to be done first, so that the missing extension handlers can * be called before notifying the renderer. * * Note: this needs to clone the url because _routeToExtension modifies its * argument. */ await super._routeToExtension(new Url(url.toString(), true)); await when(() => this.rendererLoaded); return broadcastMessage(proto.ProtocolHandlerExtension, rawUrl); } /** * Add a function to the list which will be sequentially called if an extension * is not found while routing to the extensions * @param handler A function that tries to find an extension */ public addMissingExtensionHandler(handler: FallbackHandler): void { this.missingExtensionHandlers.push(handler); } }