Terminal Streaming example in Javascript

Stream a TUI application over websockets using xterm.js.

View demo online (not mobile-friendly)
View full source on GitHub
Run the example

Running the example

You can run this self-contained example using Docker.

git clone https://github.com/scalesocket/scalesocket
cd scalesocket/examples/
docker compose up --build terminal

Then open http://localhost:5000/ in your browser.

Frontend code

The frontend is a single html file, index.html, using xterm.js. It connects with websockets to the server and shows a chat interface.

The javascript defines the terminal windget and connects it to a websocket:

const $ = document.querySelector.bind(document);
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
const params = new URLSearchParams(document.location.search);
const room = params.get("room") ?? 'default';

// create a terminal
const term = new Terminal({ disableStdin: false });
const element = $('#terminal');

// connect to websocket server and room based on URL
const ws = new WebSocket(`${protocol}:${window.location.host}/${room}`);
const attachAddon = new AttachAddon.AttachAddon(ws);

// attach the socket to term
term.loadAddon(attachAddon);
term.open(element);

ws.addEventListener("open", (event) => {
   $('.xterm-helper-textarea').focus();
   // some magic escape sequence to make input work for late-joiners
   term.write('\x1b)0\x1b7\x1b[?47h\x1b[1;24r\x1b[m\x1b[4l\x1b[?1h\x1b=');
});

Backend code

There is no backend code, besides running a terminal application. The backend in this example is a wrapped snake process.

Backend server

The backend is the ScaleSocket server.

We want to:

  • let participants join rooms based on URL
  • start a new snake process when a new user connects
  • host a static html file

To do this, start ScaleSocket using:

scalesocket --addr 0.0.0.0:5000\
    --binary\
    --staticdir /var/www/public/\
    # launch using unbuffer to expose a pty
    bash -- -c 'TERM=xterm-color unbuffer -p nsnake'

Note that we are using the --binary flag to enable binary mode. Furthermore, unbuffer(1) and TERM let snake work as-if it was run in an interactive terminal.

How does it work?

The frontend connects to the server using websockets. The backend spins up a new snake process.

When the frontend terminal widget sends a key-press, ScaleSocket passes it directly to the stdin of snake.

Since snake writes the screen to the terminal using stdout, ScaleSocket forwards it back to all connected clients.

Was this page helpful?