mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add IPC capabilities for Extensions (#2775)
* Add IPC capabilities for Extensions Signed-off-by: Sebastian Malton <sebastian@malton.name> * revert onA|D change: Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch to pushing the disposer in the methods Signed-off-by: Sebastian Malton <sebastian@malton.name> * improve documentation, switch to a singleton instead of extension methods Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix build Signed-off-by: Sebastian Malton <sebastian@malton.name> * make exported class abstract, improve guide Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix docs Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * Change guide demo to initialization in constructor Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
e188cf45e6
commit
6be465858b
@ -24,6 +24,7 @@ Each guide or code sample includes the following:
|
|||||||
| [KubeObjectListLayout](kube-object-list-layout.md) | |
|
| [KubeObjectListLayout](kube-object-list-layout.md) | |
|
||||||
| [Working with mobx](working-with-mobx.md) | |
|
| [Working with mobx](working-with-mobx.md) | |
|
||||||
| [Protocol Handlers](protocol-handlers.md) | |
|
| [Protocol Handlers](protocol-handlers.md) | |
|
||||||
|
| [Sending Data between main and renderer](ipc.md) | |
|
||||||
|
|
||||||
## Samples
|
## Samples
|
||||||
|
|
||||||
|
|||||||
131
docs/extensions/guides/ipc.md
Normal file
131
docs/extensions/guides/ipc.md
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# Inter Process Communication
|
||||||
|
|
||||||
|
A Lens Extension can utilize IPC to send information between its `LensRendererExtension` and its `LensMainExtension`.
|
||||||
|
This is useful when wanting to communicate directly within your extension.
|
||||||
|
For example, if a user logs into a service that your extension is a facade for and `main` needs to know some information so that you can start syncing items to the `Catalog`, this would be a good way to send that information along.
|
||||||
|
|
||||||
|
IPC channels are blocked off per extension.
|
||||||
|
Meaning that each extension can only communicate with itself.
|
||||||
|
|
||||||
|
## Types of IPC
|
||||||
|
|
||||||
|
There are two flavours of IPC that are provided:
|
||||||
|
|
||||||
|
- Event based
|
||||||
|
- Request based
|
||||||
|
|
||||||
|
### Event Based IPC
|
||||||
|
|
||||||
|
This is the same as an [Event Emitter](https://nodejs.org/api/events.html#events_class_eventemitter) but is not limited to just one Javascript process.
|
||||||
|
This is a good option when you need to report that something has happened but you don't need a response.
|
||||||
|
|
||||||
|
This is a fully two-way form of communication.
|
||||||
|
Both `LensMainExtension` and `LensRendererExtension` can do this sort of IPC.
|
||||||
|
|
||||||
|
### Request Based IPC
|
||||||
|
|
||||||
|
This is more like a Remote Procedure Call (RPC).
|
||||||
|
With this sort of IPC the caller waits for the result from the other side.
|
||||||
|
This is accomplished by returning a `Promise<T>` which needs to be `await`-ed.
|
||||||
|
|
||||||
|
This is a unidirectional form of communication.
|
||||||
|
Only `LensRendererExtension` can initiate this kind of request, and only `LensMainExtension` can and respond this this kind of request.
|
||||||
|
|
||||||
|
## Registering IPC Handlers and Listeners
|
||||||
|
|
||||||
|
The general terminology is as follows:
|
||||||
|
|
||||||
|
- A "handler" is the function that responds to a "Request Based IPC" event.
|
||||||
|
- A "listener" is the function that is called when a "Event Based IPC" event is emitted.
|
||||||
|
|
||||||
|
To register either a handler or a listener, you should do something like the following:
|
||||||
|
|
||||||
|
`main.ts`:
|
||||||
|
```typescript
|
||||||
|
import { LensMainExtension, Interface, Types, Store } from "@k8slens/extensions";
|
||||||
|
import { registerListeners, IpcMain } from "./helpers/main";
|
||||||
|
|
||||||
|
export class ExampleExtensionMain extends LensMainExtension {
|
||||||
|
onActivate() {
|
||||||
|
IpcMain.createInstance(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This file shows that you need to create an instance of the store to be able to use IPC.
|
||||||
|
Lens will automatically clean up that store and all the handlers on deactivation and uninstall.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`helpers/main.ts`:
|
||||||
|
```typescript
|
||||||
|
import { Store } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
export class IpcMain extends Store.MainIpcStore {
|
||||||
|
constructor(extension: LensMainExtension) {
|
||||||
|
super(extension);
|
||||||
|
|
||||||
|
this.listenIpc("initialize", onInitialize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInitialize(event: Types.IpcMainEvent, id: string) {
|
||||||
|
console.log(`starting to initialize: ${id}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In other files, it is not necessary to pass around any instances.
|
||||||
|
It should be able to just call `getInstance()` everywhere in your extension as needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`renderer.ts`:
|
||||||
|
```typescript
|
||||||
|
import { LensRendererExtension, Interface, Types } from "@k8slens/extensions";
|
||||||
|
import { IpcRenderer } from "./helpers/renderer";
|
||||||
|
|
||||||
|
export class ExampleExtensionRenderer extends LensRendererExtension {
|
||||||
|
onActivate() {
|
||||||
|
const ipc = IpcRenderer.createInstance(this);
|
||||||
|
|
||||||
|
setTimeout(() => ipc.broadcastIpc("initialize", "an-id"), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also needed to create an instance to broadcast messages too.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`helpers/renderer.ts`:
|
||||||
|
```typescript
|
||||||
|
import { Store } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
export class IpcMain extends Store.RendererIpcStore {}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is necessary to create child classes of these `abstract class`'s in your extension before you can use them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
As this example shows: the channel names *must* be the same.
|
||||||
|
It should also be noted that "listeners" and "handlers" are specific to either `LensRendererExtension` and `LensMainExtension`.
|
||||||
|
There is no behind the scenes transfer of these functions.
|
||||||
|
|
||||||
|
If you want to register a "handler" you would call `Store.MainIpcStore.handleIpc(...)` instead.
|
||||||
|
The cleanup of these handlers is handled by Lens itself.
|
||||||
|
|
||||||
|
`Store.RendererIpcStore.broadcastIpc(...)` and `Store.MainIpcStore.broadcastIpc(...)` sends an event to all renderer frames and to main.
|
||||||
|
Because of this, no matter where you broadcast from, all listeners in `main` and `renderer` will be notified.
|
||||||
|
|
||||||
|
### Allowed Values
|
||||||
|
|
||||||
|
This IPC mechanism utilizes the [Structured Clone Algorithm](developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) for serialization.
|
||||||
|
This means that more types than what are JSON serializable can be used, but not all the information will be passed through.
|
||||||
|
|
||||||
|
## Using IPC
|
||||||
|
|
||||||
|
Calling IPC is very simple.
|
||||||
|
If you are meaning to do an event based call, merely call `broadcastIpc(<channel>, ...<args>)` from within your extension.
|
||||||
|
|
||||||
|
If you are meaning to do a request based call from `renderer`, you should do `const res = await Store.RendererIpcStore.invokeIpc(<channel>, ...<args>));` instead.
|
||||||
@ -35,6 +35,7 @@ nav:
|
|||||||
- Stores: extensions/guides/stores.md
|
- Stores: extensions/guides/stores.md
|
||||||
- Working with MobX: extensions/guides/working-with-mobx.md
|
- Working with MobX: extensions/guides/working-with-mobx.md
|
||||||
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
||||||
|
- IPC: extensions/guides/ipc.md
|
||||||
- Testing and Publishing:
|
- Testing and Publishing:
|
||||||
- Testing Extensions: extensions/testing-and-publishing/testing.md
|
- Testing Extensions: extensions/testing-and-publishing/testing.md
|
||||||
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
|
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
|
||||||
|
|||||||
@ -42,35 +42,33 @@ function getSubFrames(): ClusterFrameInfo[] {
|
|||||||
return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true });
|
return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function broadcastMessage(channel: string, ...args: any[]) {
|
export function broadcastMessage(channel: string, ...args: any[]) {
|
||||||
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
||||||
|
|
||||||
if (!views) return;
|
if (!views) return;
|
||||||
|
|
||||||
if (ipcRenderer) {
|
ipcRenderer?.send(channel, ...args);
|
||||||
ipcRenderer.send(channel, ...args);
|
ipcMain?.emit(channel, ...args);
|
||||||
} else if (ipcMain) {
|
|
||||||
ipcMain.emit(channel, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const view of views) {
|
const subFramesP = ipcRenderer
|
||||||
const type = view.getType();
|
? requestMain(subFramesChannel)
|
||||||
|
: Promise.resolve(getSubFrames());
|
||||||
|
|
||||||
logger.silly(`[IPC]: broadcasting "${channel}" to ${type}=${view.id}`, { args });
|
subFramesP
|
||||||
view.send(channel, ...args);
|
.then(subFrames => {
|
||||||
|
for (const view of views) {
|
||||||
|
try {
|
||||||
|
logger.silly(`[IPC]: broadcasting "${channel}" to ${view.getType()}=${view.id}`, { args });
|
||||||
|
view.send(channel, ...args);
|
||||||
|
|
||||||
try {
|
for (const frameInfo of subFrames) {
|
||||||
const subFrames: ClusterFrameInfo[] = ipcRenderer
|
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
|
||||||
? await requestMain(subFramesChannel)
|
}
|
||||||
: getSubFrames();
|
} catch (error) {
|
||||||
|
logger.error("[IPC]: failed to send IPC message", { error: String(error) });
|
||||||
for (const frameInfo of subFrames) {
|
}
|
||||||
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
});
|
||||||
logger.error("[IPC]: failed to send IPC message", { error: String(error) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||||
|
|||||||
@ -19,10 +19,12 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ipcMain } from "electron";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
|
import { Disposer } from "../utils";
|
||||||
|
|
||||||
export type HandlerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
|
export type ListenerEvent<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
|
||||||
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
||||||
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
||||||
|
|
||||||
@ -34,22 +36,22 @@ export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
|||||||
* @param verifier The function to be called to verify that the args are the correct type
|
* @param verifier The function to be called to verify that the args are the correct type
|
||||||
*/
|
*/
|
||||||
export function onceCorrect<
|
export function onceCorrect<
|
||||||
EM extends EventEmitter,
|
IPC extends EventEmitter,
|
||||||
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
Listener extends (event: ListenerEvent<IPC>, ...args: any[]) => any
|
||||||
>({
|
>({
|
||||||
source,
|
source,
|
||||||
channel,
|
channel,
|
||||||
listener,
|
listener,
|
||||||
verifier,
|
verifier,
|
||||||
}: {
|
}: {
|
||||||
source: EM,
|
source: IPC,
|
||||||
channel: string | symbol,
|
channel: string,
|
||||||
listener: L,
|
listener: Listener,
|
||||||
verifier: ListVerifier<Rest<Parameters<L>>>,
|
verifier: ListVerifier<Rest<Parameters<Listener>>>,
|
||||||
}): void {
|
}): void {
|
||||||
function handler(event: HandlerEvent<EM>, ...args: unknown[]): void {
|
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]): void {
|
||||||
if (verifier(args)) {
|
if (verifier(args)) {
|
||||||
source.removeListener(channel, handler); // remove immediately
|
source.removeListener(channel, wrappedListener); // remove immediately
|
||||||
|
|
||||||
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
||||||
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
||||||
@ -58,7 +60,7 @@ export function onceCorrect<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.on(channel, handler);
|
source.on(channel, wrappedListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,25 +70,53 @@ export function onceCorrect<
|
|||||||
* @param verifier The function to be called to verify that the args are the correct type
|
* @param verifier The function to be called to verify that the args are the correct type
|
||||||
*/
|
*/
|
||||||
export function onCorrect<
|
export function onCorrect<
|
||||||
EM extends EventEmitter,
|
IPC extends EventEmitter,
|
||||||
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
Listener extends (event: ListenerEvent<IPC>, ...args: any[]) => any
|
||||||
>({
|
>({
|
||||||
source,
|
source,
|
||||||
channel,
|
channel,
|
||||||
listener,
|
listener,
|
||||||
verifier,
|
verifier,
|
||||||
}: {
|
}: {
|
||||||
source: EM,
|
source: IPC,
|
||||||
channel: string | symbol,
|
channel: string,
|
||||||
listener: L,
|
listener: Listener,
|
||||||
verifier: ListVerifier<Rest<Parameters<L>>>,
|
verifier: ListVerifier<Rest<Parameters<Listener>>>,
|
||||||
}): void {
|
}): Disposer {
|
||||||
source.on(channel, (event, ...args: unknown[]) => {
|
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) {
|
||||||
if (verifier(args)) {
|
if (verifier(args)) {
|
||||||
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
||||||
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
||||||
} else {
|
} else {
|
||||||
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
source.on(channel, wrappedListener);
|
||||||
|
|
||||||
|
return () => source.off(channel, wrappedListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleCorrect<
|
||||||
|
Handler extends (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any,
|
||||||
|
>({
|
||||||
|
channel,
|
||||||
|
handler,
|
||||||
|
verifier,
|
||||||
|
}: {
|
||||||
|
channel: string,
|
||||||
|
handler: Handler,
|
||||||
|
verifier: ListVerifier<Rest<Parameters<Handler>>>,
|
||||||
|
}): Disposer {
|
||||||
|
function wrappedHandler(event: Electron.IpcMainInvokeEvent, ...args: unknown[]): ReturnType<Handler> {
|
||||||
|
if (verifier(args)) {
|
||||||
|
return handler(event, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(`Invalid args for invoke on channel: ${channel}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(channel, wrappedHandler);
|
||||||
|
|
||||||
|
return () => ipcMain.removeHandler(channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import * as Util from "./utils";
|
|||||||
import * as ClusterFeature from "./cluster-feature";
|
import * as ClusterFeature from "./cluster-feature";
|
||||||
import * as Interface from "../interfaces";
|
import * as Interface from "../interfaces";
|
||||||
import * as Catalog from "./catalog";
|
import * as Catalog from "./catalog";
|
||||||
|
import * as Types from "./types";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
App,
|
App,
|
||||||
@ -39,5 +40,6 @@ export {
|
|||||||
ClusterFeature,
|
ClusterFeature,
|
||||||
Interface,
|
Interface,
|
||||||
Store,
|
Store,
|
||||||
|
Types,
|
||||||
Util,
|
Util,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,3 +20,5 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export { ExtensionStore } from "../extension-store";
|
export { ExtensionStore } from "../extension-store";
|
||||||
|
export { MainIpcStore } from "../main-ipc-store";
|
||||||
|
export { RendererIpcStore } from "../renderer-ipc-store";
|
||||||
|
|||||||
24
src/extensions/core-api/types.ts
Normal file
24
src/extensions/core-api/types.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type IpcMainInvokeEvent = Electron.IpcMainInvokeEvent;
|
||||||
|
export type IpcRendererEvent = Electron.IpcRendererEvent;
|
||||||
|
export type IpcMainEvent = Electron.IpcMainEvent;
|
||||||
39
src/extensions/ipc-store.ts
Normal file
39
src/extensions/ipc-store.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Singleton } from "../common/utils";
|
||||||
|
import { LensExtension } from "./lens-extension";
|
||||||
|
import { createHash } from "crypto";
|
||||||
|
import { broadcastMessage } from "../common/ipc";
|
||||||
|
|
||||||
|
export const IpcPrefix = Symbol();
|
||||||
|
|
||||||
|
export abstract class IpcStore extends Singleton {
|
||||||
|
readonly [IpcPrefix]: string;
|
||||||
|
|
||||||
|
constructor(protected extension: LensExtension) {
|
||||||
|
super();
|
||||||
|
this[IpcPrefix] = createHash("sha256").update(extension.id).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastIpc(channel: string, ...args: any[]): void {
|
||||||
|
broadcastMessage(`extensions@${this[IpcPrefix]}:${channel}`, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,7 +23,8 @@ import type { InstalledExtension } from "./extension-discovery";
|
|||||||
import { action, observable, reaction } from "mobx";
|
import { action, observable, reaction } from "mobx";
|
||||||
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { ProtocolHandlerRegistration } from "./registries/protocol-handler-registry";
|
import { ProtocolHandlerRegistration } from "./registries";
|
||||||
|
import { disposer } from "../common/utils";
|
||||||
|
|
||||||
export type LensExtensionId = string; // path to manifest (package.json)
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||||
@ -37,6 +38,8 @@ export interface LensExtensionManifest {
|
|||||||
lens?: object; // fixme: add more required fields for validation
|
lens?: object; // fixme: add more required fields for validation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Disposers = Symbol();
|
||||||
|
|
||||||
export class LensExtension {
|
export class LensExtension {
|
||||||
readonly id: LensExtensionId;
|
readonly id: LensExtensionId;
|
||||||
readonly manifest: LensExtensionManifest;
|
readonly manifest: LensExtensionManifest;
|
||||||
@ -46,6 +49,7 @@ export class LensExtension {
|
|||||||
protocolHandlers: ProtocolHandlerRegistration[] = [];
|
protocolHandlers: ProtocolHandlerRegistration[] = [];
|
||||||
|
|
||||||
@observable private isEnabled = false;
|
@observable private isEnabled = false;
|
||||||
|
[Disposers] = disposer();
|
||||||
|
|
||||||
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -62,6 +66,10 @@ export class LensExtension {
|
|||||||
return this.manifest.version;
|
return this.manifest.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.manifest.description;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getExtensionFileFolder returns the path to an already created folder. This
|
* getExtensionFileFolder returns the path to an already created folder. This
|
||||||
* folder is for the sole use of this extension.
|
* folder is for the sole use of this extension.
|
||||||
@ -73,15 +81,11 @@ export class LensExtension {
|
|||||||
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get description() {
|
|
||||||
return this.manifest.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async enable() {
|
async enable() {
|
||||||
if (this.isEnabled) return;
|
if (this.isEnabled) return;
|
||||||
this.isEnabled = true;
|
this.isEnabled = true;
|
||||||
this.onActivate();
|
this.onActivate?.();
|
||||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +93,8 @@ export class LensExtension {
|
|||||||
async disable() {
|
async disable() {
|
||||||
if (!this.isEnabled) return;
|
if (!this.isEnabled) return;
|
||||||
this.isEnabled = false;
|
this.isEnabled = false;
|
||||||
this.onDeactivate();
|
this.onDeactivate?.();
|
||||||
|
this[Disposers]();
|
||||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +130,12 @@ export class LensExtension {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onActivate() {
|
protected onActivate(): void {
|
||||||
// mock
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDeactivate() {
|
protected onDeactivate(): void {
|
||||||
// mock
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,12 +19,12 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { MenuRegistration } from "./registries/menu-registry";
|
|
||||||
import { LensExtension } from "./lens-extension";
|
import { LensExtension } from "./lens-extension";
|
||||||
import { WindowManager } from "../main/window-manager";
|
import { WindowManager } from "../main/window-manager";
|
||||||
import { getExtensionPageUrl } from "./registries/page-registry";
|
import { getExtensionPageUrl } from "./registries/page-registry";
|
||||||
import { CatalogEntity, catalogEntityRegistry } from "../common/catalog";
|
import { CatalogEntity, catalogEntityRegistry } from "../common/catalog";
|
||||||
import { IObservableArray } from "mobx";
|
import { IObservableArray } from "mobx";
|
||||||
|
import { MenuRegistration } from "./registries";
|
||||||
|
|
||||||
export class LensMainExtension extends LensExtension {
|
export class LensMainExtension extends LensExtension {
|
||||||
appMenus: MenuRegistration[] = [];
|
appMenus: MenuRegistration[] = [];
|
||||||
|
|||||||
45
src/extensions/main-ipc-store.ts
Normal file
45
src/extensions/main-ipc-store.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ipcMain } from "electron";
|
||||||
|
import { IpcPrefix, IpcStore } from "./ipc-store";
|
||||||
|
import { Disposers } from "./lens-extension";
|
||||||
|
import { LensMainExtension } from "./lens-main-extension";
|
||||||
|
|
||||||
|
export abstract class MainIpcStore extends IpcStore {
|
||||||
|
constructor(extension: LensMainExtension) {
|
||||||
|
super(extension);
|
||||||
|
extension[Disposers].push(() => MainIpcStore.resetInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIpc(channel: string, handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any): void {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
ipcMain.handle(prefixedChannel, handler);
|
||||||
|
this.extension[Disposers].push(() => ipcMain.removeHandler(prefixedChannel));
|
||||||
|
}
|
||||||
|
|
||||||
|
listenIpc(channel: string, listener: (event: Electron.IpcMainEvent, ...args: any[]) => any): void {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
ipcMain.addListener(prefixedChannel, listener);
|
||||||
|
this.extension[Disposers].push(() => ipcMain.removeListener(prefixedChannel, listener));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,3 +32,4 @@ export * from "./kube-object-status-registry";
|
|||||||
export * from "./command-registry";
|
export * from "./command-registry";
|
||||||
export * from "./entity-setting-registry";
|
export * from "./entity-setting-registry";
|
||||||
export * from "./welcome-menu-registry";
|
export * from "./welcome-menu-registry";
|
||||||
|
export * from "./protocol-handler-registry";
|
||||||
|
|||||||
44
src/extensions/renderer-ipc-store.ts
Normal file
44
src/extensions/renderer-ipc-store.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* 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 { IpcPrefix, IpcStore } from "./ipc-store";
|
||||||
|
import { Disposers } from "./lens-extension";
|
||||||
|
import { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
|
|
||||||
|
export abstract class RendererIpcStore extends IpcStore {
|
||||||
|
constructor(extension: LensRendererExtension) {
|
||||||
|
super(extension);
|
||||||
|
extension[Disposers].push(() => RendererIpcStore.resetInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
listenIpc(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): void {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
ipcRenderer.addListener(prefixedChannel, listener);
|
||||||
|
this.extension[Disposers].push(() => ipcRenderer.removeListener(prefixedChannel, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeIpc(channel: string, ...args: any[]): Promise<any> {
|
||||||
|
const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`;
|
||||||
|
|
||||||
|
return ipcRenderer.invoke(prefixedChannel, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user