Просмотр исходного кода

Fix: Keypresses in Terminal throws exception (#71)

Jim Su 2 лет назад
Родитель
Сommit
a722f5c0b1
5 измененных файлов с 43 добавлено и 15 удалено
  1. 1 0
      frontend/.eslintrc
  2. 0 9
      frontend/package-lock.json
  3. 0 1
      frontend/package.json
  4. 36 5
      frontend/src/components/Terminal.tsx
  5. 6 0
      server/server.py

+ 1 - 0
frontend/.eslintrc

@@ -18,6 +18,7 @@
     {
     {
       "files": ["*.ts", "*.tsx"],
       "files": ["*.ts", "*.tsx"],
       "rules": {
       "rules": {
+        "no-underscore-dangle": "off",
         "jsx-a11y/no-static-element-interactions": "off",
         "jsx-a11y/no-static-element-interactions": "off",
         "jsx-a11y/click-events-have-key-events": "off",
         "jsx-a11y/click-events-have-key-events": "off",
         "react/no-array-index-key": "off"
         "react/no-array-index-key": "off"

+ 0 - 9
frontend/package-lock.json

@@ -26,7 +26,6 @@
         "vite": "^5.1.6",
         "vite": "^5.1.6",
         "vite-tsconfig-paths": "^4.3.2",
         "vite-tsconfig-paths": "^4.3.2",
         "web-vitals": "^2.1.4",
         "web-vitals": "^2.1.4",
-        "xterm-addon-attach": "^0.9.0",
         "xterm-addon-fit": "^0.8.0"
         "xterm-addon-fit": "^0.8.0"
       },
       },
       "devDependencies": {
       "devDependencies": {
@@ -9553,14 +9552,6 @@
       "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==",
       "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==",
       "peer": true
       "peer": true
     },
     },
-    "node_modules/xterm-addon-attach": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.9.0.tgz",
-      "integrity": "sha512-NykWWOsobVZPPK3P9eFkItrnBK9Lw0f94uey5zhqIVB1bhswdVBfl+uziEzSOhe2h0rT9wD0wOeAYsdSXeavPw==",
-      "peerDependencies": {
-        "xterm": "^5.0.0"
-      }
-    },
     "node_modules/xterm-addon-fit": {
     "node_modules/xterm-addon-fit": {
       "version": "0.8.0",
       "version": "0.8.0",
       "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz",
       "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz",

+ 0 - 1
frontend/package.json

@@ -22,7 +22,6 @@
     "vite": "^5.1.6",
     "vite": "^5.1.6",
     "vite-tsconfig-paths": "^4.3.2",
     "vite-tsconfig-paths": "^4.3.2",
     "web-vitals": "^2.1.4",
     "web-vitals": "^2.1.4",
-    "xterm-addon-attach": "^0.9.0",
     "xterm-addon-fit": "^0.8.0"
     "xterm-addon-fit": "^0.8.0"
   },
   },
   "scripts": {
   "scripts": {

+ 36 - 5
frontend/src/components/Terminal.tsx

@@ -1,9 +1,39 @@
 import React, { useEffect, useRef } from "react";
 import React, { useEffect, useRef } from "react";
-import { Terminal as XtermTerminal } from "@xterm/xterm";
-import { AttachAddon } from "xterm-addon-attach";
+import { IDisposable, Terminal as XtermTerminal } from "@xterm/xterm";
 import { FitAddon } from "xterm-addon-fit";
 import { FitAddon } from "xterm-addon-fit";
 import "@xterm/xterm/css/xterm.css";
 import "@xterm/xterm/css/xterm.css";
 
 
+class JsonWebsocketAddon {
+  _socket: WebSocket;
+
+  _disposables: IDisposable[];
+
+  constructor(socket: WebSocket) {
+    this._socket = socket;
+    this._disposables = [];
+  }
+
+  activate(terminal: XtermTerminal) {
+    this._disposables.push(
+      terminal.onData((data) => {
+        const payload = JSON.stringify({ action: "terminal", data });
+        this._socket.send(payload);
+      }),
+    );
+    this._socket.addEventListener("message", (event) => {
+      const { message } = JSON.parse(event.data);
+      if (message.action === "terminal") {
+        terminal.write(message.data);
+      }
+    });
+  }
+
+  dispose() {
+    this._disposables.forEach((d) => d.dispose());
+    this._socket.removeEventListener("message", () => {});
+  }
+}
+
 function Terminal(): JSX.Element {
 function Terminal(): JSX.Element {
   const terminalRef = useRef<HTMLDivElement>(null);
   const terminalRef = useRef<HTMLDivElement>(null);
   const WS_URL = import.meta.env.VITE_TERMINAL_WS_URL;
   const WS_URL = import.meta.env.VITE_TERMINAL_WS_URL;
@@ -32,11 +62,12 @@ function Terminal(): JSX.Element {
 
 
     if (!WS_URL) {
     if (!WS_URL) {
       throw new Error(
       throw new Error(
-        "The environment variable REACT_APP_TERMINAL_WS_URL is not set. Please set it to the WebSocket URL of the terminal server.",
+        "The environment variable VITE_TERMINAL_WS_URL is not set. Please set it to the WebSocket URL of the terminal server.",
       );
       );
     }
     }
-    const attachAddon = new AttachAddon(new WebSocket(WS_URL as string));
-    terminal.loadAddon(attachAddon);
+    const socket = new WebSocket(WS_URL as string);
+    const jsonWebsocketAddon = new JsonWebsocketAddon(socket);
+    terminal.loadAddon(jsonWebsocketAddon);
 
 
     return () => {
     return () => {
       terminal.dispose();
       terminal.dispose();

+ 6 - 0
server/server.py

@@ -57,6 +57,12 @@ async def websocket_endpoint(websocket: WebSocket):
                     continue
                     continue
 
 
                 agent_listener = asyncio.create_task(listen_for_agent_messages())
                 agent_listener = asyncio.create_task(listen_for_agent_messages())
+            if action == "terminal":
+                msg = {
+                    "action": "terminal",
+                    "data": data["data"]
+                }
+                await send_message_to_client(get_message_payload(msg))
             else:
             else:
                 if agent_websocket is None:
                 if agent_websocket is None:
                     await send_message_to_client(get_error_payload("Agent not connected"))
                     await send_message_to_client(get_error_payload("Agent not connected"))