diff --git a/src/common/channel/channel.test.ts b/src/common/channel/channel.test.ts index 72cf486e15..a1f8bb5a57 100644 --- a/src/common/channel/channel.test.ts +++ b/src/common/channel/channel.test.ts @@ -227,6 +227,75 @@ describe("channel", () => { }); }); }); + + describe("requesting from renderer in main, given listener for channel in a renderer and application has started", () => { + let testRequestChannel: TestRequestChannel; + let requestListenerInRendererMock: AsyncFnMock<(arg: string) => string>; + let rendererDi: DiContainer; + let mainDi: DiContainer; + let requestFromChannel: RequestFromChannel; + + beforeEach(async () => { + const applicationBuilder = getApplicationBuilder(); + + mainDi = applicationBuilder.dis.mainDi; + rendererDi = applicationBuilder.dis.rendererDi; + + requestListenerInRendererMock = asyncFn(); + + const testChannelListenerInRendererInjectable = getInjectable({ + id: "test-channel-listener-in-renderer", + + instantiate: (di) => ({ + channel: di.inject(testRequestChannelInjectable), + + handler: requestListenerInRendererMock, + }), + + injectionToken: requestChannelListenerInjectionToken, + }); + + rendererDi.register(testChannelListenerInRendererInjectable); + + // Notice how test channel has presence in both DIs, being from common + mainDi.register(testRequestChannelInjectable); + rendererDi.register(testRequestChannelInjectable); + + testRequestChannel = mainDi.inject(testRequestChannelInjectable); + + requestFromChannel = mainDi.inject( + requestFromChannelInjectionToken, + ); + + await applicationBuilder.render(); + }); + + describe("when requesting from channel", () => { + let actualPromise: Promise; + + beforeEach(() => { + actualPromise = requestFromChannel(testRequestChannel, "some-request"); + }); + + it("triggers listener in renderer", () => { + expect(requestListenerInRendererMock).toHaveBeenCalledWith("some-request"); + }); + + it("does not resolve yet", async () => { + const promiseStatus = await getPromiseStatus(actualPromise); + + expect(promiseStatus.fulfilled).toBe(false); + }); + + it("when renderer resolves with response, resolves with response", async () => { + await requestListenerInRendererMock.resolve("some-response"); + + const actual = await actualPromise; + + expect(actual).toBe("some-response"); + }); + }); + }); }); const testMessageChannelInjectable = getInjectable({ diff --git a/src/common/channel/request-from-channel-for-main/request-from-channel-for-main-request-channel.injectable.ts b/src/common/channel/request-from-channel-for-main/request-from-channel-for-main-request-channel.injectable.ts new file mode 100644 index 0000000000..5375eecb18 --- /dev/null +++ b/src/common/channel/request-from-channel-for-main/request-from-channel-for-main-request-channel.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MessageChannel } from "../message-channel-injection-token"; +import { messageChannelInjectionToken } from "../message-channel-injection-token"; + +export type RequestFromChannelForMainRequestChannel = MessageChannel<{ channelId: string; request: any }>; + +const requestFromChannelForMainRequestChannelInjectable = getInjectable({ + id: "request-from-channel-for-main-request-channel", + + instantiate: (): RequestFromChannelForMainRequestChannel => ({ + id: "request-from-channel-for-main-request-channel", + }), + + injectionToken: messageChannelInjectionToken, +}); + +export default requestFromChannelForMainRequestChannelInjectable; diff --git a/src/common/channel/request-from-channel-for-main/request-from-channel-for-main-response-channel.injectable.ts b/src/common/channel/request-from-channel-for-main/request-from-channel-for-main-response-channel.injectable.ts new file mode 100644 index 0000000000..38d88f6f1e --- /dev/null +++ b/src/common/channel/request-from-channel-for-main/request-from-channel-for-main-response-channel.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MessageChannel } from "../message-channel-injection-token"; +import { messageChannelInjectionToken } from "../message-channel-injection-token"; + +export type RequestFromChannelForMainResponseChannel = MessageChannel<{ channelId: string; response: any }>; + +const requestFromChannelForMainResponseChannelInjectable = getInjectable({ + id: "request-from-channel-for-main-response-channel", + + instantiate: (): RequestFromChannelForMainResponseChannel => ({ + id: "request-from-channel-for-main-response-channel", + }), + + injectionToken: messageChannelInjectionToken, +}); + +export default requestFromChannelForMainResponseChannelInjectable; diff --git a/src/main/channel/request-from-channel/request-from-channel-for-main-response-channel-listener.injectable.ts b/src/main/channel/request-from-channel/request-from-channel-for-main-response-channel-listener.injectable.ts new file mode 100644 index 0000000000..0359eeeb53 --- /dev/null +++ b/src/main/channel/request-from-channel/request-from-channel-for-main-response-channel-listener.injectable.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MessageChannelListener } from "../../../common/channel/message-channel-listener-injection-token"; +import { messageChannelListenerInjectionToken } from "../../../common/channel/message-channel-listener-injection-token"; +import type { RequestFromChannelForMainResponseChannel } from "../../../common/channel/request-from-channel-for-main/request-from-channel-for-main-response-channel.injectable"; +import requestFromChannelForMainResponseChannelInjectable from "../../../common/channel/request-from-channel-for-main/request-from-channel-for-main-response-channel.injectable"; +import requestFromChannelForMainResponsePromiseInjectable from "./request-from-channel-for-main-response-promise.injectable"; + +const requestFromChannelForMainResponseChannelListenerInjectable = + getInjectable({ + id: "request-from-channel-for-main-response-channel-listener", + + instantiate: ( + di, + ): MessageChannelListener => { + const channel = di.inject(requestFromChannelForMainResponseChannelInjectable); + const getResponsePromise = (channelId: string) => di.inject(requestFromChannelForMainResponsePromiseInjectable, channelId); + + return { + channel, + + handler: ({ channelId, response }) => { + const responsePromise = getResponsePromise(channelId); + + responsePromise.resolve(response); + }, + }; + }, + + injectionToken: messageChannelListenerInjectionToken, + }); + +export default requestFromChannelForMainResponseChannelListenerInjectable; diff --git a/src/main/channel/request-from-channel/request-from-channel-for-main-response-promise.injectable.ts b/src/main/channel/request-from-channel/request-from-channel-for-main-response-promise.injectable.ts new file mode 100644 index 0000000000..6e9b642f3f --- /dev/null +++ b/src/main/channel/request-from-channel/request-from-channel-for-main-response-promise.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; + +const requestFromChannelForMainResponsePromiseInjectable = getInjectable({ + id: "request-from-channel-for-main-response-promise", + + instantiate: (di, channelId: string) => { + void channelId; + + let resolve: (response: boolean) => void; + + const promise = new Promise(_resolve => { + resolve = _resolve; + }); + + return ({ + promise, + + resolve: (response: boolean) => { + resolve(response); + }, + }); + }, + + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, channelId: string) => channelId, + }), +}); + +export default requestFromChannelForMainResponsePromiseInjectable; diff --git a/src/main/channel/request-from-channel/request-from-channel.injectable.ts b/src/main/channel/request-from-channel/request-from-channel.injectable.ts new file mode 100644 index 0000000000..6401d6084c --- /dev/null +++ b/src/main/channel/request-from-channel/request-from-channel.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { requestFromChannelInjectionToken } from "../../../common/channel/request-from-channel-injection-token"; +import { messageToChannelInjectionToken } from "../../../common/channel/message-to-channel-injection-token"; +import requestFromChannelForMainRequestChannelInjectable from "../../../common/channel/request-from-channel-for-main/request-from-channel-for-main-request-channel.injectable"; +import requestFromChannelForMainResponsePromiseInjectable from "./request-from-channel-for-main-response-promise.injectable"; + +const requestFromChannelInjectable = getInjectable({ + id: "request-from-channel", + + instantiate: (di) => { + const messageToChannel = di.inject(messageToChannelInjectionToken); + const requestFromChannelForMainMessageChannel = di.inject(requestFromChannelForMainRequestChannelInjectable); + const getResponsePromise = (channelId: string) => di.inject(requestFromChannelForMainResponsePromiseInjectable, channelId); + + return async (channel, ...[request]) => { + const responsePromise = getResponsePromise(channel.id); + + messageToChannel(requestFromChannelForMainMessageChannel, { channelId: channel.id, request }); + + return await responsePromise.promise; + + }; + }, + + injectionToken: requestFromChannelInjectionToken, +}); + +export default requestFromChannelInjectable; diff --git a/src/renderer/channel/request-from-channel-for-main/request-from-channel-for-main-message-channel-listener.injectable.ts b/src/renderer/channel/request-from-channel-for-main/request-from-channel-for-main-message-channel-listener.injectable.ts new file mode 100644 index 0000000000..ebd05ce936 --- /dev/null +++ b/src/renderer/channel/request-from-channel-for-main/request-from-channel-for-main-message-channel-listener.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MessageChannelListener } from "../../../common/channel/message-channel-listener-injection-token"; +import { messageChannelListenerInjectionToken } from "../../../common/channel/message-channel-listener-injection-token"; +import type { RequestFromChannelForMainRequestChannel } from "../../../common/channel/request-from-channel-for-main/request-from-channel-for-main-request-channel.injectable"; +import requestFromChannelForMainRequestChannelInjectable from "../../../common/channel/request-from-channel-for-main/request-from-channel-for-main-request-channel.injectable"; +import { requestChannelListenerInjectionToken } from "../../../common/channel/request-channel-listener-injection-token"; +import requestFromChannelForMainResponseChannelInjectable from "../../../common/channel/request-from-channel-for-main/request-from-channel-for-main-response-channel.injectable"; +import { messageToChannelInjectionToken } from "../../../common/channel/message-to-channel-injection-token"; + +const requestFromChannelForMainMessageChannelListenerInjectable = getInjectable( + { + id: "request-from-channel-for-main-message-channel-listener", + + instantiate: (di): MessageChannelListener => { + const requestChannel = di.inject(requestFromChannelForMainRequestChannelInjectable); + const responseChannel = di.inject(requestFromChannelForMainResponseChannelInjectable); + const messageToChannel = di.inject(messageToChannelInjectionToken); + const listeners = di.injectMany(requestChannelListenerInjectionToken); + + return { + channel: requestChannel, + + handler: async ({ channelId, request }) => { + const targetListener = listeners.find(listener => listener.channel.id === channelId); + + if (targetListener) { + const response = await targetListener.handler(request); + + messageToChannel(responseChannel, { + channelId, + response, + }); + } + }, + }; + }, + + injectionToken: messageChannelListenerInjectionToken, + }, +); + +export default requestFromChannelForMainMessageChannelListenerInjectable;