Skip to content

holiber/jabterm

Repository files navigation

JabTerm

CI Test coverage

Just Another Browser Terminal — run your real terminal in browser

JabTerm demo

Features

  • Single <JabTerm> React component
  • Multiple independent terminals on one page without conflicts
  • Node.js server powered by node-pty — real shell, real colors
  • Same-origin WebSocket proxy for HTTPS / Cloudflare / tunnel deployments
  • CLI binary: npx jabterm-server --port 3223

Quick Start

1. Install

pnpm add jabterm @xterm/xterm

2. Start the server

npx jabterm-server --port 3223

Or programmatically:

import { createJabtermServer } from "jabterm/server";

const server = createJabtermServer({ port: 3223, host: "127.0.0.1", path: "/ws" });
await server.listen();
// server.address() -> { address, family, port }
// server.close() to shut down deterministically

3. Render the component

import { JabTerm } from "jabterm/react";
import "@xterm/xterm/css/xterm.css";

function App() {
  return (
    <div style={{ width: "100%", height: 400 }}>
      <JabTerm wsUrl="ws://localhost:3223" />
    </div>
  );
}

Local development

pnpm install
pnpm dev:demo

This starts:

  • the terminal WebSocket server on ws://127.0.0.1:3223
  • the demo page on http://127.0.0.1:3224

CI also regenerates these assets on pushes to main (so contributors typically don't have to).

API Reference

<JabTerm> Props

Prop Type Default Description
wsUrl string required Full WebSocket URL (ws:// or wss://)
onTitleChange (title: string) => void Fires when the shell sets a window title
onOpen () => void Fires when the WebSocket becomes open
onClose (ev: CloseEvent) => void Fires when the WebSocket closes
onError (ev: Event) => void Fires on WebSocket errors
captureOutput boolean true Capture output for imperative read*() methods
maxCaptureChars number 200000 Max captured output size (characters)
className string CSS class for the outer container
fontSize number 13 Font size in pixels
fontFamily string system monospace Font family
accessibilitySupport "on" | "off" | "auto" xterm accessibility support mode (set to "on" if you need to read terminal text from the DOM for automation)
theme { background?, foreground?, cursor? } { background: "#1e1e1e" } xterm.js theme overrides

The outer container also exposes data-jabterm-state="connecting|open|closed" to make UI tests (e.g. Playwright) wait reliably.

Imperative API (JabTermHandle)

<JabTerm ref={...} /> exposes:

  • focus(), fit(), resize(cols, rows), paste(text), send(data)
  • getXterm() to access the underlying xterm instance
  • readAll(), readLast(n), readNew(), getNewCount() for testing/automation

createJabtermServer(options?)

const server = createJabtermServer({
  port: 3223,          // default: 3223 (use 0 for ephemeral)
  host: "127.0.0.1",   // default: 127.0.0.1
  path: "/ws",         // default: "/"
  shell: "/bin/bash",  // default: resolves from $SHELL / OS defaults
  cwd: "/home/user",   // default: $HOME
  env: { FOO: "bar" }, // extra env for spawned PTYs
  strictPort: false,   // default: false — fail if port is busy (ignored for port 0)
});

await server.listen();
console.log(server.address()); // { address, family, port }

The WebSocket endpoint supports per-session routing: connect to ${path}/:terminalId (e.g. /ws/my-terminal).

setDocumentTitle(title)

Optional client-side helper:

import { setDocumentTitle } from "jabterm/react";

Security notes

jabterm/server spawns real shell processes. For production deployments:

  • Keep it bound to loopback (127.0.0.1) and expose only behind an authenticated app/reverse-proxy.
  • Consider adding origin checks and/or a token handshake for WebSocket connections.

createTerminalProxy(options)

Creates a WebSocketServer in noServer mode for same-origin proxying:

import { createTerminalProxy } from "jabterm/server";

const proxyWss = createTerminalProxy({
  upstreamUrl: "ws://127.0.0.1:3223",
});

httpServer.on("upgrade", (req, socket, head) => {
  if (new URL(req.url, "http://localhost").pathname === "/ws/terminal") {
    proxyWss.handleUpgrade(req, socket, head, (ws) => {
      proxyWss.emit("connection", ws, req);
    });
  }
});

Architecture

See docs/ARCHITECTURE.md for internals

License

MIT

About

JabTerm — Just Another Browser Terminal. Drop-in React component + Node.js server for embedding real terminals in web apps.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors