diff --git a/src/common/test-utils/use-fake-time.ts b/src/common/test-utils/use-fake-time.ts
index e455984861..a788a8e2b7 100644
--- a/src/common/test-utils/use-fake-time.ts
+++ b/src/common/test-utils/use-fake-time.ts
@@ -20,6 +20,5 @@ export const testUsingFakeTime = (dateTime = "2015-10-21T07:28:00Z") => {
usingFakeTime = true;
jest.useFakeTimers();
-
jest.setSystemTime(new Date(dateTime));
};
diff --git a/src/features/terminal/__snapshots__/opening-terminal-tab.test.tsx.snap b/src/features/terminal/__snapshots__/opening-terminal-tab.test.tsx.snap
index 3323da6e5f..b4f65871ce 100644
--- a/src/features/terminal/__snapshots__/opening-terminal-tab.test.tsx.snap
+++ b/src/features/terminal/__snapshots__/opening-terminal-tab.test.tsx.snap
@@ -6,6 +6,44 @@ exports[`test for opening terminal tab within cluster frame when new terminal ta
+
+
+
+
+
+
+
+
+ close
+
+
+
+ Close
+
+
+
+
+
+
diff --git a/src/features/terminal/opening-terminal-tab.test.tsx b/src/features/terminal/opening-terminal-tab.test.tsx
index 1244543d08..22703a321f 100644
--- a/src/features/terminal/opening-terminal-tab.test.tsx
+++ b/src/features/terminal/opening-terminal-tab.test.tsx
@@ -18,6 +18,8 @@ import type { ApplicationBuilder } from "../test-utils/application-builder";
import { setupInitializingApplicationBuilder } from "../test-utils/application-builder";
import type { FindByTextWithMarkup } from "../../test-utils/findByTextWithMarkup";
import { findByTextWithMarkupFor } from "../../test-utils/findByTextWithMarkup";
+import terminalStoreInjectable from "../../renderer/components/dock/terminal/store.injectable";
+import { flushPromises } from "../../common/test-utils/flush-promises";
describe("test for opening terminal tab within cluster frame", () => {
let builder: ApplicationBuilder;
@@ -113,8 +115,21 @@ describe("test for opening terminal tab within cluster frame", () => {
});
describe("when the next data is sent", () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ const terminalStore = builder.applicationWindow.only.di.inject(terminalStoreInjectable);
+ const terminalApi = terminalStore.getTerminalApi("terminal");
+
+ assert(terminalApi);
+
+ const waitForData = new Promise(resolve => terminalApi.on("data", (message) => {
+ if (message) {
+ resolve();
+ }
+ }));
+
+ await flushPromises();
sendData("I am a prompt");
+ await waitForData;
});
it("renders the new data", async () => {
diff --git a/src/features/test-utils/application-builder.tsx b/src/features/test-utils/application-builder.tsx
index 8cb700f8e2..01ec75c081 100644
--- a/src/features/test-utils/application-builder.tsx
+++ b/src/features/test-utils/application-builder.tsx
@@ -81,9 +81,10 @@ import { runManySyncFor } from "../../common/runnable/run-many-sync-for";
import type { MemoryHistory } from "history";
import { object } from "../../common/utils";
import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable";
-import { testUsingFakeTime } from "../../common/test-utils/use-fake-time";
import createVersionDetectorInjectable from "../../main/cluster-detectors/create-version-detector.injectable";
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
+import getElementByIdInjectable from "../../renderer/utils/get-element-by-id.injectable";
+import { testUsingFakeTime } from "../../common/test-utils/use-fake-time";
type Callback = (di: DiContainer) => void | Promise;
@@ -237,8 +238,8 @@ export const setupInitializingApplicationBuilder = (init: (builder: ApplicationB
const createElectronWindowFake: CreateElectronWindow = (configuration) => {
const windowId = configuration.id;
-
const windowDi = getRendererDi({ doGeneralOverrides: true });
+ let rendered: RenderResult;
overrideForWindow(windowDi, windowId);
overrideFsWithFakes(windowDi);
@@ -258,7 +259,15 @@ export const setupInitializingApplicationBuilder = (init: (builder: ApplicationB
return computed(() => [...rendererExtensionState.values()]);
});
- let rendered: RenderResult;
+ windowDi.override(getElementByIdInjectable, () => (id) => {
+ const elem = rendered?.container.querySelector(`#${id}`);
+
+ if (!elem) {
+ throw new Error(`Missing #${id} in DOM`);
+ }
+
+ return elem;
+ });
windowHelpers.set(windowId, { di: windowDi, getRendered: () => rendered });
@@ -357,7 +366,7 @@ export const setupInitializingApplicationBuilder = (init: (builder: ApplicationB
windowDi.override(currentLocationInjectable, () => ({
hostname: "localhost",
port: `${mainDi.inject(lensProxyPortInjectable).get()}`,
- protocol: "http",
+ protocol: "https",
}));
});
diff --git a/src/main/__test__/kube-auth-proxy.test.ts b/src/main/__test__/kube-auth-proxy.test.ts
index bcb1c33293..6bcd3cf95b 100644
--- a/src/main/__test__/kube-auth-proxy.test.ts
+++ b/src/main/__test__/kube-auth-proxy.test.ts
@@ -5,7 +5,7 @@
import waitUntilPortIsUsedInjectable from "../../common/utils/wait-until-port-is-used/wait-until-port-is-used.injectable";
import type { Cluster } from "../../common/cluster/cluster";
-import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
+import type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import type { ChildProcess } from "child_process";
import { Kubectl } from "../kubectl/kubectl";
import type { DeepMockProxy } from "jest-mock-extended";
diff --git a/src/main/k8s-request.injectable.ts b/src/main/k8s-request.injectable.ts
index 9797ba98e5..a698b96a7a 100644
--- a/src/main/k8s-request.injectable.ts
+++ b/src/main/k8s-request.injectable.ts
@@ -2,7 +2,6 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
-import { apiKubePrefix } from "../common/vars";
import type { Cluster } from "../common/cluster/cluster";
import { getInjectable } from "@ogre-tools/injectable";
import type { RequestInit } from "node-fetch";
@@ -23,8 +22,7 @@ const k8sRequestInjectable = getInjectable({
return async (cluster, path, { timeout = 30_000, ...init } = {}) => {
const controller = withTimeout(timeout);
-
- const response = await lensFetch(`/${cluster.id}${apiKubePrefix}${path}`, {
+ const response = await lensFetch(`/${cluster.id}${path}`, {
...init,
signal: controller.signal,
});
diff --git a/src/main/lens-proxy/lens-proxy.ts b/src/main/lens-proxy/lens-proxy.ts
index 5be786e28d..1ec1acf04a 100644
--- a/src/main/lens-proxy/lens-proxy.ts
+++ b/src/main/lens-proxy/lens-proxy.ts
@@ -17,7 +17,7 @@ import type { SetRequired } from "type-fest";
import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable";
import type { Logger } from "../../common/logger";
import type { SelfSignedCert } from "selfsigned";
-import type { GetClusterForRequest } from "../cluster/get-cluster-for-request.injectable";
+import type { GetClusterForRequest } from "./get-cluster-for-request.injectable";
export type ServerIncomingMessage = SetRequired;
export type ProxyRequestHandler = (args: ProxyApiRequestArgs) => void | Promise;
diff --git a/src/renderer/api/create-terminal-api.injectable.ts b/src/renderer/api/create-terminal-api.injectable.ts
index 59af749968..d0d22d0988 100644
--- a/src/renderer/api/create-terminal-api.injectable.ts
+++ b/src/renderer/api/create-terminal-api.injectable.ts
@@ -11,17 +11,19 @@ import currentLocationInjectable from "./current-location.injectable";
import defaultWebsocketApiParamsInjectable from "./default-websocket-api-params.injectable";
import type { TerminalApiDependencies, TerminalApiQuery } from "./terminal-api";
import { TerminalApi } from "./terminal-api";
+import websocketAgentInjectable from "./websocket-agent.injectable";
export type CreateTerminalApi = (query: TerminalApiQuery) => TerminalApi;
const createTerminalApiInjectable = getInjectable({
id: "create-terminal-api",
instantiate: (di): CreateTerminalApi => {
- const partialDeps = {
+ const partialDeps: Omit = {
requestShellApiToken: di.inject(requestShellApiTokenInjectable),
defaultParams: di.inject(defaultWebsocketApiParamsInjectable),
logger: di.inject(loggerInjectable),
currentLocation: di.inject(currentLocationInjectable),
+ websocketAgent: di.inject(websocketAgentInjectable),
};
return (query) => {
@@ -29,12 +31,10 @@ const createTerminalApiInjectable = getInjectable({
assert(hostedClusterId, "Can only create Terminal APIs within cluster frames");
- const deps: TerminalApiDependencies = {
+ return new TerminalApi({
...partialDeps,
hostedClusterId,
- };
-
- return new TerminalApi(deps, query);
+ }, query);
};
},
});
diff --git a/src/renderer/api/terminal-api.ts b/src/renderer/api/terminal-api.ts
index 2785e294ae..e75a306068 100644
--- a/src/renderer/api/terminal-api.ts
+++ b/src/renderer/api/terminal-api.ts
@@ -13,6 +13,7 @@ import { type TerminalMessage, TerminalChannels } from "../../common/terminal/ch
import type { RequestShellApiToken } from "../../features/terminal/renderer/request-shell-api-token.injectable";
import type { CurrentLocation } from "./current-location.injectable";
import type { ClusterId } from "../../common/cluster-types";
+import type { CloseEvent, Event, MessageEvent } from "ws";
enum TerminalColor {
RED = "\u001b[31m",
@@ -127,7 +128,9 @@ export class TerminalApi extends WebSocketApi {
}
}
- protected _onMessage({ data, ...evt }: MessageEvent): void {
+ protected _onMessage(event: MessageEvent): void {
+ const data = event.data as string;
+
try {
const message = JSON.parse(data) as TerminalMessage;
@@ -138,7 +141,7 @@ export class TerminalApi extends WebSocketApi {
* don't want this data to survive if the app is closed
*/
window.localStorage.setItem(`${this.query.id}:last-data`, message.data);
- super._onMessage({ data: message.data, ...evt });
+ super._onMessage({ ...event, data: message.data });
break;
case TerminalChannels.CONNECTED:
this.emit("connected");
diff --git a/src/renderer/api/websocket-agent.global-override-for-injectable.ts b/src/renderer/api/websocket-agent.global-override-for-injectable.ts
new file mode 100644
index 0000000000..b3b8a239a2
--- /dev/null
+++ b/src/renderer/api/websocket-agent.global-override-for-injectable.ts
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import { Agent } from "https";
+import { getGlobalOverride } from "../../common/test-utils/get-global-override";
+import websocketAgentInjectable from "./websocket-agent.injectable";
+
+export default getGlobalOverride(websocketAgentInjectable, () => new Agent({
+ rejectUnauthorized: false,
+}));
diff --git a/src/renderer/api/websocket-agent.injectable.ts b/src/renderer/api/websocket-agent.injectable.ts
new file mode 100644
index 0000000000..caf14d43d8
--- /dev/null
+++ b/src/renderer/api/websocket-agent.injectable.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable } from "@ogre-tools/injectable";
+import { Agent } from "https";
+import lensProxyCertificateInjectable from "../../common/certificate/lens-proxy-certificate.injectable";
+
+const websocketAgentInjectable = getInjectable({
+ id: "websocket-agent",
+ instantiate: (di) => {
+ const lensProxyCertificate = di.inject(lensProxyCertificateInjectable);
+
+ return new Agent({
+ cert: lensProxyCertificate.get().cert,
+ });
+ },
+});
+
+export default websocketAgentInjectable;
diff --git a/src/renderer/api/websocket-api.ts b/src/renderer/api/websocket-api.ts
index 611e0b7e67..285aa9cd12 100644
--- a/src/renderer/api/websocket-api.ts
+++ b/src/renderer/api/websocket-api.ts
@@ -9,6 +9,9 @@ import type TypedEventEmitter from "typed-emitter";
import type { Defaulted } from "../utils";
import type { DefaultWebsocketApiParams } from "./default-websocket-api-params.injectable";
import type { Logger } from "../../common/logger";
+import type { CloseEvent, Event, MessageEvent } from "ws";
+import { WebSocket } from "ws";
+import type { Agent } from "https";
interface WebsocketApiParams {
/**
@@ -66,6 +69,7 @@ export interface WebSocketEvents {
export interface WebSocketApiDependencies {
readonly defaultParams: DefaultWebsocketApiParams;
readonly logger: Logger;
+ readonly websocketAgent: Agent;
}
export class WebSocketApi extends (EventEmitter as { new(): TypedEventEmitter }) {
@@ -100,7 +104,9 @@ export class WebSocketApi extends (EventEmitter
this.socket?.close();
// start new connection
- this.socket = new WebSocket(url);
+ this.socket = new WebSocket(url, {
+ agent: this.dependencies.websocketAgent,
+ });
this.socket.addEventListener("open", ev => this._onOpen(ev));
this.socket.addEventListener("message", ev => this._onMessage(ev));
this.socket.addEventListener("error", ev => this._onError(ev));
@@ -164,7 +170,9 @@ export class WebSocketApi extends (EventEmitter
this.writeLog("%cOPEN", "color:green;font-weight:bold;", evt);
}
- protected _onMessage({ data }: MessageEvent): void {
+ protected _onMessage(event: MessageEvent): void {
+ const data = event.data as string;
+
(this as TypedEventEmitter).emit("data", data);
this.writeLog("%cMESSAGE", "color:black;font-weight:bold;", data);
}
diff --git a/src/renderer/components/dock/terminal/terminal-spawning-pool.injectable.ts b/src/renderer/components/dock/terminal/terminal-spawning-pool.injectable.ts
index 7c6f9d984e..e210ab5192 100644
--- a/src/renderer/components/dock/terminal/terminal-spawning-pool.injectable.ts
+++ b/src/renderer/components/dock/terminal/terminal-spawning-pool.injectable.ts
@@ -3,16 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
-import assert from "assert";
+import { memoize } from "lodash";
+import getElementByIdInjectable from "../../../utils/get-element-by-id.injectable";
+/**
+ * It is necessary to have this a function because in a testing environment the DOM isn't
+ * available until after first render
+ */
const terminalSpawningPoolInjectable = getInjectable({
id: "terminal-spawning-pool",
- instantiate: () => {
- const pool = document.getElementById("terminal-init");
+ instantiate: (di) => {
+ const getElementById = di.inject(getElementByIdInjectable);
- assert(pool, "DOM MUST contain #terminal-init element");
-
- return pool;
+ return memoize(() => getElementById("terminal-init"));
},
});
diff --git a/src/renderer/components/dock/terminal/terminal.ts b/src/renderer/components/dock/terminal/terminal.ts
index 1300c64578..41e77f6b3f 100644
--- a/src/renderer/components/dock/terminal/terminal.ts
+++ b/src/renderer/components/dock/terminal/terminal.ts
@@ -24,12 +24,12 @@ import { SearchAddon } from "xterm-addon-search";
import { WebglAddon } from "xterm-addon-webgl";
export interface TerminalDependencies {
- readonly spawningPool: HTMLElement;
readonly terminalConfig: IComputedValue;
readonly terminalCopyOnSelect: IComputedValue;
readonly isMac: boolean;
readonly xtermColorTheme: IComputedValue>;
readonly logger: Logger;
+ spawningPool: () => HTMLElement;
openLinkInBrowser: OpenLinkInBrowser;
createTerminalRenderer: CreateTerminalRenderer;
}
@@ -73,7 +73,7 @@ export class Terminal {
const { elem } = this;
if (elem) {
- this.dependencies.spawningPool.appendChild(elem);
+ this.dependencies.spawningPool().appendChild(elem);
}
}
@@ -106,7 +106,7 @@ export class Terminal {
this.xterm.loadAddon(new WebLinksAddon());
this.xterm.loadAddon(new SearchAddon());
- this.xterm.open(this.dependencies.spawningPool);
+ this.xterm.open(this.dependencies.spawningPool());
try {
const webgl = new WebglAddon();
diff --git a/src/renderer/utils/get-element-by-id.injectable.ts b/src/renderer/utils/get-element-by-id.injectable.ts
new file mode 100644
index 0000000000..89401eb490
--- /dev/null
+++ b/src/renderer/utils/get-element-by-id.injectable.ts
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable } from "@ogre-tools/injectable";
+
+const getElementByIdInjectable = getInjectable({
+ id: "get-element-by-id",
+ instantiate: () => (id: string) => {
+ const elem = document.getElementById(id);
+
+ if (!elem) {
+ throw new Error(`Missing #${id} in DOM`);
+ }
+
+ return elem;
+ },
+ causesSideEffects: true,
+});
+
+export default getElementByIdInjectable;