diff --git a/frontend/.eslintrc b/frontend/.eslintrc index 8e35bfe128..a68fa7edc9 100644 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -18,6 +18,7 @@ { "files": ["*.ts", "*.tsx"], "rules": { + "no-underscore-dangle": "off", "jsx-a11y/no-static-element-interactions": "off", "jsx-a11y/click-events-have-key-events": "off", "react/no-array-index-key": "off" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7418d526c4..355b5fbf45 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,7 +26,6 @@ "vite": "^5.1.6", "vite-tsconfig-paths": "^4.3.2", "web-vitals": "^2.1.4", - "xterm-addon-attach": "^0.9.0", "xterm-addon-fit": "^0.8.0" }, "devDependencies": { @@ -9553,14 +9552,6 @@ "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", "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": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5aaa8976ad..fd66ff482a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,6 @@ "vite": "^5.1.6", "vite-tsconfig-paths": "^4.3.2", "web-vitals": "^2.1.4", - "xterm-addon-attach": "^0.9.0", "xterm-addon-fit": "^0.8.0" }, "scripts": { diff --git a/frontend/src/components/Terminal.tsx b/frontend/src/components/Terminal.tsx index 12346e04b2..a50692ba8e 100644 --- a/frontend/src/components/Terminal.tsx +++ b/frontend/src/components/Terminal.tsx @@ -1,9 +1,39 @@ 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 "@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 { const terminalRef = useRef(null); const WS_URL = import.meta.env.VITE_TERMINAL_WS_URL; @@ -32,11 +62,12 @@ function Terminal(): JSX.Element { if (!WS_URL) { 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 () => { terminal.dispose(); diff --git a/server/server.py b/server/server.py index 24371e4cfe..d5b3086b34 100644 --- a/server/server.py +++ b/server/server.py @@ -57,6 +57,12 @@ async def websocket_endpoint(websocket: WebSocket): continue 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: if agent_websocket is None: await send_message_to_client(get_error_payload("Agent not connected"))