mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce competition for channel abstraction
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
bb2e8fbf65
commit
18b1f6df35
13
src/common/sync-box/channel/channel-injection-token.ts
Normal file
13
src/common/sync-box/channel/channel-injection-token.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export interface Channel {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const channelInjectionToken = getInjectionToken<Channel>({
|
||||
id: "channel",
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export interface ChannelListener {
|
||||
channel: any;
|
||||
handler: (value: any) => void;
|
||||
}
|
||||
|
||||
export const channelListenerInjectionToken = getInjectionToken<ChannelListener>(
|
||||
{
|
||||
id: "channel-listener",
|
||||
},
|
||||
);
|
||||
187
src/common/sync-box/channel/channel.test.ts
Normal file
187
src/common/sync-box/channel/channel.test.ts
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/lens-window-injection-token";
|
||||
import { lensWindowInjectionToken } from "../../../main/start-main-application/lens-window/application-window/lens-window-injection-token";
|
||||
import { sendToAgnosticChannelInjectionToken } from "./send-to-agnostic-channel-injection-token";
|
||||
import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
||||
import { channelListenerInjectionToken } from "./channel-listener-injection-token";
|
||||
import createLensWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable";
|
||||
import type { Channel } from "./channel-injection-token";
|
||||
import { channelInjectionToken } from "./channel-injection-token";
|
||||
|
||||
describe("channel", () => {
|
||||
describe("messaging from main to renderer, given listener for channel in a window and application has started", () => {
|
||||
let testChannel: Channel;
|
||||
let testListenerInWindowMock: jest.Mock;
|
||||
let mainDi: DiContainer;
|
||||
let sendToAgnosticChannel: (channel: Channel, message: any) => void;
|
||||
|
||||
beforeEach(async () => {
|
||||
const applicationBuilder = getApplicationBuilder();
|
||||
|
||||
mainDi = applicationBuilder.dis.mainDi;
|
||||
const rendererDi = applicationBuilder.dis.rendererDi;
|
||||
|
||||
testListenerInWindowMock = jest.fn();
|
||||
|
||||
const testChannelListenerInTestWindowInjectable = getInjectable({
|
||||
id: "test-channel-listener-in-test-window",
|
||||
|
||||
instantiate: (di) => ({
|
||||
channel: di.inject(testChannelInjectable),
|
||||
|
||||
handler: testListenerInWindowMock,
|
||||
}),
|
||||
|
||||
injectionToken: channelListenerInjectionToken,
|
||||
});
|
||||
|
||||
rendererDi.register(testChannelListenerInTestWindowInjectable);
|
||||
|
||||
// Notice how test channel has presence in both DIs, being from common
|
||||
mainDi.register(testChannelInjectable);
|
||||
rendererDi.register(testChannelInjectable);
|
||||
|
||||
testChannel = mainDi.inject(testChannelInjectable);
|
||||
|
||||
sendToAgnosticChannel = mainDi.inject(
|
||||
sendToAgnosticChannelInjectionToken,
|
||||
);
|
||||
|
||||
await applicationBuilder.render();
|
||||
});
|
||||
|
||||
describe("given window is shown", () => {
|
||||
let someWindowFake: LensWindow;
|
||||
|
||||
beforeEach(async () => {
|
||||
someWindowFake = createTestWindow(mainDi, "some-window");
|
||||
|
||||
await someWindowFake.show();
|
||||
});
|
||||
|
||||
it("when sending message, triggers listener in window", () => {
|
||||
sendToAgnosticChannel(testChannel, "some-message");
|
||||
|
||||
expect(testListenerInWindowMock).toHaveBeenCalledWith("some-message");
|
||||
});
|
||||
|
||||
it("given window is hidden, when sending message, does not trigger listener in window", () => {
|
||||
someWindowFake.close();
|
||||
|
||||
sendToAgnosticChannel(testChannel, "some-message");
|
||||
|
||||
expect(testListenerInWindowMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("given multiple shown windows, when sending message, triggers listeners in all windows", async () => {
|
||||
const someWindowFake = createTestWindow(mainDi, "some-window");
|
||||
const someOtherWindowFake = createTestWindow(mainDi, "some-other-window");
|
||||
|
||||
await someWindowFake.show();
|
||||
await someOtherWindowFake.show();
|
||||
|
||||
sendToAgnosticChannel(testChannel, "some-message");
|
||||
|
||||
expect(testListenerInWindowMock.mock.calls).toEqual([
|
||||
["some-message"],
|
||||
["some-message"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("messaging from renderer to main, given listener for channel in a main and application has started", () => {
|
||||
let testChannel: Channel;
|
||||
let testListenerInMainMock: jest.Mock;
|
||||
let rendererDi: DiContainer;
|
||||
let mainDi: DiContainer;
|
||||
let sendToAgnosticChannel: (channel: Channel, message: any) => void;
|
||||
|
||||
beforeEach(async () => {
|
||||
const applicationBuilder = getApplicationBuilder();
|
||||
|
||||
mainDi = applicationBuilder.dis.mainDi;
|
||||
rendererDi = applicationBuilder.dis.rendererDi;
|
||||
|
||||
testListenerInMainMock = jest.fn();
|
||||
|
||||
const testChannelListenerInMainInjectable = getInjectable({
|
||||
id: "test-channel-listener-in-main",
|
||||
|
||||
instantiate: (di) => ({
|
||||
channel: di.inject(testChannelInjectable),
|
||||
|
||||
handler: testListenerInMainMock,
|
||||
}),
|
||||
|
||||
injectionToken: channelListenerInjectionToken,
|
||||
});
|
||||
|
||||
mainDi.register(testChannelListenerInMainInjectable);
|
||||
|
||||
// Notice how test channel has presence in both DIs, being from common
|
||||
mainDi.register(testChannelInjectable);
|
||||
rendererDi.register(testChannelInjectable);
|
||||
|
||||
testChannel = rendererDi.inject(testChannelInjectable);
|
||||
|
||||
sendToAgnosticChannel = rendererDi.inject(
|
||||
sendToAgnosticChannelInjectionToken,
|
||||
);
|
||||
|
||||
await applicationBuilder.render();
|
||||
});
|
||||
|
||||
it("when sending message, triggers listener in main", () => {
|
||||
sendToAgnosticChannel(testChannel, "some-message");
|
||||
|
||||
expect(testListenerInMainMock).toHaveBeenCalledWith("some-message");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const testChannelInjectable = getInjectable({
|
||||
id: "some-test-channel",
|
||||
|
||||
instantiate: () => {
|
||||
const channelId = "some-channel-id";
|
||||
|
||||
return {
|
||||
id: channelId,
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: channelInjectionToken,
|
||||
});
|
||||
|
||||
const createTestWindow = (di: DiContainer, id: string) => {
|
||||
const testWindowInjectable = getInjectable({
|
||||
id,
|
||||
|
||||
instantiate: (di) => {
|
||||
const createLensWindow = di.inject(createLensWindowInjectable);
|
||||
|
||||
return createLensWindow({
|
||||
id,
|
||||
title: "Some test window",
|
||||
defaultHeight: 42,
|
||||
defaultWidth: 42,
|
||||
getContentUrl: () => "some-content-url",
|
||||
resizable: true,
|
||||
windowFrameUtilitiesAreShown: false,
|
||||
centered: false,
|
||||
});
|
||||
},
|
||||
|
||||
injectionToken: lensWindowInjectionToken,
|
||||
});
|
||||
|
||||
di.register(testWindowInjectable);
|
||||
|
||||
return di.inject(testWindowInjectable);
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export const enlistChannelListenerInjectionToken = getInjectionToken<
|
||||
(channel: any, handler: any) => () => void
|
||||
>({
|
||||
id: "enlist-channel-listener",
|
||||
});
|
||||
@ -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 { getStartableStoppable } from "../../utils/get-startable-stoppable";
|
||||
import { channelListenerInjectionToken } from "./channel-listener-injection-token";
|
||||
import { enlistChannelListenerInjectionToken } from "./enlist-channel-listener-injection-token";
|
||||
|
||||
const listeningOfChannelsInjectable = getInjectable({
|
||||
id: "listening-of-channels",
|
||||
|
||||
instantiate: (di) => {
|
||||
const enlistChannelListener = di.inject(enlistChannelListenerInjectionToken);
|
||||
const channelListeners = di.injectMany(channelListenerInjectionToken);
|
||||
|
||||
return getStartableStoppable("listening-of-channels", () => {
|
||||
const disposers = channelListeners.map(({ channel, handler }) =>
|
||||
enlistChannelListener(channel, handler),
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposers.forEach((disposer) => {
|
||||
disposer();
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default listeningOfChannelsInjectable;
|
||||
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { Channel } from "./channel-injection-token";
|
||||
|
||||
export const sendToAgnosticChannelInjectionToken = getInjectionToken<(channel: Channel, message: any) => void>({
|
||||
id: "send-to-agnostic-channel",
|
||||
});
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 { IpcMainEvent } from "electron";
|
||||
import ipcMainInjectable from "../../app-paths/register-channel/ipc-main/ipc-main.injectable";
|
||||
import { enlistChannelListenerInjectionToken } from "../../../common/sync-box/channel/enlist-channel-listener-injection-token";
|
||||
|
||||
const enlistChannelListenerInjectable = getInjectable({
|
||||
id: "enlist-channel-listener-for-main",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipcMain = di.inject(ipcMainInjectable);
|
||||
|
||||
return (channel: any, handler: any) => {
|
||||
const nativeCallback = (_: IpcMainEvent, message: unknown) =>
|
||||
handler(message);
|
||||
|
||||
ipcMain.on(channel.id, nativeCallback);
|
||||
|
||||
return () => {
|
||||
ipcMain.off(channel.id, nativeCallback);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: enlistChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default enlistChannelListenerInjectable;
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||
import listeningOfChannelsInjectable from "../../../common/sync-box/channel/listening-of-channels.injectable";
|
||||
|
||||
const startListeningOfChannelsInjectable = getInjectable({
|
||||
id: "start-listening-of-channels-main",
|
||||
|
||||
instantiate: (di) => {
|
||||
const listeningOfChannels = di.inject(listeningOfChannelsInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await listeningOfChannels.start();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: onLoadOfApplicationInjectionToken,
|
||||
});
|
||||
|
||||
export default startListeningOfChannelsInjectable;
|
||||
32
src/main/channel/send-to-agnostic-channel.injectable.ts
Normal file
32
src/main/channel/send-to-agnostic-channel.injectable.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { lensWindowInjectionToken } from "../start-main-application/lens-window/application-window/lens-window-injection-token";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { filter } from "lodash/fp";
|
||||
import { sendToAgnosticChannelInjectionToken } from "../../common/sync-box/channel/send-to-agnostic-channel-injection-token";
|
||||
|
||||
const sendToAgnosticChannelInjectable = getInjectable({
|
||||
id: "send-to-agnostic-channel-main",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAllLensWindows = () => di.injectMany(lensWindowInjectionToken);
|
||||
|
||||
return (channel, message) => {
|
||||
const visibleWindows = pipeline(
|
||||
getAllLensWindows(),
|
||||
filter((lensWindow) => !!lensWindow.visible),
|
||||
);
|
||||
|
||||
visibleWindows.forEach((lensWindow) =>
|
||||
lensWindow.send({ channel: channel.id, data: [message] }),
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: sendToAgnosticChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default sendToAgnosticChannelInjectable;
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import ipcRendererInjectable from "../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IpcRendererEvent } from "electron";
|
||||
import { enlistChannelListenerInjectionToken } from "../../../common/sync-box/channel/enlist-channel-listener-injection-token";
|
||||
|
||||
const enlistChannelListenerInjectable = getInjectable({
|
||||
id: "enlist-channel-listener-for-renderer",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return (channel: any, handler: any) => {
|
||||
const nativeCallback = (_: IpcRendererEvent, message: unknown) =>
|
||||
handler(message);
|
||||
|
||||
ipcRenderer.on(channel.id, nativeCallback);
|
||||
|
||||
return () => {
|
||||
ipcRenderer.off(channel.id, nativeCallback);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: enlistChannelListenerInjectionToken,
|
||||
});
|
||||
|
||||
export default enlistChannelListenerInjectable;
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
|
||||
import listeningOfChannelsInjectable from "../../../common/sync-box/channel/listening-of-channels.injectable";
|
||||
|
||||
const startListeningOfChannelsInjectable = getInjectable({
|
||||
id: "start-listening-of-channels-renderer",
|
||||
|
||||
instantiate: (di) => {
|
||||
const listeningOfChannels = di.inject(listeningOfChannelsInjectable);
|
||||
|
||||
return {
|
||||
run: async () => {
|
||||
await listeningOfChannels.start();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: beforeFrameStartsInjectionToken,
|
||||
});
|
||||
|
||||
export default startListeningOfChannelsInjectable;
|
||||
23
src/renderer/channel/send-to-agnostic-channel.injectable.ts
Normal file
23
src/renderer/channel/send-to-agnostic-channel.injectable.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { sendToAgnosticChannelInjectionToken } from "../../common/sync-box/channel/send-to-agnostic-channel-injection-token";
|
||||
import sendToMainInjectable from "./send-to-main.injectable";
|
||||
|
||||
const sendToAgnosticChannelInjectable = getInjectable({
|
||||
id: "send-to-agnostic-channel-main",
|
||||
|
||||
instantiate: (di) => {
|
||||
const sendToMain = di.inject(sendToMainInjectable);
|
||||
|
||||
return (channel, message) => {
|
||||
sendToMain(channel.id, message);
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: sendToAgnosticChannelInjectionToken,
|
||||
});
|
||||
|
||||
export default sendToAgnosticChannelInjectable;
|
||||
20
src/renderer/channel/send-to-main.injectable.ts
Normal file
20
src/renderer/channel/send-to-main.injectable.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 ipcRendererInjectable from "../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
const sendToMainInjectable = getInjectable({
|
||||
id: "send-to-main",
|
||||
|
||||
instantiate: (di) => {
|
||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||
|
||||
return (channelId: string, message: any) => {
|
||||
ipcRenderer.send(channelId, message);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default sendToMainInjectable;
|
||||
@ -11,7 +11,9 @@ import registerIpcChannelListenerInjectable from "../renderer/app-paths/get-valu
|
||||
import type { SendToViewArgs } from "../main/start-main-application/lens-window/application-window/lens-window-injection-token";
|
||||
import sendToChannelInElectronBrowserWindowInjectable from "../main/start-main-application/lens-window/application-window/send-to-channel-in-electron-browser-window.injectable";
|
||||
import { isEmpty } from "lodash/fp";
|
||||
|
||||
import enlistChannelListenerInjectableInRenderer from "../renderer/channel/channel-listeners/enlist-channel-listener.injectable";
|
||||
import enlistChannelListenerInjectableInMain from "../main/channel/channel-listeners/enlist-channel-listener.injectable";
|
||||
import sendToMainInjectable from "../renderer/channel/send-to-main.injectable";
|
||||
|
||||
export const overrideIpcBridge = ({
|
||||
rendererDi,
|
||||
@ -101,4 +103,49 @@ export const overrideIpcBridge = ({
|
||||
handles.forEach((handle) => handle(...data));
|
||||
},
|
||||
);
|
||||
|
||||
const mainIpcFakeHandles = new Map<
|
||||
string,
|
||||
((...args: any[]) => void)[]
|
||||
>();
|
||||
|
||||
rendererDi.override(
|
||||
enlistChannelListenerInjectableInRenderer,
|
||||
() => (channel, handler) => {
|
||||
const existingHandles = rendererIpcFakeHandles.get(channel.id) || [];
|
||||
|
||||
rendererIpcFakeHandles.set(channel.id, [...existingHandles, handler]);
|
||||
|
||||
return () => {
|
||||
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
rendererDi.override(sendToMainInjectable, () => (channelId, message) => {
|
||||
const handles = mainIpcFakeHandles.get(channelId);
|
||||
|
||||
if (isEmpty(handles)) {
|
||||
throw new Error(
|
||||
`Tried to send message to channel "${channelId}" but there where no listeners. Current channels with listeners: "${[
|
||||
...mainIpcFakeHandles.keys(),
|
||||
].join('", "')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
handles.forEach((handle) => handle(message));
|
||||
});
|
||||
|
||||
mainDi.override(
|
||||
enlistChannelListenerInjectableInMain,
|
||||
() => (channel, handler) => {
|
||||
const existingHandles = mainIpcFakeHandles.get(channel.id) || [];
|
||||
|
||||
mainIpcFakeHandles.set(channel.id, [...existingHandles, handler]);
|
||||
|
||||
return () => {
|
||||
|
||||
};
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user