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

Switch to pushing the disposer in the methods

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-05-14 15:09:23 -04:00
parent 5e1af1be2b
commit 7a93d8f5c4
5 changed files with 75 additions and 60 deletions

View File

@ -1,6 +1,6 @@
# Inter Process Communication
A Lens Extension can utilise IPC to send information between its `LensRendererExtension` and its `LensMainExtension`.
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.
@ -29,7 +29,7 @@ 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` and respond this this kind of request.
Only `LensRendererExtension` can initiate this kind of request, and only `LensMainExtension` can and respond this this kind of request.
## Registering IPC Handlers and Listeners
@ -40,38 +40,46 @@ The general terminology is as follows:
To register either a handler or a listener, you should do something like the following:
File1:
```typescript
import { LensMainExtension, Interface, Types } from "@k8slens/extensions";
export class ExampleExtensionMain extends LensMainExtension {
onActivate() {
this.disposers.push(
this.listenIpc({
channel: "initialize",
listener: this.initializeListener,
verifier: this.initializeVerifier,
})
);
this.listenIpc({
channel: "initialize",
listener: this.initializeListener,
verifier: this.initializeVerifier,
});
}
initializeListener = (event: Types.IpcMainEvent, uid: string) => {
console.log(`starting to initialize: ${uid}`);
initializeListener = (event: Types.IpcMainEvent, id: string) => {
console.log(`starting to initialize: ${id}`);
};
initializeVerifier = (args: unknown[]): args is [uid: string] => {
initializeVerifier = (args: unknown[]): args is [id: string] => {
return args.length === 1 && typeof args[0] === "string";
}
}
```
File2:
```typescript
import { LensMainExtension, Interface, Types } from "@k8slens/extensions";
export class ExampleExtensionRenderer extends LensRendererExtension {
onActivate() {
setTimeout(() => this.sendIpc("initialize", "an-id"), 5000);
}
}
```
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 `this.handleIpc(...)` instead.
The `LensExtension.prototype.disopsers` is a list of `() => void`'s (or `Utils.Disposer`'s).
Those functions are sort of like destructors in that they should clean up some state.
Some functions return them to indicate that it will clean up the state which was just registered.
As an extension developer you do not need to run them yourself.
Lens will run them when your extension gets deactivated or uninstalled.
The cleanup of these handlers is handled by Lens itself.
### Note about verification
@ -84,6 +92,11 @@ Your handler or listener will not be called if it fails this validation.
Instead an error or log message will occur.
This should help with debugging because you are notified immediately that there is a mismatch between what you are expecting and what was sent.
### 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.

View File

@ -42,35 +42,33 @@ function getSubFrames(): ClusterFrameInfo[] {
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();
if (!views) return;
if (ipcRenderer) {
ipcRenderer.send(channel, ...args);
} else if (ipcMain) {
ipcMain.emit(channel, ...args);
}
ipcRenderer?.send(channel, ...args);
ipcMain?.emit(channel, ...args);
for (const view of views) {
const type = view.getType();
const subFramesP = ipcRenderer
? requestMain(subFramesChannel)
: Promise.resolve(getSubFrames());
logger.silly(`[IPC]: broadcasting "${channel}" to ${type}=${view.id}`, { args });
view.send(channel, ...args);
subFramesP
.then(subFrames => {
for (const view of views) {
try {
logger.silly(`[IPC]: broadcasting "${channel}" to ${view.getType()}=${view.id}`, { args });
view.send(channel, ...args);
try {
const subFrames: ClusterFrameInfo[] = ipcRenderer
? await requestMain(subFramesChannel)
: getSubFrames();
for (const frameInfo of subFrames) {
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
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) });
}
}
} catch (error) {
logger.error("[IPC]: failed to send IPC message", { error: String(error) });
}
}
});
}
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {

View File

@ -52,7 +52,7 @@ export class LensExtension {
protocolHandlers: ProtocolHandlerRegistration[] = [];
@observable private isEnabled = false;
disposers = disposer();
protected disposers = disposer();
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
this.id = id;

View File

@ -25,7 +25,6 @@ import { getExtensionPageUrl } from "./registries/page-registry";
import { CatalogEntity, catalogEntityRegistry } from "../common/catalog";
import { IObservableArray } from "mobx";
import { IpcHandlerRegistration, MenuRegistration } from "./registries";
import type { Disposer } from "../common/utils";
import { handleCorrect, ListenerEvent, ListVerifier, onCorrect, Rest } from "../common/ipc";
import { ipcMain, IpcMain } from "electron";
@ -53,12 +52,14 @@ export class LensMainExtension extends LensExtension {
handleIpc<
Handler extends (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any,
>({ channel, ...reg }: IpcHandlerRegistration<Handler>): Disposer {
>({ channel, ...reg }: IpcHandlerRegistration<Handler>): void {
// This is to have a uniform length prefix so that two extensions cannot talk to each other's IPCs accidentally
return handleCorrect({
channel: `extensions@${this[IpcPrefix]}:${channel}`,
...reg,
});
this.disposers.push(
handleCorrect({
channel: `extensions@${this[IpcPrefix]}:${channel}`,
...reg,
})
);
}
listenIpc<
@ -70,11 +71,13 @@ export class LensMainExtension extends LensExtension {
channel: string,
listener: Listener,
verifier: ListVerifier<Rest<Parameters<Listener>>>,
}): Disposer {
return onCorrect({
source: ipcMain,
channel: `extensions@${this[IpcPrefix]}:${channel}`,
...reg,
});
}): void {
this.disposers.push(
onCorrect({
source: ipcMain,
channel: `extensions@${this[IpcPrefix]}:${channel}`,
...reg,
})
);
}
}

View File

@ -30,7 +30,6 @@ import { CommandRegistration } from "./registries/command-registry";
import { EntitySettingRegistration } from "./registries/entity-setting-registry";
import { ipcRenderer, IpcRenderer } from "electron";
import { ListenerEvent, ListVerifier, onCorrect, Rest } from "../common/ipc";
import { Disposer } from "../common/utils";
export class LensRendererExtension extends LensExtension {
globalPages: PageRegistration[] = [];
@ -73,12 +72,14 @@ export class LensRendererExtension extends LensExtension {
channel: string,
listener: Listener,
verifier: ListVerifier<Rest<Parameters<Listener>>>,
}): Disposer {
return onCorrect({
source: ipcRenderer,
channel: `extensions@${this[IpcPrefix]}:${channel}`,
...reg,
});
}): void {
this.disposers.push(
onCorrect({
source: ipcRenderer,
channel: `extensions@${this[IpcPrefix]}:${channel}`,
...reg,
})
);
}
invokeIpc(channel: string, ...args: any[]): Promise<any> {