xtermjs / xterm.js

A terminal for the web
https://xtermjs.org/
MIT License
17.55k stars 1.62k forks source link

Use more than 1 terminal on a web page, terminal send data wrong #4132

Closed ljd1995 closed 2 years ago

ljd1995 commented 2 years ago

Details

Steps to reproduce

Hello, I got exception when i build multi terminal using xterm.js, when a single page has more than one terminal, the terminal send data will always be on the last terminal

First, I use vue3 composition api to build my project.

I have 2 terminal on a web page, main page code is

<template>
  <div class="content">
    <term1 :key="1"></term1>
    <term2 :key="2"></term2>
  </div>
</template>
<script lang="ts" setup>
import term1 from './components/term1.vue'
import term2 from './components/term2.vue'
</script>
<style scoped>
.content {
  height: 100vh;
  display: flex;
  width: 100%;
}
</style>

term1.vue code is

<template>
  <div id="terminal-1" :key="1" class="console"></div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import "xterm/css/xterm.css";
import { Terminal } from "xterm";

const terminal = ref();

const initSocket = (socketURI: string) => {
  const socket = new WebSocket(socketURI);
  socket.onopen = () => {
    initTerm(socket);
  };
  socket.onmessage = (ev) => {
    const data: string = ev.data;
    const msg = JSON.parse(data);
    if (msg.type == "content") {
      terminal.value.write(msg.data);
    }
  };
};

const height = Math.floor(window.screen.availHeight / 20);

const initTerm = (socket: WebSocket) => {
  try {
    terminal.value = new Terminal({
      fastScrollModifier: "ctrl",
      rightClickSelectsWord: false,
      rows: height,
      convertEol: true,
      scrollback: 8000,
      fontSize: 15, 
      disableStdin: false,
      cursorStyle: "block", 
      cursorBlink: true,
      tabStopWidth: 4,
      fontFamily:
        "Menlo For Powerline,Consolas,Liberation Mono,Menlo,Courier,monospace",
      theme: {
        foreground: "#d2d2d2", 
        background: "#2b2b2b", 
        cursor: "#adadad",
        black: "#000000",
        red: "#d81e00",
        green: "#5ea702",
        yellow: "#cfae00",
        blue: "#427ab3",
        magenta: "#89658e",
        cyan: "#00a7aa",
        white: "#dbded8",
        brightBlack: "#686a66",
        brightRed: "#f54235",
        brightGreen: "#99e343",
        brightYellow: "#fdeb61",
        brightBlue: "#84b0d8",
        brightMagenta: "#bc94b7",
        brightCyan: "#37e6e8",
        brightWhite: "#f1f1f0",
      },
    });
    terminal.value.open(
      document.getElementById("terminal-1") as unknown as HTMLElement
    );
    terminal.value.onData((data) => {
      socket.send(
        JSON.stringify({
          data: data,
          type: "content",
        })
      );
    });
  } catch (e) {
    console.log(e);
    console.log("init term failed");
  }
};
onMounted(() => {
  initSocket("ws://127.0.0.1:8000/api/v1/ws?host_id=3");
});
</script>
<style scoped>
.console {
  width: 49%;
  display: flex;
  height: calc(100vh - 50px);
  max-height: calc(100vh -50px);
  margin-right: 15px;
}
</style>

term2.vue code is

<template>
  <div id="terminal-2" :key="2" class="console"></div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import "xterm/css/xterm.css";
import { Terminal } from "xterm";

const terminal = ref();

const initSocket = (socketURI: string) => {
  const socket = new WebSocket(socketURI);
  socket.onopen = () => {
    initTerm(socket);
  };
  socket.onmessage = (ev) => {
    const data: string = ev.data;
    const msg = JSON.parse(data);
    if (msg.type == "content") {
      terminal.value.write(msg.data);
    }
  };
};

const height = Math.floor(window.screen.availHeight / 20);

const initTerm = (socket: WebSocket) => {
  try {
    terminal.value = new Terminal({
      fastScrollModifier: "ctrl",
      rightClickSelectsWord: false,
      rows: height,
      convertEol: true,
      scrollback: 8000,
      fontSize: 15, 
      disableStdin: false,
      cursorStyle: "block", 
      cursorBlink: true,
      tabStopWidth: 4,
      fontFamily:
        "Menlo For Powerline,Consolas,Liberation Mono,Menlo,Courier,monospace",
      theme: {
        foreground: "#d2d2d2", 
        background: "#2b2b2b", 
        cursor: "#adadad",
        black: "#000000",
        red: "#d81e00",
        green: "#5ea702",
        yellow: "#cfae00",
        blue: "#427ab3",
        magenta: "#89658e",
        cyan: "#00a7aa",
        white: "#dbded8",
        brightBlack: "#686a66",
        brightRed: "#f54235",
        brightGreen: "#99e343",
        brightYellow: "#fdeb61",
        brightBlue: "#84b0d8",
        brightMagenta: "#bc94b7",
        brightCyan: "#37e6e8",
        brightWhite: "#f1f1f0",
      },
    });
    terminal.value.open(
      document.getElementById("terminal-2") as unknown as HTMLElement
    );
    terminal.value.onData((data) => {
      socket.send(
        JSON.stringify({
          data: data,
          type: "content",
        })
      );
    });
  } catch (e) {
    console.log(e);
    console.log("init term failed");
  }
};
onMounted(() => {
  initSocket("ws://127.0.0.1:8000/api/v1/ws?host_id=4");
});
</script>
<style scoped>
.content {
  height: 100vh;
  display: flex;
  width: 100%;
}
.content > .el-tabs__content {
  padding: 32px;
  color: #6b778c;
  font-size: 32px;
  font-weight: 600;
}
.console {
  width: 49%;
  display: flex;
  height: calc(100vh - 50px);
  max-height: calc(100vh -50px);
  margin-right: 15px;
}
</style>

And backend I use Python FastAPI and paramiko to build websocket endpoint

aa

In the gif, I input command in left terminal(term1.vue), but command has send to right terminal(term2.vue), I can't understand why this problem occurs,please give me some help

ljd1995 commented 2 years ago

I used the backend to print the log to verify the problem, and found that the websocket that sent the command was inconsistent, and the terminal that wrote the command seemed to be the last terminal.

image
jerch commented 2 years ago

Looks like a scoping issue of your terminal.value, prolly getting overridden by the second terminal setup (just a guess, idk vue).

This is not an xterm.js issue, please refer to vue documentation how to setup this correctly.