mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Switch to using messaging and startable stoppable from NPM package (#7368)
* Add custom jest resolver to fix requiring "uuid" module Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update dependencies Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Introduce test utils for rendering and running with thrown mobx reactions Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract startable-stoppable to NPM package Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract messaging to NPM package Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using startable-stoppable from NPM package Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Switch to using messaging from the Feature Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove old implementation of messaging Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make setupping app paths happen earlier in renderer Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix typo Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add kludge to make testing-library work properly from test-utils package Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix code style Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add lint:fix -root script Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Fix unrelated failing unit tests Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Turn of no-floating-promises from typescript linting for being broken Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make linting not happen for dist -directories Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make linting failures appear as failure Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Stop running prettier twice It already gets ran as eslint-plugin. Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Make CI run unit tests for all packages by consolidating name of NPM script Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing unit tests for coverage Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Skip coverage for test utils Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Remove check for coverage in packages which are not ready for it Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Stop collecting coverage from index.ts files them being indirections to the implementation Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Implement sending message to channel in main Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing feature dependencies Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add dummy implementations for requesting in main from renderer Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Re-enable communicating from main to cluster frames Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Ignore trivial files from coverage Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Update package-lock Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract message-bridge to separate NPM package to prevent dev dependencies being in the production bundle Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Extract computed channel to own NPM package for clear dependencies Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Consolidate electron related stuff to a directory Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Add missing publish configurations Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> * Ignore test implementation from coverage being not interesting Signed-off-by: Janne Savolainen <janne.savolainen@live.fi> --------- Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
df49ff9b96
commit
0f1f030a06
261 files changed
+4447
-1795
No files matched your search
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@k8slens/eslint-config/eslint",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
"@k8slens/eslint-config/prettier"
|
||||
@@ -0,0 +1,3 @@
|
||||
export type { MessageBridgeFake } from "./src/get-message-bridge-fake/get-message-bridge-fake";
|
||||
|
||||
export { getMessageBridgeFake } from "./src/get-message-bridge-fake/get-message-bridge-fake";
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact;
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@k8slens/messaging-fake-bridge",
|
||||
"private": false,
|
||||
"version": "1.0.0-alpha.1",
|
||||
"description": "Fake implementation to bridge multiple dependency injection containers.",
|
||||
"type": "commonjs",
|
||||
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lensapp/lens.git"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
"name": "OpenLens Authors",
|
||||
"email": "info@k8slens.dev"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"dev": "webpack --mode=development --watch",
|
||||
"test:unit": "jest --coverage --runInBand",
|
||||
"lint:fix": "lens-lint --fix",
|
||||
"lint": "lens-lint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||
"@ogre-tools/fp": "^15.1.2",
|
||||
"@ogre-tools/injectable": "^15.1.2",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
|
||||
"devDependencies": {
|
||||
"@async-fn/jest": "^1.6.4",
|
||||
"@k8slens/feature-core": "6.5.0-alpha.1",
|
||||
"@k8slens/eslint-config": "^6.5.0-alpha.1",
|
||||
"@ogre-tools/injectable-extension-for-mobx": "^15.1.2",
|
||||
"mobx": "^6.7.0"
|
||||
}
|
||||
}
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
import { createContainer, DiContainer, Injectable } from "@ogre-tools/injectable";
|
||||
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
||||
import { registerFeature } from "@k8slens/feature-core";
|
||||
import {
|
||||
getMessageChannel,
|
||||
getMessageChannelListenerInjectable,
|
||||
getRequestChannel,
|
||||
getRequestChannelListenerInjectable,
|
||||
MessageChannel,
|
||||
testUtils,
|
||||
RequestChannel,
|
||||
requestFromChannelInjectionToken,
|
||||
sendMessageToChannelInjectionToken,
|
||||
} from "@k8slens/messaging";
|
||||
|
||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { runInAction } from "mobx";
|
||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
||||
import { getMessageBridgeFake } from "./get-message-bridge-fake";
|
||||
import { startApplicationInjectionToken } from "@k8slens/application";
|
||||
|
||||
type SomeMessageChannel = MessageChannel<string>;
|
||||
type SomeRequestChannel = RequestChannel<string, number>;
|
||||
|
||||
const someMessageChannel: SomeMessageChannel = getMessageChannel("some-message-channel");
|
||||
const someRequestChannel: SomeRequestChannel = getRequestChannel("some-request-channel");
|
||||
const someOtherRequestChannel: SomeRequestChannel = {
|
||||
id: "some-other-request-channel",
|
||||
};
|
||||
const someRequestChannelWithoutListeners: SomeRequestChannel = {
|
||||
id: "some-request-channel-without-listeners",
|
||||
};
|
||||
|
||||
[{ scenarioIsAsync: true }, { scenarioIsAsync: false }].forEach(({ scenarioIsAsync }) =>
|
||||
describe(`get-message-bridge-fake, given running as ${
|
||||
scenarioIsAsync ? "async" : "sync"
|
||||
}`, () => {
|
||||
let messageBridgeFake: any;
|
||||
|
||||
beforeEach(() => {
|
||||
messageBridgeFake = getMessageBridgeFake();
|
||||
});
|
||||
|
||||
describe("given multiple DIs are involved", () => {
|
||||
let someDi1: DiContainer;
|
||||
let someDi2: DiContainer;
|
||||
let someDiWithoutListeners: DiContainer;
|
||||
|
||||
beforeEach(async () => {
|
||||
someDi1 = createContainer("some-di-1");
|
||||
someDi2 = createContainer("some-di-2");
|
||||
|
||||
someDiWithoutListeners = createContainer("some-di-3");
|
||||
|
||||
registerMobX(someDi1);
|
||||
registerMobX(someDi2);
|
||||
registerMobX(someDiWithoutListeners);
|
||||
|
||||
runInAction(() => {
|
||||
const feature = testUtils.messagingFeatureForUnitTesting;
|
||||
|
||||
registerFeature(someDi1, feature);
|
||||
registerFeature(someDi2, feature);
|
||||
registerFeature(someDiWithoutListeners, feature);
|
||||
});
|
||||
|
||||
messageBridgeFake.involve(someDi1, someDi2, someDiWithoutListeners);
|
||||
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.setAsync(scenarioIsAsync);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
someDi1.inject(startApplicationInjectionToken)(),
|
||||
someDi2.inject(startApplicationInjectionToken)(),
|
||||
someDiWithoutListeners.inject(startApplicationInjectionToken)(),
|
||||
]);
|
||||
});
|
||||
|
||||
describe("given there are message listeners", () => {
|
||||
let someHandler1MockInDi1: jest.Mock;
|
||||
let someHandler1MockInDi2: jest.Mock;
|
||||
let someHandler2MockInDi2: jest.Mock;
|
||||
let someListener1InDi2: Injectable<unknown, unknown>;
|
||||
|
||||
beforeEach(() => {
|
||||
someHandler1MockInDi1 = jest.fn();
|
||||
someHandler1MockInDi2 = jest.fn();
|
||||
someHandler2MockInDi2 = jest.fn();
|
||||
|
||||
const someListener1InDi1 = getMessageChannelListenerInjectable({
|
||||
id: "some-listener-in-di-1",
|
||||
channel: someMessageChannel,
|
||||
getHandler: () => someHandler1MockInDi1,
|
||||
});
|
||||
|
||||
someListener1InDi2 = getMessageChannelListenerInjectable({
|
||||
id: "some-listener-in-di-2",
|
||||
channel: someMessageChannel,
|
||||
getHandler: () => someHandler1MockInDi2,
|
||||
});
|
||||
|
||||
const someListener2InDi2 = getMessageChannelListenerInjectable({
|
||||
id: "some-listener-2-in-di-2",
|
||||
channel: someMessageChannel,
|
||||
getHandler: () => someHandler2MockInDi2,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
someDi1.register(someListener1InDi1);
|
||||
someDi2.register(someListener1InDi2);
|
||||
someDi2.register(someListener2InDi2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given there is a listener in di-2 that responds to a message with a message", () => {
|
||||
beforeEach(() => {
|
||||
const someResponder = getMessageChannelListenerInjectable({
|
||||
id: "some-responder-di-2",
|
||||
channel: someMessageChannel,
|
||||
|
||||
getHandler: (di) => {
|
||||
const sendMessage = di.inject(sendMessageToChannelInjectionToken);
|
||||
|
||||
return (message) => {
|
||||
sendMessage(someMessageChannel, `some-response-to: ${message}`);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
someDi2.register(someResponder);
|
||||
});
|
||||
});
|
||||
|
||||
describe("given a message is sent in di-1", () => {
|
||||
beforeEach(() => {
|
||||
const sendMessageToChannelFromDi1 = someDi1.inject(
|
||||
sendMessageToChannelInjectionToken,
|
||||
);
|
||||
|
||||
sendMessageToChannelFromDi1(someMessageChannel, "some-message");
|
||||
});
|
||||
|
||||
const scenarioTitle = scenarioIsAsync
|
||||
? "when all message steps are propagated using a wrapper"
|
||||
: "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioTitle, () => {
|
||||
let someWrapper: jest.Mock;
|
||||
|
||||
beforeEach((done) => {
|
||||
someWrapper = jest.fn((propagation) => propagation());
|
||||
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagationRecursive(someWrapper).then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it("the response gets handled in di-1", () => {
|
||||
expect(someHandler1MockInDi1).toHaveBeenCalledWith(
|
||||
"some-response-to: some-message",
|
||||
{
|
||||
frameId: 42,
|
||||
processId: 42,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
it("the wrapper gets called with the both propagations", () => {
|
||||
expect(someWrapper).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
const scenarioName: string = scenarioIsAsync
|
||||
? "when all message steps are propagated not using a wrapper"
|
||||
: "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagationRecursive().then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it("the response gets handled in di-1", () => {
|
||||
expect(someHandler1MockInDi1).toHaveBeenCalledWith(
|
||||
"some-response-to: some-message",
|
||||
{
|
||||
frameId: 42,
|
||||
processId: 42,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sending message in a DI", () => {
|
||||
beforeEach(() => {
|
||||
const sendMessageToChannelFromDi1 = someDi1.inject(sendMessageToChannelInjectionToken);
|
||||
|
||||
sendMessageToChannelFromDi1(someMessageChannel, "some-message");
|
||||
});
|
||||
|
||||
it("listener in sending DI does not handle the message", () => {
|
||||
expect(someHandler1MockInDi1).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
it("listeners in other than sending DIs do not handle the message yet", () => {
|
||||
expect(someHandler1MockInDi2).not.toHaveBeenCalled();
|
||||
expect(someHandler2MockInDi2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const scenarioName = scenarioIsAsync ? "when messages are propagated" : "immediately";
|
||||
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
describe(scenarioName, () => {
|
||||
beforeEach((done) => {
|
||||
if (scenarioIsAsync) {
|
||||
messageBridgeFake.messagePropagation().then(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it("listeners in other than sending DIs handle the message", () => {
|
||||
expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message", {
|
||||
frameId: 42,
|
||||
processId: 42,
|
||||
});
|
||||
|
||||
expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message", {
|
||||
frameId: 42,
|
||||
processId: 42,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
scenarioIsAsync &&
|
||||
describe("when messages are propagated using a wrapper, such as act() in react testing lib", () => {
|
||||
let someWrapper: jest.Mock;
|
||||
|
||||
beforeEach(async () => {
|
||||
someWrapper = jest.fn((observation) => observation());
|
||||
|
||||
await messageBridgeFake.messagePropagation(someWrapper);
|
||||
});
|
||||
|
||||
it("the wrapper gets called with the related propagation", async () => {
|
||||
expect(someWrapper).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("listeners still handle the message", () => {
|
||||
expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-message", {
|
||||
frameId: 42,
|
||||
processId: 42,
|
||||
});
|
||||
|
||||
expect(someHandler2MockInDi2).toHaveBeenCalledWith("some-message", {
|
||||
frameId: 42,
|
||||
processId: 42,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("given a listener is deregistered, when sending message, deregistered listener does not handle the message", () => {
|
||||
runInAction(() => {
|
||||
someDi2.deregister(someListener1InDi2);
|
||||
});
|
||||
|
||||
const sendMessageToChannelFromDi1 = someDi1.inject(sendMessageToChannelInjectionToken);
|
||||
|
||||
someHandler1MockInDi2.mockClear();
|
||||
|
||||
sendMessageToChannelFromDi1(someMessageChannel, "irrelevant");
|
||||
|
||||
expect(someHandler1MockInDi2).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("given there are request listeners", () => {
|
||||
let someHandler1MockInDi1: AsyncFnMock<(message: string) => Promise<number>>;
|
||||
|
||||
let someHandler1MockInDi2: AsyncFnMock<(message: string) => Promise<number>>;
|
||||
|
||||
let someListener1InDi2: Injectable<unknown, unknown>;
|
||||
let actualPromise: Promise<number>;
|
||||
|
||||
beforeEach(() => {
|
||||
someHandler1MockInDi1 = asyncFn();
|
||||
someHandler1MockInDi2 = asyncFn();
|
||||
|
||||
const someListener1InDi1 = getRequestChannelListenerInjectable({
|
||||
id: "some-request-listener-in-di-1",
|
||||
channel: someOtherRequestChannel,
|
||||
getHandler: () => someHandler1MockInDi1,
|
||||
});
|
||||
|
||||
someListener1InDi2 = getRequestChannelListenerInjectable({
|
||||
id: "some-request-listener-in-di-2",
|
||||
channel: someRequestChannel,
|
||||
getHandler: () => someHandler1MockInDi2,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
someDi1.register(someListener1InDi1);
|
||||
someDi2.register(someListener1InDi2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when requesting from a channel in a DI", () => {
|
||||
beforeEach(() => {
|
||||
const requestFromChannelFromDi1 = someDi1.inject(requestFromChannelInjectionToken);
|
||||
|
||||
actualPromise = requestFromChannelFromDi1(someRequestChannel, "some-request");
|
||||
});
|
||||
|
||||
it("listener in requesting DI does not handle the request", () => {
|
||||
expect(someHandler1MockInDi1).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("the listener in other than requesting DIs handle the request", () => {
|
||||
expect(someHandler1MockInDi2).toHaveBeenCalledWith("some-request");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when handle resolves, resolves with response", async () => {
|
||||
await someHandler1MockInDi2.resolve(42);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
it("given a listener is deregistered, when requesting, deregistered listener does not handle the request", () => {
|
||||
runInAction(() => {
|
||||
someDi2.deregister(someListener1InDi2);
|
||||
});
|
||||
|
||||
const sendMessageToChannelFromDi1 = someDi1.inject(sendMessageToChannelInjectionToken);
|
||||
|
||||
someHandler1MockInDi2.mockClear();
|
||||
|
||||
sendMessageToChannelFromDi1(someMessageChannel, "irrelevant");
|
||||
|
||||
expect(someHandler1MockInDi2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("given there are multiple listeners between different DIs for same channel, when requesting, throws", () => {
|
||||
const someConflictingListenerInjectable = getRequestChannelListenerInjectable({
|
||||
id: "conflicting-listener",
|
||||
channel: someRequestChannel,
|
||||
getHandler: () => () => 84,
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
someDi1.register(someConflictingListenerInjectable);
|
||||
});
|
||||
|
||||
const requestFromChannelFromDi2 = someDi2.inject(requestFromChannelInjectionToken);
|
||||
|
||||
return expect(() =>
|
||||
requestFromChannelFromDi2(someRequestChannel, "irrelevant"),
|
||||
).rejects.toThrow(
|
||||
'Tried to make a request but multiple listeners were discovered for channel "some-request-channel" in multiple DIs.',
|
||||
);
|
||||
});
|
||||
|
||||
it("when requesting from channel without listener, throws", () => {
|
||||
const requestFromChannel = someDi1.inject(requestFromChannelInjectionToken);
|
||||
|
||||
return expect(() =>
|
||||
requestFromChannel(someRequestChannelWithoutListeners, "irrelevant"),
|
||||
).rejects.toThrow(
|
||||
'Tried to make a request but no listeners for channel "some-request-channel-without-listeners" was discovered in any DIs',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import type { Channel, MessageChannelHandler, RequestChannelHandler } from "@k8slens/messaging";
|
||||
|
||||
import {
|
||||
enlistMessageChannelListenerInjectionToken,
|
||||
enlistRequestChannelListenerInjectionToken,
|
||||
RequestFromChannel,
|
||||
requestFromChannelInjectionToken,
|
||||
sendMessageToChannelInjectionToken,
|
||||
} from "@k8slens/messaging";
|
||||
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import { filter, map } from "lodash/fp";
|
||||
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
|
||||
|
||||
export type MessageBridgeFake = {
|
||||
involve: (...dis: DiContainer[]) => void;
|
||||
messagePropagation: () => Promise<void>;
|
||||
messagePropagationRecursive: (callback: any) => any;
|
||||
setAsync: (value: boolean) => void;
|
||||
};
|
||||
|
||||
const overrideMessaging = ({
|
||||
di,
|
||||
messageListenersByDi,
|
||||
messagePropagationBuffer,
|
||||
getAsyncModeStatus,
|
||||
}: {
|
||||
di: DiContainer;
|
||||
|
||||
messageListenersByDi: Map<DiContainer, Map<string, Set<MessageChannelHandler<Channel>>>>;
|
||||
|
||||
messagePropagationBuffer: Set<{ resolve: () => Promise<void> }>;
|
||||
|
||||
getAsyncModeStatus: () => boolean;
|
||||
}) => {
|
||||
const messageHandlersByChannel = new Map<string, Set<MessageChannelHandler<Channel>>>();
|
||||
|
||||
messageListenersByDi.set(di, messageHandlersByChannel);
|
||||
|
||||
di.override(sendMessageToChannelInjectionToken, () => (channel, message) => {
|
||||
const allOtherDis = [...messageListenersByDi.keys()].filter((x) => x !== di);
|
||||
|
||||
allOtherDis.forEach((otherDi) => {
|
||||
const listeners = messageListenersByDi.get(otherDi);
|
||||
|
||||
const handlersForChannel = listeners?.get(channel.id);
|
||||
|
||||
if (!handlersForChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getAsyncModeStatus()) {
|
||||
const resolvableHandlePromise = asyncFn();
|
||||
|
||||
resolvableHandlePromise().then(() => {
|
||||
handlersForChannel.forEach((handler) => handler(message, { frameId: 42, processId: 42 }));
|
||||
});
|
||||
|
||||
messagePropagationBuffer.add(resolvableHandlePromise);
|
||||
} else {
|
||||
handlersForChannel.forEach((handler) => handler(message, { frameId: 42, processId: 42 }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
di.override(enlistMessageChannelListenerInjectionToken, () => (listener) => {
|
||||
if (!messageHandlersByChannel.has(listener.channel.id)) {
|
||||
messageHandlersByChannel.set(listener.channel.id, new Set());
|
||||
}
|
||||
|
||||
const handlerSet = messageHandlersByChannel.get(listener.channel.id);
|
||||
|
||||
handlerSet?.add(listener.handler);
|
||||
|
||||
return () => {
|
||||
handlerSet?.delete(listener.handler);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const overrideRequesting = ({
|
||||
di,
|
||||
requestListenersByDi,
|
||||
}: {
|
||||
di: DiContainer;
|
||||
|
||||
requestListenersByDi: Map<DiContainer, Map<string, Set<RequestChannelHandler<Channel>>>>;
|
||||
}) => {
|
||||
const requestHandlersByChannel = new Map<string, Set<RequestChannelHandler<Channel>>>();
|
||||
|
||||
requestListenersByDi.set(di, requestHandlersByChannel);
|
||||
|
||||
di.override(
|
||||
requestFromChannelInjectionToken,
|
||||
() =>
|
||||
(async (channel, request) =>
|
||||
pipeline(
|
||||
[...requestListenersByDi.values()],
|
||||
map((listenersByChannel) => listenersByChannel?.get(channel.id)),
|
||||
filter((x) => !!x),
|
||||
|
||||
(channelSpecificListeners) => {
|
||||
if (channelSpecificListeners.length === 0) {
|
||||
throw new Error(
|
||||
`Tried to make a request but no listeners for channel "${channel.id}" was discovered in any DIs`,
|
||||
);
|
||||
}
|
||||
|
||||
if (channelSpecificListeners.length > 1) {
|
||||
throw new Error(
|
||||
`Tried to make a request but multiple listeners were discovered for channel "${channel.id}" in multiple DIs.`,
|
||||
);
|
||||
}
|
||||
|
||||
const listeners = channelSpecificListeners[0];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const [handler] = listeners!;
|
||||
|
||||
return handler;
|
||||
},
|
||||
|
||||
async (handler) => handler(request),
|
||||
)) as RequestFromChannel,
|
||||
);
|
||||
|
||||
di.override(enlistRequestChannelListenerInjectionToken, () => (listener) => {
|
||||
if (!requestHandlersByChannel.has(listener.channel.id)) {
|
||||
requestHandlersByChannel.set(listener.channel.id, new Set());
|
||||
}
|
||||
|
||||
const handlerSet = requestHandlersByChannel.get(listener.channel.id);
|
||||
|
||||
handlerSet?.add(listener.handler);
|
||||
|
||||
return () => {
|
||||
handlerSet?.delete(listener.handler);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getMessageBridgeFake = (): MessageBridgeFake => {
|
||||
const messageListenersByDi = new Map<
|
||||
DiContainer,
|
||||
Map<string, Set<MessageChannelHandler<Channel>>>
|
||||
>();
|
||||
|
||||
const requestListenersByDi = new Map<
|
||||
DiContainer,
|
||||
Map<string, Set<RequestChannelHandler<Channel>>>
|
||||
>();
|
||||
|
||||
const messagePropagationBuffer = new Set<AsyncFnMock<() => void>>();
|
||||
|
||||
const messagePropagation = async (wrapper: (callback: any) => any = (callback) => callback()) => {
|
||||
const oldMessages = [...messagePropagationBuffer.values()];
|
||||
|
||||
messagePropagationBuffer.clear();
|
||||
await Promise.all(oldMessages.map((x) => wrapper(x.resolve)));
|
||||
};
|
||||
|
||||
const messagePropagationRecursive = async (
|
||||
wrapper: (callback: any) => any = (callback) => callback(),
|
||||
) => {
|
||||
while (messagePropagationBuffer.size) {
|
||||
await messagePropagation(wrapper);
|
||||
}
|
||||
};
|
||||
|
||||
let asyncModeStatus = false;
|
||||
const getAsyncModeStatus = () => asyncModeStatus;
|
||||
|
||||
return {
|
||||
involve: (...dis: DiContainer[]) => {
|
||||
dis.forEach((di) => {
|
||||
overrideRequesting({ di, requestListenersByDi });
|
||||
|
||||
overrideMessaging({
|
||||
di,
|
||||
messageListenersByDi,
|
||||
messagePropagationBuffer,
|
||||
getAsyncModeStatus,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
messagePropagation,
|
||||
|
||||
messagePropagationRecursive,
|
||||
|
||||
setAsync: (value) => {
|
||||
asyncModeStatus = value;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@k8slens/typescript/config/base.json",
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require("@k8slens/webpack").configForNode;
|
||||
Reference in new issue
Block a user