From 42bd808e43a22b23658dceac2eb2ee80990ce835 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Mon, 20 Mar 2023 15:35:49 +0200 Subject: [PATCH] Re-enable communicating from main to cluster frames Signed-off-by: Janne Savolainen --- ...essage-channel-listener-injection-token.ts | 4 +- .../get-message-bridge-fake.test.ts | 28 ++++- .../get-message-bridge-fake.ts | 6 +- ...ist-message-channel-listener.injectable.ts | 4 +- .../enlist-message-channel-listener.test.ts | 16 +-- ...allow-communication-listener.injectable.ts | 21 ++++ .../frameIds.injectable.ts | 8 ++ ...send-message-to-channel.injectable.test.ts | 102 ++++++++++++------ .../send-message-to-channel.injectable.ts | 16 ++- ...llow-communication-to-iframe.injectable.ts | 25 +++++ .../src/allow-communication-to-iframe.test.ts | 35 ++++++ 11 files changed, 211 insertions(+), 54 deletions(-) create mode 100644 packages/technical-features/messaging/main/src/send-message-to-channel/allow-communication-listener.injectable.ts create mode 100644 packages/technical-features/messaging/main/src/send-message-to-channel/frameIds.injectable.ts create mode 100644 packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.injectable.ts create mode 100644 packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.test.ts diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts index 6343d281a7..386c9f9e5e 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/message/message-channel-listener-injection-token.ts @@ -6,8 +6,10 @@ export interface MessageChannel { _messageSignature?: Message; } +export type ExtraData = { processId: number; frameId: number }; + export type MessageChannelHandler = Channel extends MessageChannel - ? (message: Message) => void + ? (message: Message, data: ExtraData) => void : never; export interface MessageChannelListener { diff --git a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts index d439248b9d..17e148aa9b 100644 --- a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts +++ b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.test.ts @@ -162,6 +162,10 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = { it("the response gets handled in di-1", () => { expect(someHandler1MockInDi1).toHaveBeenCalledWith( "some-response-to: some-message", + { + frameId: 42, + processId: 42, + }, ); }); @@ -188,6 +192,10 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = { it("the response gets handled in di-1", () => { expect(someHandler1MockInDi1).toHaveBeenCalledWith( "some-response-to: some-message", + { + frameId: 42, + processId: 42, + }, ); }); }); @@ -224,9 +232,15 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = { }); it("listeners in other than sending DIs handle the message", () => { - expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message"); + expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message", { + frameId: 42, + processId: 42, + }); - expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message"); + expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message", { + frameId: 42, + processId: 42, + }); }); }); @@ -245,9 +259,15 @@ const someRequestChannelWithoutListeners: SomeRequestChannel = { }); it("listeners still handle the message", () => { - expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message"); + expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message", { + frameId: 42, + processId: 42, + }); - expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message"); + expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message", { + frameId: 42, + processId: 42, + }); }); }); }); diff --git a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts index b0817f11e2..cf5d2f93c2 100644 --- a/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts +++ b/packages/technical-features/messaging/agnostic/src/features/unit-testing/get-message-bridge-fake/get-message-bridge-fake.ts @@ -54,12 +54,12 @@ const overrideMessaging = ({ const resolvableHandlePromise = asyncFn(); resolvableHandlePromise().then(() => { - handlersForChannel.forEach((handler) => handler(message)); + handlersForChannel.forEach((handler) => handler(message, { frameId: 42, processId: 42 })); }); messagePropagationBuffer.add(resolvableHandlePromise); } else { - handlersForChannel.forEach((handler) => handler(message)); + handlersForChannel.forEach((handler) => handler(message, { frameId: 42, processId: 42 })); } }); }); @@ -85,7 +85,7 @@ const overrideRequesting = ({ }: { di: DiContainer; - requestListenersByDi: Map>>>; + requestListenersByDi: Map>>>; }) => { const requestHandlersByChannel = new Map>>(); diff --git a/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts b/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts index 94b155a006..5a33ed39d9 100644 --- a/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts +++ b/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts @@ -10,8 +10,8 @@ const enlistMessageChannelListenerInjectable = getInjectable({ const ipcMain = di.inject(ipcMainInjectable); return ({ channel, handler }) => { - const nativeOnCallback = (_: IpcMainEvent, message: unknown) => { - handler(message); + const nativeOnCallback = (nativeEvent: IpcMainEvent, message: unknown) => { + handler(message, { frameId: nativeEvent.frameId, processId: nativeEvent.processId }); }; ipcMain.on(channel.id, nativeOnCallback); diff --git a/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts b/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts index 5b4f31be37..c4385246cd 100644 --- a/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts +++ b/packages/technical-features/messaging/main/src/channel-listeners/enlist-message-channel-listener.test.ts @@ -60,11 +60,11 @@ describe("enlist message channel listener in main", () => { describe("when message arrives", () => { beforeEach(() => { - onMock.mock.calls[0][1]({} as IpcMainEvent, "some-message"); + onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, "some-message"); }); it("calls the handler with the message", () => { - expect(handlerMock).toHaveBeenCalledWith("some-message"); + expect(handlerMock).toHaveBeenCalledWith("some-message", { frameId: 42, processId: 84 }); }); it("when disposing the listener, de-registers the listener", () => { @@ -75,21 +75,21 @@ describe("enlist message channel listener in main", () => { }); it("given number as message, when message arrives, calls the handler with the message", () => { - onMock.mock.calls[0][1]({} as IpcMainEvent, 42); + onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, 42); - expect(handlerMock).toHaveBeenCalledWith(42); + expect(handlerMock).toHaveBeenCalledWith(42, { frameId: 42, processId: 84 }); }); it("given boolean as message, when message arrives, calls the handler with the message", () => { - onMock.mock.calls[0][1]({} as IpcMainEvent, true); + onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, true); - expect(handlerMock).toHaveBeenCalledWith(true); + expect(handlerMock).toHaveBeenCalledWith(true, { frameId: 42, processId: 84 }); }); it("given object as message, when message arrives, calls the handler with the message", () => { - onMock.mock.calls[0][1]({} as IpcMainEvent, { some: "object" }); + onMock.mock.calls[0][1]({ frameId: 42, processId: 84 } as IpcMainEvent, { some: "object" }); - expect(handlerMock).toHaveBeenCalledWith({ some: "object" }); + expect(handlerMock).toHaveBeenCalledWith({ some: "object" }, { frameId: 42, processId: 84 }); }); }); }); diff --git a/packages/technical-features/messaging/main/src/send-message-to-channel/allow-communication-listener.injectable.ts b/packages/technical-features/messaging/main/src/send-message-to-channel/allow-communication-listener.injectable.ts new file mode 100644 index 0000000000..af74d7a810 --- /dev/null +++ b/packages/technical-features/messaging/main/src/send-message-to-channel/allow-communication-listener.injectable.ts @@ -0,0 +1,21 @@ +import { getMessageChannel, getMessageChannelListenerInjectable } from "@k8slens/messaging"; +import frameIdsInjectable from "./frameIds.injectable"; + +const frameCommunicationAdminChannel = getMessageChannel( + "frame-communication-admin-channel", +); + +const allowCommunicationListenerInjectable = getMessageChannelListenerInjectable({ + id: "allow-communication", + channel: frameCommunicationAdminChannel, + + getHandler: (di) => { + const frameIds = di.inject(frameIdsInjectable); + + return (_, { frameId, processId }) => { + frameIds.add({ frameId, processId }); + }; + }, +}); + +export default allowCommunicationListenerInjectable; diff --git a/packages/technical-features/messaging/main/src/send-message-to-channel/frameIds.injectable.ts b/packages/technical-features/messaging/main/src/send-message-to-channel/frameIds.injectable.ts new file mode 100644 index 0000000000..6dff45afc2 --- /dev/null +++ b/packages/technical-features/messaging/main/src/send-message-to-channel/frameIds.injectable.ts @@ -0,0 +1,8 @@ +import { getInjectable } from "@ogre-tools/injectable"; + +const frameIdsInjectable = getInjectable({ + id: "frame-ids", + instantiate: () => new Set<{ frameId: number; processId: number }>(), +}); + +export default frameIdsInjectable; diff --git a/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts b/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts index 3c4d26c57f..5c5a486658 100644 --- a/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts +++ b/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.test.ts @@ -4,6 +4,7 @@ import { messagingFeatureForMain } from "../feature"; import { getMessageChannel, sendMessageToChannelInjectionToken } from "@k8slens/messaging"; import getWebContentsInjectable from "./get-web-contents.injectable"; import type { WebContents } from "electron"; +import allowCommunicationListenerInjectable from "./allow-communication-listener.injectable"; const someChannel = getMessageChannel("some-channel"); @@ -24,51 +25,88 @@ describe("send-message-to-channel", () => { expect(() => sendMessageToChannel(someChannel, "some-message")).not.toThrow(); }); - it("given multiple web contents, when sending a message, sends message to all web contents", () => { - const sendToWebContentsMock = jest.fn(); + describe("given web content that is alive", () => { + let sendToFrameMock: jest.Mock; + let sendMessageMock: jest.Mock; - di.override(getWebContentsInjectable, () => () => [ - { - send: (...args: any[]) => sendToWebContentsMock("some-web-content", ...args), - isDestroyed: () => false, - isCrashed: () => false, - } as unknown as WebContents, + beforeEach(() => { + sendToFrameMock = jest.fn(); + sendMessageMock = jest.fn(); - { - send: (...args: any[]) => sendToWebContentsMock("some-other-web-content", ...args), - isDestroyed: () => false, - isCrashed: () => false, - } as unknown as WebContents, - ]); + di.override(getWebContentsInjectable, () => () => [ + { + send: (...args: any[]) => sendMessageMock("first", ...args), + sendToFrame: (...args: any[]) => sendToFrameMock("first", ...args), + isDestroyed: () => false, + isCrashed: () => false, + } as unknown as WebContents, + { + send: (...args: any[]) => sendMessageMock("second", ...args), + sendToFrame: (...args: any[]) => sendToFrameMock("second", ...args), + isDestroyed: () => false, + isCrashed: () => false, + } as unknown as WebContents, + ]); + }); - const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); + it("when sending message, sends the message to webcontents", () => { + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); - sendMessageToChannel(someChannel, "some-message"); + sendMessageToChannel(someChannel, "some-message"); - expect(sendToWebContentsMock.mock.calls).toEqual([ - ["some-web-content", "some-channel", "some-message"], - ["some-other-web-content", "some-channel", "some-message"], - ]); + expect(sendMessageMock.mock.calls).toEqual([ + ["first", "some-channel", "some-message"], + ["second", "some-channel", "some-message"], + ]); + }); + + describe("when multiple renderers inform that they are ready to listen messages", () => { + beforeEach(() => { + const allowCommunicationListener = di.inject(allowCommunicationListenerInjectable); + + allowCommunicationListener.handler(undefined, { frameId: 42, processId: 126 }); + allowCommunicationListener.handler(undefined, { frameId: 84, processId: 168 }); + }); + + describe("when sending a message", () => { + beforeEach(() => { + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); + + sendMessageToChannel(someChannel, "some-message"); + }); + + it("sends the message to webcontents", () => { + expect(sendMessageMock.mock.calls).toEqual([ + ["first", "some-channel", "some-message"], + ["second", "some-channel", "some-message"], + ]); + }); + + it("sends the message to individual frames in webcontents", () => { + expect(sendToFrameMock.mock.calls).toEqual([ + ["first", [42, 126], "some-channel", "some-message"], + ["first", [84, 168], "some-channel", "some-message"], + + ["second", [42, 126], "some-channel", "some-message"], + ["second", [84, 168], "some-channel", "some-message"], + ]); + }); + }); + }); }); - it("given non alive web content, when sending a message, sends message to all web contents being alive", () => { + it("given non alive web contents, when sending a message, does not send messages", () => { const sendToWebContentsMock = jest.fn(); di.override(getWebContentsInjectable, () => () => [ { - send: (...args: any[]) => sendToWebContentsMock("some-alive-content", ...args), - isDestroyed: () => false, - isCrashed: () => false, - } as unknown as WebContents, - - { - send: (...args: any[]) => sendToWebContentsMock("destroyed-web-content", ...args), + send: sendToWebContentsMock, isDestroyed: () => true, isCrashed: () => false, } as unknown as WebContents, { - send: (...args: any[]) => sendToWebContentsMock("crashed-web-content", ...args), + send: sendToWebContentsMock, isDestroyed: () => false, isCrashed: () => true, } as unknown as WebContents, @@ -76,10 +114,8 @@ describe("send-message-to-channel", () => { const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); - sendMessageToChannel(someChannel, "some-message"); + sendMessageToChannel(someChannel, "irrelevant"); - expect(sendToWebContentsMock.mock.calls).toEqual([ - ["some-alive-content", "some-channel", "some-message"], - ]); + expect(sendToWebContentsMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.ts b/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.ts index a95fc3742b..e68d8e1577 100644 --- a/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.ts +++ b/packages/technical-features/messaging/main/src/send-message-to-channel/send-message-to-channel.injectable.ts @@ -2,8 +2,9 @@ import { getInjectable } from "@ogre-tools/injectable"; import { pipeline } from "@ogre-tools/fp"; import { SendMessageToChannel, sendMessageToChannelInjectionToken } from "@k8slens/messaging"; import getWebContentsInjectable from "./get-web-contents.injectable"; -import { reject } from "lodash/fp"; +import { flatMap, reject } from "lodash/fp"; import type { WebContents } from "electron"; +import frameIdsInjectable from "./frameIds.injectable"; const isDestroyed = (webContent: WebContents) => webContent.isDestroyed(); const isCrashed = (webContent: WebContents) => webContent.isCrashed(); @@ -18,6 +19,7 @@ const sendMessageToChannelInjectable = getInjectable({ instantiate: (di) => { const getWebContents = di.inject(getWebContentsInjectable); + const frameIds = di.inject(frameIdsInjectable); return ((channel, message) => { pipeline( @@ -25,8 +27,16 @@ const sendMessageToChannelInjectable = getInjectable({ reject(isDestroyed), reject(isCrashed), - forEach((webContent) => { - webContent.send(channel.id, message); + flatMap((webContent) => [ + (channelId: string, ...args: any[]) => webContent.send(channelId, ...args), + + ...[...frameIds].map(({ frameId, processId }) => (channelId: string, ...args: any[]) => { + webContent.sendToFrame([frameId, processId], channelId, ...args); + }), + ]), + + forEach((send) => { + send(channel.id, message); }), ); }) as SendMessageToChannel; diff --git a/packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.injectable.ts b/packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.injectable.ts new file mode 100644 index 0000000000..59abe7f931 --- /dev/null +++ b/packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.injectable.ts @@ -0,0 +1,25 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; +import { getMessageChannel, sendMessageToChannelInjectionToken } from "@k8slens/messaging"; + +export const frameCommunicationAdminChannel = getMessageChannel( + "frame-communication-admin-channel", +); + +const allowCommunicationToIframeInjectable = getInjectable({ + id: "allow-communication-to-iframe-injectable", + + instantiate: (di) => { + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); + + return { + run: () => { + sendMessageToChannel(frameCommunicationAdminChannel); + }, + }; + }, + + injectionToken: onLoadOfApplicationInjectionToken, +}); + +export default allowCommunicationToIframeInjectable; diff --git a/packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.test.ts b/packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.test.ts new file mode 100644 index 0000000000..990c6601d9 --- /dev/null +++ b/packages/technical-features/messaging/renderer/src/allow-communication-to-iframe.test.ts @@ -0,0 +1,35 @@ +import { createContainer, DiContainer } from "@ogre-tools/injectable"; +import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; +import { startApplicationInjectionToken } from "@k8slens/application"; +import { registerFeature } from "@k8slens/feature-core"; +import { messagingFeatureForRenderer } from "./feature"; +import { runInAction } from "mobx"; +import ipcRendererInjectable from "./ipc/ipc-renderer.injectable"; +import { sendMessageToChannelInjectionToken } from "@k8slens/messaging"; +import { frameCommunicationAdminChannel } from "./allow-communication-to-iframe.injectable"; + +describe("allow communication to iframe", () => { + let di: DiContainer; + let sendMessageToChannelMock: jest.Mock; + + beforeEach(() => { + di = createContainer("irrelevant"); + + registerMobX(di); + + runInAction(() => { + registerFeature(di, messagingFeatureForRenderer); + }); + + di.override(ipcRendererInjectable, () => ({ on: () => {} } as unknown)); + + sendMessageToChannelMock = jest.fn(); + di.override(sendMessageToChannelInjectionToken, () => sendMessageToChannelMock); + }); + + it("when application starts, sends message to communication channel to register the frame ID and process ID for further usage", async () => { + await di.inject(startApplicationInjectionToken)(); + + expect(sendMessageToChannelMock).toHaveBeenCalledWith(frameCommunicationAdminChannel); + }); +});