Restructure to monorepo (#6907)

* wip: restructure to monorepo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor create-release-pr to a package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* github workflow fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix typo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* add webpack-env types to core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix github workflows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor/fix integration tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* lint fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* yarn run dev

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* eslint settings for vscode

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* move templates to right package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* open-lens build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* integration test fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix nx task dependencies

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* use bash shell for unit tests in test workflow

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix test:unit for windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix build:app on windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove ELECTRON_BUILDER_EXTRA_ARGS

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* sync src/ from master

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove Makefile from core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2023-01-24 10:46:26 -08:00
1 parent 0066ca4ea2
commit 2657df2293
3321 files changed
+28699 -11447

No files matched your search

@@ -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 type { IpcMainEvent } from "electron";
import { enlistMessageChannelListenerInjectionToken } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
const enlistMessageChannelListenerInjectable = getInjectable({
id: "enlist-message-channel-listener-for-main",
instantiate: (di) => {
const ipcMain = di.inject(ipcMainInjectionToken);
return ({ channel, handler }) => {
const nativeOnCallback = (_: IpcMainEvent, message: unknown) => {
handler(message);
};
ipcMain.on(channel.id, nativeOnCallback);
return () => {
ipcMain.off(channel.id, nativeOnCallback);
};
};
},
injectionToken: enlistMessageChannelListenerInjectionToken,
});
export default enlistMessageChannelListenerInjectable;
@@ -0,0 +1,97 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
import type { EnlistMessageChannelListener } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
import { enlistMessageChannelListenerInjectionToken } from "../../../../common/utils/channel/enlist-message-channel-listener-injection-token";
import type { IpcMain, IpcMainEvent } from "electron";
describe("enlist message channel listener in main", () => {
let enlistMessageChannelListener: EnlistMessageChannelListener;
let ipcMainStub: IpcMain;
let onMock: jest.Mock;
let offMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
onMock = jest.fn();
offMock = jest.fn();
ipcMainStub = {
on: onMock,
off: offMock,
} as unknown as IpcMain;
di.override(ipcMainInjectable, () => ipcMainStub);
enlistMessageChannelListener = di.inject(
enlistMessageChannelListenerInjectionToken,
);
});
describe("when called", () => {
let handlerMock: jest.Mock;
let disposer: () => void;
beforeEach(() => {
handlerMock = jest.fn();
disposer = enlistMessageChannelListener({
channel: { id: "some-channel-id" },
handler: handlerMock,
});
});
it("does not call handler yet", () => {
expect(handlerMock).not.toHaveBeenCalled();
});
it("registers the listener", () => {
expect(onMock).toHaveBeenCalledWith(
"some-channel-id",
expect.any(Function),
);
});
it("does not de-register the listener yet", () => {
expect(offMock).not.toHaveBeenCalled();
});
describe("when message arrives", () => {
beforeEach(() => {
onMock.mock.calls[0][1]({} as IpcMainEvent, "some-message");
});
it("calls the handler with the message", () => {
expect(handlerMock).toHaveBeenCalledWith("some-message");
});
it("when disposing the listener, de-registers the listener", () => {
disposer();
expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
});
});
it("given number as message, when message arrives, calls the handler with the message", () => {
onMock.mock.calls[0][1]({} as IpcMainEvent, 42);
expect(handlerMock).toHaveBeenCalledWith(42);
});
it("given boolean as message, when message arrives, calls the handler with the message", () => {
onMock.mock.calls[0][1]({} as IpcMainEvent, true);
expect(handlerMock).toHaveBeenCalledWith(true);
});
it("given object as message, when message arrives, calls the handler with the message", () => {
onMock.mock.calls[0][1]({} as IpcMainEvent, { some: "object" });
expect(handlerMock).toHaveBeenCalledWith({ some: "object" });
});
});
});
@@ -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 type { IpcMainInvokeEvent } from "electron";
import type { Disposer } from "../../../../common/utils";
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
import type { RequestChannelListener } from "./listener-tokens";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
export type EnlistRequestChannelListener = <TChannel extends RequestChannel<unknown, unknown>>(listener: RequestChannelListener<TChannel>) => Disposer;
const enlistRequestChannelListenerInjectable = getInjectable({
id: "enlist-request-channel-listener-for-main",
instantiate: (di): EnlistRequestChannelListener => {
const ipcMain = di.inject(ipcMainInjectionToken);
return ({ channel, handler }) => {
const nativeHandleCallback = (_: IpcMainInvokeEvent, request: unknown) => handler(request);
ipcMain.handle(channel.id, nativeHandleCallback);
return () => {
ipcMain.off(channel.id, nativeHandleCallback);
};
};
},
});
export default enlistRequestChannelListenerInjectable;
@@ -0,0 +1,153 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import ipcMainInjectable from "../ipc-main/ipc-main.injectable";
import type { IpcMain, IpcMainInvokeEvent } from "electron";
import { getPromiseStatus } from "../../../../common/test-utils/get-promise-status";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
import type { EnlistRequestChannelListener } from "./enlist-request-channel-listener.injectable";
import enlistRequestChannelListenerInjectable from "./enlist-request-channel-listener.injectable";
import type { RequestChannelHandler } from "./listener-tokens";
type TestRequestChannel = RequestChannel<unknown, unknown>;
const testRequestChannel: TestRequestChannel = {
id: "some-channel-id",
};
describe("enlist request channel listener in main", () => {
let enlistRequestChannelListener: EnlistRequestChannelListener;
let ipcMainStub: IpcMain;
let handleMock: jest.Mock;
let offMock: jest.Mock;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
handleMock = jest.fn();
offMock = jest.fn();
ipcMainStub = {
handle: handleMock,
off: offMock,
} as unknown as IpcMain;
di.override(ipcMainInjectable, () => ipcMainStub);
enlistRequestChannelListener = di.inject(enlistRequestChannelListenerInjectable);
});
describe("when called", () => {
let handlerMock: AsyncFnMock<RequestChannelHandler<TestRequestChannel>>;
let disposer: () => void;
beforeEach(() => {
handlerMock = asyncFn();
disposer = enlistRequestChannelListener({
channel: testRequestChannel,
handler: handlerMock,
});
});
it("does not call handler yet", () => {
expect(handlerMock).not.toHaveBeenCalled();
});
it("registers the listener", () => {
expect(handleMock).toHaveBeenCalledWith(
"some-channel-id",
expect.any(Function),
);
});
it("does not de-register the listener yet", () => {
expect(offMock).not.toHaveBeenCalled();
});
describe("when request arrives", () => {
let actualPromise: Promise<any>;
beforeEach(() => {
actualPromise = handleMock.mock.calls[0][1](
{} as IpcMainInvokeEvent,
"some-request",
);
});
it("calls the handler with the request", () => {
expect(handlerMock).toHaveBeenCalledWith("some-request");
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
describe("when handler resolves with response, listener resolves with the response", () => {
beforeEach(async () => {
await handlerMock.resolve("some-response");
});
it("resolves with the response", async () => {
const actual = await actualPromise;
expect(actual).toBe("some-response");
});
it("when disposing the listener, de-registers the listener", () => {
disposer();
expect(offMock).toHaveBeenCalledWith("some-channel-id", expect.any(Function));
});
});
it("given number as response, when handler resolves with response, listener resolves with stringified response", async () => {
await handlerMock.resolve(42);
const actual = await actualPromise;
expect(actual).toBe(42);
});
it("given boolean as response, when handler resolves with response, listener resolves with stringified response", async () => {
await handlerMock.resolve(true);
const actual = await actualPromise;
expect(actual).toBe(true);
});
it("given object as response, when handler resolves with response, listener resolves with response", async () => {
await handlerMock.resolve({ some: "object" });
const actual = await actualPromise;
expect(actual).toEqual({ some: "object" });
});
});
it("given number as request, when request arrives, calls the handler with the request", () => {
handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, 42);
expect(handlerMock).toHaveBeenCalledWith(42);
});
it("given boolean as request, when request arrives, calls the handler with the request", () => {
handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, true);
expect(handlerMock).toHaveBeenCalledWith(true);
});
it("given object as request, when request arrives, calls the handler with the request", () => {
handleMock.mock.calls[0][1]({} as IpcMainInvokeEvent, { some: "object" });
expect(handlerMock).toHaveBeenCalledWith({ some: "object" });
});
});
});
@@ -0,0 +1,46 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainerForInjection } from "@ogre-tools/injectable";
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
export type RequestChannelHandler<Channel> = Channel extends RequestChannel<infer Request, infer Response>
? (req: Request) => Promise<Response> | Response
: never;
export interface RequestChannelListener<Channel> {
channel: Channel;
handler: RequestChannelHandler<Channel>;
}
export const requestChannelListenerInjectionToken = getInjectionToken<RequestChannelListener<RequestChannel<unknown, unknown>>>( {
id: "request-channel-listener",
});
export interface GetRequestChannelListenerInjectableInfo<
Channel extends RequestChannel<Request, Response>,
Request,
Response,
> {
channel: Channel;
handler: (di: DiContainerForInjection) => RequestChannelHandler<Channel>;
}
export function getRequestChannelListenerInjectable<
Channel extends RequestChannel<Request, Response>,
Request,
Response,
>(info: GetRequestChannelListenerInjectableInfo<Channel, Request, Response>) {
return getInjectable({
id: `${info.channel.id}-listener`,
instantiate: (di) => ({
channel: info.channel,
handler: info.handler(di),
}),
injectionToken: requestChannelListenerInjectionToken,
});
}
@@ -0,0 +1,34 @@
/**
* 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 { disposer } from "../../../../common/utils";
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
import { getStartableStoppable } from "../../../../common/utils/get-startable-stoppable";
import enlistRequestChannelListenerInjectable from "./enlist-request-channel-listener.injectable";
import { requestChannelListenerInjectionToken } from "./listener-tokens";
const listeningOnRequestChannelsInjectable = getInjectable({
id: "listening-on-request-channels",
instantiate: (di) => {
const enlistRequestChannelListener = di.inject(enlistRequestChannelListenerInjectable);
const requestChannelListeners = di.injectMany(requestChannelListenerInjectionToken);
return getStartableStoppable("listening-on-request-channels", () => {
const seenChannels = new Set<RequestChannel<unknown, unknown>>();
for (const listener of requestChannelListeners) {
if (seenChannels.has(listener.channel)) {
throw new Error(`Tried to register a multiple channel handlers for "${listener.channel.id}", only one handler is supported for a request channel.`);
}
seenChannels.add(listener.channel);
}
return disposer(requestChannelListeners.map(enlistRequestChannelListener));
});
},
});
export default listeningOnRequestChannelsInjectable;
@@ -0,0 +1,29 @@
/**
* 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 listeningOnMessageChannelsInjectable from "../../../../common/utils/channel/listening-on-message-channels.injectable";
import listeningOnRequestChannelsInjectable from "./listening-on-request-channels.injectable";
const startListeningOnChannelsInjectable = getInjectable({
id: "start-listening-on-channels-main",
instantiate: (di) => {
const listeningOnMessageChannels = di.inject(listeningOnMessageChannelsInjectable);
const listeningOnRequestChannels = di.inject(listeningOnRequestChannelsInjectable);
return {
id: "start-listening-on-channels-main",
run: () => {
listeningOnMessageChannels.start();
listeningOnRequestChannels.start();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startListeningOnChannelsInjectable;
@@ -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 { IpcMain } from "electron";
import { getGlobalOverride } from "../../../../common/test-utils/get-global-override";
import ipcMainInjectable from "./ipc-main.injectable";
export default getGlobalOverride(ipcMainInjectable, () => ({
handle: () => {},
on: () => {},
off: () => {},
}) as unknown as IpcMain);
@@ -0,0 +1,16 @@
/**
* 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 { ipcMain } from "electron";
import ipcMainInjectionToken from "../../../../common/ipc/ipc-main-injection-token";
const ipcMainInjectable = getInjectable({
id: "ipc-main",
instantiate: () => ipcMain,
causesSideEffects: true,
injectionToken: ipcMainInjectionToken,
});
export default ipcMainInjectable;
@@ -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 type { SendMessageToChannel } from "../../../common/utils/channel/message-to-channel-injection-token";
import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
import getVisibleWindowsInjectable from "../../start-main-application/lens-window/get-visible-windows.injectable";
import clusterFramesInjectable from "../../../common/cluster-frames.injectable";
const messageToChannelInjectable = getInjectable({
id: "message-to-channel",
instantiate: (di) => {
const getVisibleWindows = di.inject(getVisibleWindowsInjectable);
const clusterFrames = di.inject(clusterFramesInjectable);
return ((channel, data) => {
for (const window of getVisibleWindows()) {
window.send({ channel: channel.id, data });
clusterFrames.forEach(frameInfo => {
window.send({ channel: channel.id, data, frameInfo });
});
}
}) as SendMessageToChannel;
},
injectionToken: sendMessageToChannelInjectionToken,
});
export default messageToChannelInjectable;
@@ -0,0 +1,115 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import getVisibleWindowsInjectable from "../../start-main-application/lens-window/get-visible-windows.injectable";
import clusterFramesInjectable from "../../../common/cluster-frames.injectable";
import type { MessageChannel } from "../../../common/utils/channel/message-channel-listener-injection-token";
import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
import type { DiContainer } from "@ogre-tools/injectable";
import type { ClusterFrameInfo } from "../../../common/cluster-frames";
describe("message-to-channel", () => {
let di: DiContainer;
let sendToWindowMock: jest.Mock;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
sendToWindowMock = jest.fn();
di.override(getVisibleWindowsInjectable, () => () => [
{
id: "some-window",
send: sendToWindowMock,
show: () => {},
reload: () => {},
isStarting: false,
start: async () => {},
close: () => {},
isVisible: true,
},
{
id: "some-other-window",
send: sendToWindowMock,
show: () => {},
reload: () => {},
isStarting: false,
start: async () => {},
close: () => {},
isVisible: true,
},
]);
di.override(
clusterFramesInjectable,
() =>
new Map<string, ClusterFrameInfo>([
[
"some-cluster-id",
{ frameId: 42, processId: 84 },
],
[
"some-other-cluster-id",
{ frameId: 126, processId: 168 },
],
]),
);
});
describe("when sending message", () => {
beforeEach(() => {
const sendMessageToChannel = di.inject(
sendMessageToChannelInjectionToken,
);
sendMessageToChannel(someChannel, 42);
});
it("sends to each window and cluster frames", () => {
expect(sendToWindowMock.mock.calls).toEqual([
[{ channel: "some-channel-id", data: 42 }],
[
{
channel: "some-channel-id",
data: 42,
frameInfo: { frameId: 42, processId: 84 },
},
],
[
{
channel: "some-channel-id",
data: 42,
frameInfo: { frameId: 126, processId: 168 },
},
],
[{ channel: "some-channel-id", data: 42 }],
[
{
channel: "some-channel-id",
data: 42,
frameInfo: { frameId: 42, processId: 84 },
},
],
[
{
channel: "some-channel-id",
data: 42,
frameInfo: { frameId: 126, processId: 168 },
},
],
]);
});
});
});
const someChannel: MessageChannel<number> = {
id: "some-channel-id",
};
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
const anyKubeconfig = /^kubeconfig$/i;
/**
* This function deletes all keys of the form /^kubeconfig$/i, returning a new
* object.
*
* This is needed because `kubectl` checks for other version of kubeconfig
* before KUBECONFIG and we only set KUBECONFIG.
* @param env The current copy of env
*/
export function clearKubeconfigEnvVars(env: Partial<Record<string, string>>): Partial<Record<string, string>> {
return Object.fromEntries(
Object.entries(env)
.filter(([key]) => anyKubeconfig.exec(key) === null),
);
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import commandLineArgumentsInjectable from "./command-line-arguments.injectable";
export default getGlobalOverride(commandLineArgumentsInjectable, () => []);
@@ -0,0 +1,13 @@
/**
* 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";
const commandLineArgumentsInjectable = getInjectable({
id: "command-line-arguments",
instantiate: () => process.argv,
causesSideEffects: true,
});
export default commandLineArgumentsInjectable;
@@ -0,0 +1,77 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Readable } from "stream";
import URLParse from "url-parse";
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../common/logger.injectable";
export interface GetPortFromStreamArgs {
/**
* Should be case insensitive
* Must have a named matching group called `address`
*/
lineRegex: {
match: (line: string) => {
matched: boolean;
groups?: {
address?: string;
};
raw?: RegExpExecArray;
};
rawMatcher: string;
};
/**
* Called when the port is found
*/
onFind?: () => void;
/**
* Timeout for how long to wait for the port.
* Default: 15s
*/
timeout?: number;
}
export type GetPortFromStream = (stream: Readable, args: GetPortFromStreamArgs) => Promise<number>;
const getPortFromStreamInjectable = getInjectable({
id: "get-port-from-stream",
instantiate: (di): GetPortFromStream => {
const logger = di.inject(loggerInjectable);
return (stream, args) => {
const logLines: string[] = [];
return new Promise<number>((resolve, reject) => {
const handler = (data: unknown) => {
const logItem = String(data);
const match = args.lineRegex.match(logItem);
logLines.push(logItem);
if (match.matched) {
// use unknown protocol so that there is no default port
const addr = new URLParse(`s://${match.groups?.address?.trim()}`);
args.onFind?.();
stream.off("data", handler);
clearTimeout(timeoutID);
resolve(+addr.port);
}
};
const timeoutID = setTimeout(() => {
stream.off("data", handler);
logger.warn(`[getPortFrom]: failed to retrieve port via ${args.lineRegex.rawMatcher}`, logLines);
reject(new Error("failed to retrieve port from stream"));
}, args.timeout ?? 15000);
stream.on("data", handler);
});
};
},
});
export default getPortFromStreamInjectable;
@@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type http from "http";
/**
* Respond to a HTTP request with a body of JSON data
* @param res The HTTP response to write data to
* @param content The data or its JSON stringified version of it
* @param status [200] The status code to respond with
*/
export function respondJson(res: http.ServerResponse, content: Object | string, status = 200) {
const normalizedContent = typeof content === "object"
? JSON.stringify(content)
: content;
respond(res, normalizedContent, "application/json", status);
}
/**
* Respond to a HTTP request with a body of plain text data
* @param res The HTTP response to write data to
* @param content The string data to respond with
* @param status [200] The status code to respond with
*/
export function respondText(res: http.ServerResponse, content: string, status = 200) {
respond(res, content, "text/plain", status);
}
/**
* Respond to a HTTP request with a body of plain text data
* @param res The HTTP response to write data to
* @param content The string data to respond with
* @param contentType The HTTP Content-Type header value
* @param status [200] The status code to respond with
*/
export function respond(res: http.ServerResponse, content: string, contentType: string, status = 200) {
res.setHeader("Content-Type", contentType);
res.statusCode = status;
res.end(content);
}
@@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export function getBoolean(query: URLSearchParams, key: string): boolean {
const value = query.get(key);
switch (value?.toLowerCase()) {
case "false":
case "f":
case "0":
case null:
case undefined:
return false;
default:
return true;
}
}
@@ -0,0 +1,14 @@
/**
* 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 electron from "electron";
const electronInjectable = getInjectable({
id: "electron",
instantiate: () => electron,
causesSideEffects: true,
});
export default electronInjectable;
@@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { resolveSystemProxyChannel } from "../../../common/utils/resolve-system-proxy/resolve-system-proxy-channel";
import { getRequestChannelListenerInjectable } from "../channel/channel-listeners/listener-tokens";
import resolveSystemProxyInjectable from "./resolve-system-proxy.injectable";
const resolveSystemProxyChannelResponderInjectable = getRequestChannelListenerInjectable({
channel: resolveSystemProxyChannel,
handler: (di) => di.inject(resolveSystemProxyInjectable),
});
export default resolveSystemProxyChannelResponderInjectable;
@@ -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 electronInjectable from "./electron.injectable";
import withErrorLoggingInjectable from "../../../common/utils/with-error-logging/with-error-logging.injectable";
const resolveSystemProxyFromElectronInjectable = getInjectable({
id: "resolve-system-proxy-from-electron",
instantiate: (di) => {
const electron = di.inject(electronInjectable);
const withErrorLoggingFor = di.inject(withErrorLoggingInjectable);
const withErrorLogging = withErrorLoggingFor(() => "Error resolving proxy");
return withErrorLogging(async (url: string) => {
const webContent = electron.webContents
.getAllWebContents()
.find((x) => !x.isDestroyed());
if (!webContent) {
throw new Error(`Tried to resolve proxy for "${url}", but no browser window was available`);
}
return await webContent.session.resolveProxy(url);
});
},
});
export default resolveSystemProxyFromElectronInjectable;
@@ -0,0 +1,138 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import resolveSystemProxyFromElectronInjectable from "./resolve-system-proxy-from-electron.injectable";
import electronInjectable from "./electron.injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type electron from "electron";
import { getPromiseStatus } from "../../../common/test-utils/get-promise-status";
import logErrorInjectable from "../../../common/log-error.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
describe("technical: resolve-system-proxy-from-electron", () => {
let resolveSystemProxyMock: AsyncFnMock<(url: string) => Promise<string>>;
let logErrorMock: jest.Mock;
let di: DiContainer;
let actualPromise: Promise<string>;
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
logErrorMock = jest.fn();
di.override(logErrorInjectable, () => logErrorMock);
});
describe("given there are non-destroyed Lens windows, when called with URL", () => {
beforeEach(() => {
resolveSystemProxyMock = asyncFn();
di.override(
electronInjectable,
() =>
({
webContents: {
getAllWebContents: () => [
{
isDestroyed: () => true,
session: {
resolveProxy: () => {
throw new Error("should never come here");
},
},
},
{
isDestroyed: () => false,
session: { resolveProxy: resolveSystemProxyMock },
},
{
isDestroyed: () => false,
session: {
resolveProxy: () => {
throw new Error("should never come here");
},
},
},
],
},
} as unknown as typeof electron),
);
const resolveSystemProxyFromElectron = di.inject(
resolveSystemProxyFromElectronInjectable,
);
actualPromise = resolveSystemProxyFromElectron("some-url");
});
it("calls to resolve proxy from the first window", () => {
expect(resolveSystemProxyMock).toHaveBeenCalledWith("some-url");
});
it("does not resolve yet", async () => {
const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(false);
});
it("when call for proxy, resolves with the proxy", async () => {
resolveSystemProxyMock.resolve("some-proxy");
expect(await actualPromise).toBe("some-proxy");
});
});
describe("given there are only destroyed Lens windows, when called with URL", () => {
let error: any;
beforeEach(async () => {
di.override(
electronInjectable,
() =>
({
webContents: {
getAllWebContents: () => [
{
isDestroyed: () => true,
session: {
resolveProxy: () => {
throw new Error("should never come here");
},
},
},
],
},
} as unknown as typeof electron),
);
resolveSystemProxyMock = asyncFn();
const resolveSystemProxyFromElectron = di.inject(
resolveSystemProxyFromElectronInjectable,
);
try {
await resolveSystemProxyFromElectron("some-url");
} catch (e) {
error = e;
}
});
it("throws error", () => {
expect(error.message).toBe('Tried to resolve proxy for "some-url", but no browser window was available');
});
it("logs error", () => {
expect(logErrorMock).toHaveBeenCalledWith("Error resolving proxy", error);
});
});
});
@@ -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 { resolveSystemProxyInjectionToken } from "../../../common/utils/resolve-system-proxy/resolve-system-proxy-injection-token";
import resolveSystemProxyFromElectronInjectable from "./resolve-system-proxy-from-electron.injectable";
const resolveSystemProxyInjectable = getInjectable({
id: "resolve-system-proxy-for-main",
instantiate: (di) => {
const resolveSystemProxyFromElectron = di.inject(resolveSystemProxyFromElectronInjectable);
return (url) => resolveSystemProxyFromElectron(url);
},
injectionToken: resolveSystemProxyInjectionToken,
});
export default resolveSystemProxyInjectable;
@@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { syncBoxInitialValueChannel } from "../../../common/utils/sync-box/channels";
import { syncBoxInjectionToken } from "../../../common/utils/sync-box/sync-box-injection-token";
import { getRequestChannelListenerInjectable } from "../channel/channel-listeners/listener-tokens";
const syncBoxInitialValueChannelListenerInjectable = getRequestChannelListenerInjectable({
channel: syncBoxInitialValueChannel,
handler: (di) => {
const syncBoxes = di.injectMany(syncBoxInjectionToken);
return () => syncBoxes.map((box) => ({
id: box.id,
value: box.value.get(),
}));
},
});
export default syncBoxInitialValueChannelListenerInjectable;