1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/packages/core/src/renderer/api/terminal-api.ts
Jari Kolehmainen 2657df2293
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>
2023-01-24 10:46:26 -08:00

186 lines
5.2 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { WebSocketApiDependencies, WebSocketEvents } from "./websocket-api";
import { WebSocketApi } from "./websocket-api";
import isEqual from "lodash/isEqual";
import url from "url";
import { makeObservable, observable } from "mobx";
import { ipcRenderer } from "electron";
import type { Logger } from "../../common/logger";
import { once } from "lodash";
import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels";
enum TerminalColor {
RED = "\u001b[31m",
GREEN = "\u001b[32m",
YELLOW = "\u001b[33m",
BLUE = "\u001b[34m",
MAGENTA = "\u001b[35m",
CYAN = "\u001b[36m",
GRAY = "\u001b[90m",
LIGHT_GRAY = "\u001b[37m",
NO_COLOR = "\u001b[0m",
}
export interface TerminalApiQuery extends Record<string, string | undefined> {
id: string;
node?: string;
type?: string;
}
export interface TerminalEvents extends WebSocketEvents {
ready: () => void;
connected: () => void;
}
export interface TerminalApiDependencies extends WebSocketApiDependencies {
readonly hostedClusterId: string;
readonly logger: Logger;
}
export class TerminalApi extends WebSocketApi<TerminalEvents> {
protected size?: { width: number; height: number };
@observable public isReady = false;
constructor(protected readonly dependencies: TerminalApiDependencies, protected readonly query: TerminalApiQuery) {
super(dependencies, {
flushOnOpen: false,
pingInterval: 30,
});
makeObservable(this);
if (query.node) {
query.type ||= "node";
}
}
async connect() {
if (!this.socket) {
/**
* Only emit this message if we are not "reconnecting", so as to keep the
* output display clean when the computer wakes from sleep
*/
this.emitStatus("Connecting ...");
}
const authTokenArray = await ipcRenderer.invoke("cluster:shell-api", this.dependencies.hostedClusterId, this.query.id);
if (!(authTokenArray instanceof Uint8Array)) {
throw new TypeError("ShellApi token is not a Uint8Array");
}
const { hostname, protocol, port } = location;
const socketUrl = url.format({
protocol: protocol.includes("https") ? "wss" : "ws",
hostname,
port,
pathname: "/api",
query: {
...this.query,
shellToken: Buffer.from(authTokenArray).toString("base64"),
},
slashes: true,
});
const onReady = once((data?: string) => {
this.isReady = true;
this.emit("ready");
this.removeListener("data", onReady);
this.removeListener("connected", onReady);
this.flush();
// data is undefined if the event that was handled is "connected"
if (data === undefined) {
const lastData = window.localStorage.getItem(`${this.query.id}:last-data`);
if (lastData) {
/**
* Output the last line, the makes sure that the terminal isn't completely
* empty when the user refreshes.
*/
this.emit("data", lastData);
}
}
});
this.prependListener("data", onReady);
this.prependListener("connected", onReady);
super.connect(socketUrl);
}
sendMessage(message: TerminalMessage) {
return this.send(JSON.stringify(message));
}
sendTerminalSize(cols: number, rows: number) {
const newSize = { width: cols, height: rows };
if (!isEqual(this.size, newSize)) {
this.sendMessage({
type: TerminalChannels.RESIZE,
data: newSize,
});
this.size = newSize;
}
}
protected _onMessage({ data, ...evt }: MessageEvent<string>): void {
try {
const message = JSON.parse(data) as TerminalMessage;
switch (message.type) {
case TerminalChannels.STDOUT:
/**
* save the last data for reconnections. User localStorage because we
* 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 });
break;
case TerminalChannels.CONNECTED:
this.emit("connected");
break;
default:
this.dependencies.logger.warn(`[TERMINAL-API]: unknown or unhandleable message type`, message);
break;
}
} catch (error) {
this.dependencies.logger.error(`[TERMINAL-API]: failed to handle message`, error);
}
}
protected _onOpen(evt: Event) {
// Client should send terminal size in special channel 4,
// But this size will be changed by terminal.fit()
this.sendTerminalSize(120, 80);
super._onOpen(evt);
}
protected _onClose(evt: CloseEvent) {
super._onClose(evt);
this.isReady = false;
}
protected emitStatus(data: string, options: { color?: TerminalColor; showTime?: boolean } = {}) {
const { color, showTime } = options;
const time = showTime ? `${(new Date()).toLocaleString()} ` : "";
if (color) {
data = `${color}${data}${TerminalColor.NO_COLOR}`;
}
this.emit("data", `${time}${data}\r\n`);
}
protected emitError(error: string) {
this.emitStatus(error, {
color: TerminalColor.RED,
});
}
}