Add better type safety around Channels (#6133)

* Add better type safety around Channels

- Reallow non-JSON serializable complex types

- Remove unnecessary JSON serialization step to IPC

- Remove unnecesary injectable-ization of MessageChannel and
  RequestChannel declarations

- Add channel listener injectable getter function to remove general need
  for type casting and improve type safety

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add check to verify data in IPC related tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix implementation in light of new safety check in tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix listener ID clash potential

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch to not forcing split of request channel handlers being seperate from the listener decl

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move message channels to changed signature too

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix not registering listener

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test imports

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Refactor listening of different channel kinds

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add test covering new checking code for request channel handlers

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-10-13 07:50:19 -04:00
1 parent d96918c966
commit 5a5eda935b
115 files changed
+949 -1568

No files matched your search

@@ -1,22 +0,0 @@
/**
* 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 { AppPaths } from "./app-path-injection-token";
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type AppPathsChannel = RequestChannel<void, AppPaths>;
const appPathsChannelInjectable = getInjectable({
id: "app-paths-channel",
instantiate: (): AppPathsChannel => ({
id: "app-paths",
}),
injectionToken: messageChannelInjectionToken,
});
export default appPathsChannelInjectable;
+13
View 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 type { AppPaths } from "./app-path-injection-token";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
export type AppPathsChannel = RequestChannel<void, AppPaths>;
export const appPathsChannel: AppPathsChannel = {
id: "app-paths",
};
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
export type RestartAndInstallUpdateChannel = MessageChannel<void>;
export const restartAndInstallUpdateChannel: RestartAndInstallUpdateChannel = {
id: "restart-and-install-update-channel",
};
@@ -1,21 +0,0 @@
/**
* 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 "../../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../../utils/channel/message-channel-injection-token";
export type RestartAndInstallUpdateChannel = MessageChannel;
const restartAndInstallUpdateChannel = getInjectable({
id: "restart-and-install-update-channel",
instantiate: (): RestartAndInstallUpdateChannel => ({
id: "restart-and-install-update-channel",
}),
injectionToken: messageChannelInjectionToken,
});
export default restartAndInstallUpdateChannel;
@@ -19,7 +19,7 @@ const selectedUpdateChannelInjectable = getInjectable({
instantiate: (di): SelectedUpdateChannel => {
const defaultUpdateChannel = di.inject(defaultUpdateChannelInjectable);
const state = observable.box<UpdateChannel>();
const state = observable.box<UpdateChannel>(undefined, { deep: false });
return {
value: computed(() => state.get() ?? defaultUpdateChannel.get()),
@@ -1,22 +0,0 @@
/**
* 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 { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type AppNavigationChannel = MessageChannel<string>;
const appNavigationChannelInjectable = getInjectable({
id: "app-navigation-channel",
instantiate: (): AppNavigationChannel => ({
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
}),
injectionToken: messageChannelInjectionToken,
});
export default appNavigationChannelInjectable;
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
export type AppNavigationChannel = MessageChannel<string>;
export const appNavigationChannel: AppNavigationChannel = {
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
};
@@ -1,22 +0,0 @@
/**
* 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 { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type ClusterFrameNavigationChannel = MessageChannel<string>;
const clusterFrameNavigationChannelInjectable = getInjectable({
id: "cluster-frame-navigation-channel",
instantiate: (): ClusterFrameNavigationChannel => ({
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
}),
injectionToken: messageChannelInjectionToken,
});
export default clusterFrameNavigationChannelInjectable;
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
export type ClusterFrameNavigationChannel = MessageChannel<string>;
export const clusterFrameNavigationChannel: ClusterFrameNavigationChannel = {
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
};
+22 -11
View File
@@ -5,22 +5,33 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { ExecFileOptions } from "child_process";
import { execFile } from "child_process";
import { promisify } from "util";
import type { AsyncResult } from "../utils/async-result";
export type ExecFile = (filePath: string, args: string[], options: ExecFileOptions) => Promise<string>;
export interface ExecFile {
(filePath: string, args: string[], options: ExecFileOptions): Promise<AsyncResult<string, { stderr: string; error: Error }>>;
}
const execFileInjectable = getInjectable({
id: "exec-file",
instantiate: (): ExecFile => {
const asyncExecFile = promisify(execFile);
return async (filePath, args, options) => {
const result = await asyncExecFile(filePath, args, options);
return result.stdout;
};
},
instantiate: (): ExecFile => (filePath, args, options) => new Promise((resolve) => {
execFile(filePath, args, options, (error, stdout, stderr) => {
if (error) {
resolve({
callWasSuccessful: false,
error: {
error,
stderr,
},
});
} else {
resolve({
callWasSuccessful: true,
response: stdout,
});
}
});
}),
causesSideEffects: true,
});
@@ -2,22 +2,12 @@
* 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 { HelmRepo } from "./helm-repo";
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
import type { AsyncResult } from "../utils/async-result";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<string>>;
const addHelmRepositoryChannelInjectable = getInjectable({
export const addHelmRepositoryChannel: AddHelmRepositoryChannel = {
id: "add-helm-repository-channel",
instantiate: (): AddHelmRepositoryChannel => ({
id: "add-helm-repository-channel",
}),
injectionToken: requestChannelInjectionToken,
});
export default addHelmRepositoryChannelInjectable;
};
@@ -1,23 +0,0 @@
/**
* 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 { RequestChannel } from "../utils/channel/request-channel-injection-token";
import type { HelmRepo } from "./helm-repo";
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
import type { AsyncResult } from "../utils/async-result";
export type GetHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;
const getActiveHelmRepositoriesChannelInjectable = getInjectable({
id: "get-active-helm-repositories-channel",
instantiate: (): GetHelmRepositoriesChannel => ({
id: "get-helm-active-list-repositories",
}),
injectionToken: requestChannelInjectionToken,
});
export default getActiveHelmRepositoriesChannelInjectable;
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { HelmRepo } from "./helm-repo";
import type { AsyncResult } from "../utils/async-result";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
export type GetActiveHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;
export const getActiveHelmRepositoriesChannel: GetActiveHelmRepositoriesChannel = {
id: "get-helm-active-list-repositories",
};
@@ -1,22 +0,0 @@
/**
* 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 { HelmRepo } from "./helm-repo";
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
import { requestChannelInjectionToken } from "../utils/channel/request-channel-injection-token";
export type RemoveHelmRepositoryChannel = RequestChannel<HelmRepo>;
const removeHelmRepositoryChannelInjectable = getInjectable({
id: "remove-helm-repository-channel",
instantiate: (): RemoveHelmRepositoryChannel => ({
id: "remove-helm-repository-channel",
}),
injectionToken: requestChannelInjectionToken,
});
export default removeHelmRepositoryChannelInjectable;
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncResult } from "../utils/async-result";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
import type { HelmRepo } from "./helm-repo";
export type RemoveHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<string, string>>;
export const removeHelmRepositoryChannel: RemoveHelmRepositoryChannel = {
id: "remove-helm-repository-channel",
};
@@ -1,21 +0,0 @@
/**
* 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 "../utils/channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
export type RootFrameRenderedChannel = MessageChannel;
const rootFrameRenderedChannelInjectable = getInjectable({
id: "root-frame-rendered-channel",
instantiate: (): RootFrameRenderedChannel => ({
id: "root-frame-rendered",
}),
injectionToken: messageChannelInjectionToken,
});
export default rootFrameRenderedChannelInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
export type RootFrameHasRenderedChannel = MessageChannel<void>;
export const rootFrameHasRenderedChannel: RootFrameHasRenderedChannel = {
id: "root-frame-rendered",
};
+48 -67
View File
@@ -4,31 +4,31 @@
*/
import type { DiContainer } from "@ogre-tools/injectable";
import { getInjectable } from "@ogre-tools/injectable";
import type { MessageToChannel } from "./message-to-channel-injection-token";
import { messageToChannelInjectionToken } from "./message-to-channel-injection-token";
import type { SendMessageToChannel } from "./message-to-channel-injection-token";
import { sendMessageToChannelInjectionToken } from "./message-to-channel-injection-token";
import type { ApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable";
import type { MessageChannel } from "./message-channel-listener-injection-token";
import { messageChannelListenerInjectionToken } from "./message-channel-listener-injection-token";
import type { MessageChannel } from "./message-channel-injection-token";
import type { RequestFromChannel } from "./request-from-channel-injection-token";
import { requestFromChannelInjectionToken } from "./request-from-channel-injection-token";
import type { RequestChannel } from "./request-channel-injection-token";
import { requestChannelListenerInjectionToken } from "./request-channel-listener-injection-token";
import type { RequestChannel } from "./request-channel-listener-injection-token";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { getPromiseStatus } from "../../test-utils/get-promise-status";
import { runInAction } from "mobx";
import type { RequestChannelHandler } from "../../../main/utils/channel/channel-listeners/listener-tokens";
import { getRequestChannelListenerInjectable } from "../../../main/utils/channel/channel-listeners/listener-tokens";
type TestMessageChannel = MessageChannel<string>;
type TestRequestChannel = RequestChannel<string, string>;
describe("channel", () => {
describe("messaging from main to renderer, given listener for channel in a window and application has started", () => {
let testMessageChannel: TestMessageChannel;
let messageListenerInWindowMock: jest.Mock;
let mainDi: DiContainer;
let messageToChannel: MessageToChannel;
let messageToChannel: SendMessageToChannel;
let builder: ApplicationBuilder;
beforeEach(async () => {
@@ -39,24 +39,17 @@ describe("channel", () => {
const testChannelListenerInTestWindowInjectable = getInjectable({
id: "test-channel-listener-in-test-window",
instantiate: (di) => ({
channel: di.inject(testMessageChannelInjectable),
instantiate: () => ({
channel: testMessageChannel,
handler: messageListenerInWindowMock,
}),
injectionToken: messageChannelListenerInjectionToken,
});
builder.beforeApplicationStart((mainDi) => {
runInAction(() => {
mainDi.register(testMessageChannelInjectable);
});
});
builder.beforeWindowStart((windowDi) => {
runInAction(() => {
windowDi.register(testChannelListenerInTestWindowInjectable);
windowDi.register(testMessageChannelInjectable);
});
});
@@ -64,8 +57,7 @@ describe("channel", () => {
await builder.startHidden();
testMessageChannel = mainDi.inject(testMessageChannelInjectable);
messageToChannel = mainDi.inject(messageToChannelInjectionToken);
messageToChannel = mainDi.inject(sendMessageToChannelInjectionToken);
});
describe("given window is started", () => {
@@ -109,9 +101,8 @@ describe("channel", () => {
});
describe("messaging from renderer to main, given listener for channel in a main and application has started", () => {
let testMessageChannel: TestMessageChannel;
let messageListenerInMainMock: jest.Mock;
let messageToChannel: MessageToChannel;
let messageToChannel: SendMessageToChannel;
beforeEach(async () => {
const applicationBuilder = getApplicationBuilder();
@@ -121,9 +112,8 @@ describe("channel", () => {
const testChannelListenerInMainInjectable = getInjectable({
id: "test-channel-listener-in-main",
instantiate: (di) => ({
channel: di.inject(testMessageChannelInjectable),
instantiate: () => ({
channel: testMessageChannel,
handler: messageListenerInMainMock,
}),
@@ -133,13 +123,6 @@ describe("channel", () => {
applicationBuilder.beforeApplicationStart((mainDi) => {
runInAction(() => {
mainDi.register(testChannelListenerInMainInjectable);
mainDi.register(testMessageChannelInjectable);
});
});
applicationBuilder.beforeWindowStart((windowDi) => {
runInAction(() => {
windowDi.register(testMessageChannelInjectable);
});
});
@@ -147,8 +130,7 @@ describe("channel", () => {
const windowDi = applicationBuilder.applicationWindow.only.di;
testMessageChannel = windowDi.inject(testMessageChannelInjectable);
messageToChannel = windowDi.inject(messageToChannelInjectionToken);
messageToChannel = windowDi.inject(sendMessageToChannelInjectionToken);
});
it("when sending message, triggers listener in main", () => {
@@ -159,8 +141,7 @@ describe("channel", () => {
});
describe("requesting from main in renderer, given listener for channel in a main and application has started", () => {
let testRequestChannel: TestRequestChannel;
let requestListenerInMainMock: AsyncFnMock<(arg: string) => string>;
let requestListenerInMainMock: AsyncFnMock<RequestChannelHandler<TestRequestChannel>>;
let requestFromChannel: RequestFromChannel;
beforeEach(async () => {
@@ -168,28 +149,14 @@ describe("channel", () => {
requestListenerInMainMock = asyncFn();
const testChannelListenerInMainInjectable = getInjectable({
id: "test-channel-listener-in-main",
instantiate: (di) => ({
channel: di.inject(testRequestChannelInjectable),
handler: requestListenerInMainMock,
}),
injectionToken: requestChannelListenerInjectionToken,
const testChannelListenerInMainInjectable = getRequestChannelListenerInjectable({
channel: testRequestChannel,
handler: () => requestListenerInMainMock,
});
applicationBuilder.beforeApplicationStart((mainDi) => {
runInAction(() => {
mainDi.register(testChannelListenerInMainInjectable);
mainDi.register(testRequestChannelInjectable);
});
});
applicationBuilder.beforeWindowStart((windowDi) => {
runInAction(() => {
windowDi.register(testRequestChannelInjectable);
});
});
@@ -197,8 +164,6 @@ describe("channel", () => {
const windowDi = applicationBuilder.applicationWindow.only.di;
testRequestChannel = windowDi.inject(testRequestChannelInjectable);
requestFromChannel = windowDi.inject(
requestFromChannelInjectionToken,
);
@@ -230,21 +195,37 @@ describe("channel", () => {
});
});
});
it("when registering multiple handlers for the same channel, throws", async () => {
const applicationBuilder = getApplicationBuilder();
const testChannelListenerInMainInjectable = getRequestChannelListenerInjectable({
channel: testRequestChannel,
handler: () => () => "some-value",
});
const testChannelListenerInMain2Injectable = getRequestChannelListenerInjectable({
channel: testRequestChannel,
handler: () => () => "some-other-value",
});
testChannelListenerInMain2Injectable.id += "2";
applicationBuilder.beforeApplicationStart((mainDi) => {
runInAction(() => {
mainDi.register(testChannelListenerInMainInjectable);
mainDi.register(testChannelListenerInMain2Injectable);
});
});
await expect(applicationBuilder.render()).rejects.toThrow('Tried to register a multiple channel handlers for "some-request-channel-id", only one handler is supported for a request channel.');
});
});
const testMessageChannelInjectable = getInjectable({
id: "some-message-test-channel",
const testMessageChannel: TestMessageChannel = {
id: "some-message-channel-id",
};
instantiate: (): TestMessageChannel => ({
id: "some-message-channel-id",
}),
});
const testRequestChannelInjectable = getInjectable({
id: "some-request-test-channel",
instantiate: (): TestRequestChannel => ({
id: "some-request-channel-id",
}),
});
const testRequestChannel: TestRequestChannel = {
id: "some-request-channel-id",
};
@@ -3,14 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { MessageChannel } from "./message-channel-injection-token";
import type { MessageChannelListener } from "./message-channel-listener-injection-token";
import type { Disposer } from "../disposer";
import type { MessageChannel, MessageChannelListener } from "./message-channel-listener-injection-token";
export type EnlistMessageChannelListener = <
TChannel extends MessageChannel<any>,
>(listener: MessageChannelListener<TChannel>) => () => void;
export type EnlistMessageChannelListener = (listener: MessageChannelListener<MessageChannel<unknown>>) => Disposer;
export const enlistMessageChannelListenerInjectionToken =
getInjectionToken<EnlistMessageChannelListener>({
id: "enlist-message-channel-listener",
});
export const enlistMessageChannelListenerInjectionToken = getInjectionToken<EnlistMessageChannelListener>({
id: "enlist-message-channel-listener",
});
@@ -1,16 +0,0 @@
/**
* 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 { RequestChannel } from "./request-channel-injection-token";
import type { RequestChannelListener } from "./request-channel-listener-injection-token";
export type EnlistRequestChannelListener = <
TChannel extends RequestChannel<any, any>,
>(listener: RequestChannelListener<TChannel>) => () => void;
export const enlistRequestChannelListenerInjectionToken =
getInjectionToken<EnlistRequestChannelListener>({
id: "enlist-request-channel-listener",
});
@@ -1,32 +0,0 @@
/**
* 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 "../get-startable-stoppable";
import { disposer } from "../index";
import { messageChannelListenerInjectionToken } from "./message-channel-listener-injection-token";
import { requestChannelListenerInjectionToken } from "./request-channel-listener-injection-token";
import { enlistMessageChannelListenerInjectionToken } from "./enlist-message-channel-listener-injection-token";
import { enlistRequestChannelListenerInjectionToken } from "./enlist-request-channel-listener-injection-token";
const listeningOfChannelsInjectable = getInjectable({
id: "listening-of-channels",
instantiate: (di) => {
const enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken);
const enlistRequestChannelListener = di.inject(enlistRequestChannelListenerInjectionToken);
const messageChannelListeners = di.injectMany(messageChannelListenerInjectionToken);
const requestChannelListeners = di.injectMany(requestChannelListenerInjectionToken);
return getStartableStoppable("listening-of-channels", () => {
const messageChannelDisposers = messageChannelListeners.map(enlistMessageChannelListener);
const requestChannelDisposers = requestChannelListeners.map(enlistRequestChannelListener);
return disposer(...messageChannelDisposers, ...requestChannelDisposers);
});
},
});
export default listeningOfChannelsInjectable;
@@ -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 { getStartableStoppable } from "../get-startable-stoppable";
import { disposer } from "../index";
import { messageChannelListenerInjectionToken } from "./message-channel-listener-injection-token";
import { enlistMessageChannelListenerInjectionToken } from "./enlist-message-channel-listener-injection-token";
const listeningOnMessageChannelsInjectable = getInjectable({
id: "listening-on-message-channels",
instantiate: (di) => {
const enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken);
const messageChannelListeners = di.injectMany(messageChannelListenerInjectionToken);
return getStartableStoppable("listening-on-channels", () => (
disposer(messageChannelListeners.map(enlistMessageChannelListener))
));
},
});
export default listeningOnMessageChannelsInjectable;
@@ -1,16 +0,0 @@
/**
* 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 { JsonValue } from "type-fest";
export interface MessageChannel<Message extends JsonValue | void = void> {
id: string;
_messageSignature?: Message;
}
export const messageChannelInjectionToken = getInjectionToken<MessageChannel<any>>({
id: "message-channel",
});
@@ -2,17 +2,50 @@
* 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 { SetRequired } from "type-fest";
import type { MessageChannel } from "./message-channel-injection-token";
import type { DiContainerForInjection } from "@ogre-tools/injectable";
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
export interface MessageChannelListener<TChannel extends MessageChannel<any>> {
channel: TChannel;
handler: (value: SetRequired<TChannel, "_messageSignature">["_messageSignature"]) => void;
export interface MessageChannel<Message> {
id: string;
_messageSignature?: Message; // only used to mark `Message` as used
}
export const messageChannelListenerInjectionToken = getInjectionToken<MessageChannelListener<MessageChannel<any>>>(
export type MessageChannelHandler<Channel> = Channel extends MessageChannel<infer Message>
? (message: Message) => void
: never;
export interface MessageChannelListener<Channel> {
channel: Channel;
handler: MessageChannelHandler<Channel>;
}
export const messageChannelListenerInjectionToken = getInjectionToken<MessageChannelListener<MessageChannel<unknown>>>(
{
id: "message-channel-listener",
},
);
export interface GetMessageChannelListenerInfo<
Channel extends MessageChannel<Message>,
Message,
> {
id: string;
channel: Channel;
handler: (di: DiContainerForInjection) => MessageChannelHandler<Channel>;
causesSideEffects?: boolean;
}
export function getMessageChannelListenerInjectable<
Channel extends MessageChannel<Message>,
Message,
>(info: GetMessageChannelListenerInfo<Channel, Message>) {
return getInjectable({
id: `${info.channel.id}-listener-${info.id}`,
instantiate: (di) => ({
channel: info.channel,
handler: info.handler(di),
}),
injectionToken: messageChannelListenerInjectionToken,
causesSideEffects: info.causesSideEffects,
});
}
@@ -3,21 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { SetRequired } from "type-fest";
import type { MessageChannel } from "./message-channel-injection-token";
import type { MessageChannel } from "./message-channel-listener-injection-token";
export interface MessageToChannel {
<TChannel extends MessageChannel<TMessage>, TMessage extends void>(
channel: TChannel,
): void;
<TChannel extends MessageChannel<any>>(
channel: TChannel,
message: SetRequired<TChannel, "_messageSignature">["_messageSignature"],
): void;
export interface SendMessageToChannel {
(channel: MessageChannel<void>): void;
<Message>(channel: MessageChannel<Message>, message: Message): void;
}
export const messageToChannelInjectionToken =
getInjectionToken<MessageToChannel>({
id: "message-to-message-channel",
});
export const sendMessageToChannelInjectionToken = getInjectionToken<SendMessageToChannel>({
id: "send-message-to-message-channel",
});
@@ -1,20 +0,0 @@
/**
* 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 { JsonValue } from "type-fest";
export interface RequestChannel<
Request extends JsonValue | void = void,
Response extends JsonValue | void = void,
> {
id: string;
_requestSignature?: Request;
_responseSignature?: Response;
}
export const requestChannelInjectionToken = getInjectionToken<RequestChannel<any, any>>({
id: "request-channel",
});
@@ -2,24 +2,9 @@
* 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 { SetRequired } from "type-fest";
import type { RequestChannel } from "./request-channel-injection-token";
export interface RequestChannelListener<TChannel extends RequestChannel<any, any>> {
channel: TChannel;
handler: (
request: SetRequired<TChannel, "_requestSignature">["_requestSignature"]
) =>
| SetRequired<TChannel, "_responseSignature">["_responseSignature"]
| Promise<
SetRequired<TChannel, "_responseSignature">["_responseSignature"]
>;
export interface RequestChannel<Request, Response> {
id: string;
_requestSignature?: Request; // used only to mark `Request` as "used"
_responseSignature?: Response; // used only to mark `Response` as "used"
}
export const requestChannelListenerInjectionToken = getInjectionToken<RequestChannelListener<RequestChannel<any, any>>>(
{
id: "request-channel-listener",
},
);
@@ -3,19 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { SetRequired } from "type-fest";
import type { RequestChannel } from "./request-channel-injection-token";
import type { RequestChannel } from "./request-channel-listener-injection-token";
export type RequestFromChannel = <
TChannel extends RequestChannel<any, any>,
>(
channel: TChannel,
...request: TChannel["_requestSignature"] extends void
? []
: [SetRequired<TChannel, "_requestSignature">["_requestSignature"]]
) => Promise<SetRequired<TChannel, "_responseSignature">["_responseSignature"]>;
export interface RequestFromChannel {
<Request, Response>(channel: RequestChannel<Request, Response>, request: Request): Promise<Response>;
<Response>(channel: RequestChannel<void, Response>): Promise<Response>;
}
export const requestFromChannelInjectionToken =
getInjectionToken<RequestFromChannel>({
id: "request-from-request-channel",
});
export const requestFromChannelInjectionToken = getInjectionToken<RequestFromChannel>({
id: "request-from-request-channel",
});
+4 -2
View File
@@ -3,6 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { SingleOrMany } from "./types";
export interface Disposer {
@@ -17,9 +19,9 @@ export interface ExtendableDisposer extends Disposer {
push(...vals: (Disposer | ExtendableDisposer | Disposable)[]): void;
}
export function disposer(...items: (Disposer | Disposable | undefined | null)[]): ExtendableDisposer {
export function disposer(...items: SingleOrMany<Disposer | Disposable | undefined | null>[]): ExtendableDisposer {
return Object.assign(() => {
for (const item of items) {
for (const item of items.flat()) {
if (!item) {
continue;
}
@@ -1,21 +0,0 @@
/**
* 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 { RequestChannel } from "../channel/request-channel-injection-token";
import { requestChannelInjectionToken } from "../channel/request-channel-injection-token";
export type ResolveSystemProxyChannel = RequestChannel<string, string>;
const resolveSystemProxyChannelInjectable = getInjectable({
id: "resolve-system-proxy-channel",
instantiate: (): ResolveSystemProxyChannel => ({
id: "resolve-system-proxy-channel",
}),
injectionToken: requestChannelInjectionToken,
});
export default resolveSystemProxyChannelInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RequestChannel } from "../channel/request-channel-listener-injection-token";
export type ResolveSystemProxyChannel = RequestChannel<string, string>;
export const resolveSystemProxyChannel: ResolveSystemProxyChannel = {
id: "resolve-system-proxy-channel",
};
@@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { syncBoxChannel } from "./channels";
import { getMessageChannelListenerInjectable } from "../channel/message-channel-listener-injection-token";
import syncBoxStateInjectable from "./sync-box-state.injectable";
const syncBoxChannelListenerInjectable = getMessageChannelListenerInjectable({
id: "init",
channel: syncBoxChannel,
handler: (di) => ({ id, value }) => di.inject(syncBoxStateInjectable, id).set(value),
});
export default syncBoxChannelListenerInjectable;
+21
View File
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MessageChannel } from "../channel/message-channel-listener-injection-token";
import type { RequestChannel } from "../channel/request-channel-listener-injection-token";
export type SyncBoxChannel = MessageChannel<{ id: string; value: any }>;
export const syncBoxChannel: SyncBoxChannel = {
id: "sync-box-channel",
};
export type SyncBoxInitialValueChannel = RequestChannel<
void,
{ id: string; value: any }[]
>;
export const syncBoxInitialValueChannel: SyncBoxInitialValueChannel = {
id: "sync-box-initial-value-channel",
};
@@ -5,17 +5,17 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { IObservableValue } from "mobx";
import { computed } from "mobx";
import syncBoxChannelInjectable from "./sync-box-channel.injectable";
import { messageToChannelInjectionToken } from "../channel/message-to-channel-injection-token";
import { syncBoxChannel } from "./channels";
import { sendMessageToChannelInjectionToken } from "../channel/message-to-channel-injection-token";
import syncBoxStateInjectable from "./sync-box-state.injectable";
import type { SyncBox } from "./sync-box-injection-token";
import { toJS } from "../toJS";
const createSyncBoxInjectable = getInjectable({
id: "create-sync-box",
instantiate: (di) => {
const syncBoxChannel = di.inject(syncBoxChannelInjectable);
const messageToChannel = di.inject(messageToChannelInjectionToken);
const messageToChannel = di.inject(sendMessageToChannelInjectionToken);
const getSyncBoxState = (id: string) => di.inject(syncBoxStateInjectable, id);
return <Value>(id: string, initialValue: Value): SyncBox<Value> => {
@@ -26,7 +26,7 @@ const createSyncBoxInjectable = getInjectable({
return {
id,
value: computed(() => state.get()),
value: computed(() => toJS(state.get())),
set: (value) => {
state.set(value);
@@ -0,0 +1,19 @@
/**
* 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 { MessageChannelHandler } from "../channel/message-channel-listener-injection-token";
import type { SyncBoxChannel } from "./channels";
import syncBoxStateInjectable from "./sync-box-state.injectable";
const syncBoxChannelHandlerInjectable = getInjectable({
id: "sync-box-channel-handler",
instantiate: (di): MessageChannelHandler<SyncBoxChannel> => {
const getSyncBoxState = (id: string) => di.inject(syncBoxStateInjectable, id);
return ({ id, value }) => getSyncBoxState(id)?.set(value);
},
});
export default syncBoxChannelHandlerInjectable;
@@ -1,35 +0,0 @@
/**
* 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 { SyncBoxChannel } from "./sync-box-channel.injectable";
import syncBoxChannelInjectable from "./sync-box-channel.injectable";
import syncBoxStateInjectable from "./sync-box-state.injectable";
import type { MessageChannelListener } from "../channel/message-channel-listener-injection-token";
import { messageChannelListenerInjectionToken } from "../channel/message-channel-listener-injection-token";
const syncBoxChannelListenerInjectable = getInjectable({
id: "sync-box-channel-listener",
instantiate: (di): MessageChannelListener<SyncBoxChannel> => {
const getSyncBoxState = (id: string) => di.inject(syncBoxStateInjectable, id);
const channel = di.inject(syncBoxChannelInjectable);
return {
channel,
handler: ({ id, value }) => {
const target = getSyncBoxState(id);
if (target) {
target.set(value);
}
},
};
},
injectionToken: messageChannelListenerInjectionToken,
});
export default syncBoxChannelListenerInjectable;
@@ -1,21 +0,0 @@
/**
* 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 "../channel/message-channel-injection-token";
import { messageChannelInjectionToken } from "../channel/message-channel-injection-token";
export type SyncBoxChannel = MessageChannel<{ id: string; value: any }>;
const syncBoxChannelInjectable = getInjectable({
id: "sync-box-channel",
instantiate: (): SyncBoxChannel => ({
id: "sync-box-channel",
}),
injectionToken: messageChannelInjectionToken,
});
export default syncBoxChannelInjectable;
@@ -1,24 +0,0 @@
/**
* 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 { RequestChannel } from "../channel/request-channel-injection-token";
import { requestChannelInjectionToken } from "../channel/request-channel-injection-token";
export type SyncBoxInitialValueChannel = RequestChannel<
void,
{ id: string; value: any }[]
>;
const syncBoxInitialValueChannelInjectable = getInjectable({
id: "sync-box-initial-value-channel",
instantiate: (): SyncBoxInitialValueChannel => ({
id: "sync-box-initial-value-channel",
}),
injectionToken: requestChannelInjectionToken,
});
export default syncBoxInitialValueChannelInjectable;
-15
View File
@@ -1,15 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import { defaultTo } from "lodash/fp";
import { withErrorSuppression } from "./with-error-suppression/with-error-suppression";
export const tentativeParseJson = (toBeParsed: any) => pipeline(
toBeParsed,
withErrorSuppression(JSON.parse),
defaultTo(toBeParsed),
);
@@ -1,15 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import { defaultTo } from "lodash/fp";
import { withErrorSuppression } from "./with-error-suppression/with-error-suppression";
export const tentativeStringifyJson = (toBeParsed: any) => pipeline(
toBeParsed,
withErrorSuppression(JSON.stringify),
defaultTo(toBeParsed),
);
@@ -7,7 +7,7 @@ import { getInjectionToken } from "@ogre-tools/injectable";
import { SemVer } from "semver";
import type { InitializableState } from "../initializable-state/create";
import { createInitializableState } from "../initializable-state/create";
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
export const buildVersionInjectionToken = getInjectionToken<InitializableState<string>>({
id: "build-version-token",