tinymux/client/web/README.md
Stephen Dennis 4e94bb75aa docs: the browser client connects DIRECTLY to netmux — correct #2215's README
The README merged in #2216 claimed browsers cannot reach netmux and
that both transports require Hydra.  Wrong on the central point:
netmux serves WebSocket natively on its ordinary game ports
(mux/src/websocket.cpp, RFC 6455) with first-byte protocol detection
sharing each port between telnet and WebSocket (#1074/#2193,
proto_detect_window), and the handshake accepts both /wsclient and /
— which is exactly the path js/connection.js dials.  The minimal
browser deployment is netmux plus static files, no extra process;
Hydra is the OPTIONAL layer for session resume, multi-game links,
stored credentials, and gRPC-Web.

The error came from concluding absence out of a truncated grep: the
file listing was piped through head and mux/proxy's matches filled the
window before mux/src/websocket.cpp appeared.  A truncated listing is
not a complete listing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 22:12:06 -06:00

3.9 KiB

Titan for the browser

The HTML5 edition of the Titan client family (iOS, Android, and this). Static files only — no build step, no bundler, no external dependencies. Open index.html from any web server and you have a MUD client with tabs, saved worlds, triggers, timers, spawns, hooks, variables, MCP 2.1, and a searchable scrollback. Settings and worlds persist in the browser's localStorage.

How a browser reaches a game

Browsers cannot open raw TCP, but they do not need a middlebox either: netmux serves WebSocket natively on its ordinary game ports (mux/src/websocket.cpp, RFC 6455). A port is shared between telnet and WebSocket by first-byte protocol detection: a WebSocket client sends its HTTP GET immediately and is upgraded (/wsclient and / are both accepted); a classic telnet client waits for the server to speak and receives the banner after the proto_detect_window grace period (#1074/#2193 — runtime-settable, 0 disables detection for a telnet-only port). This works on plain and TLS ports alike, so wss: direct to the game is a two-line config away.

browser ── ws(s) ─────────────────> netmux game port  (shared with telnet)
browser ── ws(s) / gRPC-Web ──> hydra ── telnet ──> netmux
  • Telnet over WebSocket (js/connection.js) — the simple path, and the direct one. Point the client at the game port (or at a Hydra websocket listener, or any generic telnet-over-WebSocket bridge — nothing in this path cares which) and bytes flow as-is; the client's own telnet engine (js/telnet.js) handles negotiation, IAC GA prompts, NAWS, TTYPE, and CHARSET.

  • Hydra GameSession (js/hydra_connection.js) — the optional richer protocol, via Hydra, the connection proxy in mux/proxy: protobuf ClientMessage/ ServerMessage over a WebSocket with the hydra-gamesession subprotocol, falling back to gRPC-Web unary/streaming where WebSocket is blocked. This is what the /hconnect, /hswitch, /hlinks, /hgames, /hscroll commands drive (/hhelp lists them): multiple game links in one session, server-side scrollback fetch, and Hydra-stored credentials with auto-login. Session resume means a dropped browser connection picks up where it left off — the proxy holds the game link open across it, which the direct path cannot do.

Deploying

The minimal deployment is netmux plus static files — no extra process:

  1. Serve this directory from any static host — nginx, a CDN, an S3 bucket. There is nothing to compile.
  2. Point the client at the game's host and port. A page served over https may only open wss: (mixed-content rule), so use a TLS game port for any real deployment.

Add Hydra when you want what the proxy provides — session resume, multi-game links, stored credentials, gRPC-Web for WebSocket-hostile networks: cd mux/proxy && make, copy hydra.conf.example to hydra.conf, and enable the listeners you want. See that file for TLS certificates, the master key that encrypts stored credentials, health-check endpoints, and cors_origin (gRPC-Web listeners deny cross-origin requests by default; plain WebSocket is not subject to CORS).

Development

Run the tests with:

node test_web.js

No framework: the harness loads each script into a Node vm with a stub localStorage/WebSocket, and exercises the telnet engine, MCP reassembly bounds, trigger/timer/spawn/variable behavior, and the Hydra transport (session resume, gRPC-Web fallback, reconnect) as plain assertions. Keep new logic in the dependency-free js/*.js modules so it stays testable this way.

Status

Part of the 2.14 development tree. The released 2.13 server has neither the WebSocket listener nor Hydra, so a 2.13 game wanting browser play needs a generic telnet-over-WebSocket bridge and the simple transport above.