fluffos/docs/concepts/general/websocket.md
Yucong Sun c914f03d66
Add local search and documentation guide for docs site (#1221)
* docs: add local full-text search and a contributor README

Add @easyops-cn/docusaurus-search-local to the Docusaurus site so the
docs get an offline search bar (index built at build time, no external
service). English and zh-CN pages are both indexed, and matched terms
are highlighted on the target page.

Add docs/README.md describing the Docusaurus setup, local dev/build
commands, search behavior, directory layout, and gotchas; exclude it
from the published site alongside CLAUDE.md. Point the root README's
docs/ entry at it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSzcESzU9947zkGzQ6SMmE

* docs: remove dead framework leftovers, fix index generation, complete the nav

Delete the VitePress (.vitepress/) and Jekyll (_layouts/, css/) leftovers,
the one-shot migration scripts (fix_md_header.py, fix_seealso.py), and the
stale keywords.json snapshot; prune the matching .gitignore entries and
docusaurus exclude patterns.

Rewrite gen_index.py for Docusaurus: it emitted dead .html links and
legacy 'layout: doc' frontmatter, choked on non-markdown entries, and
dropped nested categories — regenerating an index would have broken it.
It now emits the extension-less links the site actually uses, links
nested category indexes (restoring apply/* on the zh-CN index), and
refuses to run on the docs root. Fix update_index.sh's copy-paste titles
(zh-CN efun/build were titled 'APPLY'), stop it clobbering the
hand-written lpc/index.md, and cover cli/. Regenerated indexes pick up
the missing driver/ffi-plan entry. add_missing_efuns.py now takes the
keywords.json path as an argument instead of requiring a stale copy.

Move CNAME and the Google site-verification file into static/ so they
actually reach the published build output.

Complete the sidebar: link the CLI category to cli/index and add the
missing portbind/symbol/generate_keywords pages, and expose the
previously orphaned stdlib section under Reference.

Promote onBrokenLinks to 'throw' now the build is warning-free, and drop
the empty Demo section from the landing page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSzcESzU9947zkGzQ6SMmE

* docs: strip legacy 'layout: doc' frontmatter from all pages

Mechanical sweep removing the Jekyll-era 'layout: doc' line from every
doc page's frontmatter (Docusaurus ignores it), and the matching line
from the templates in docs/CLAUDE.md so new pages don't reintroduce it.
No content changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSzcESzU9947zkGzQ6SMmE

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-09 22:09:55 -04:00

9.5 KiB

title
WebSocket Support

WebSocket Support

FluffOS supports WebSocket connections, letting modern web browsers connect directly to your mud without a separate telnet client. Players can reach the game from a web page, and the driver can serve the client itself.

Overview

FluffOS WebSocket support includes:

  • Multiple subprotocolstelnet, ascii, and binary
  • Secure WebSocket (WSS) — TLS-encrypted connections
  • Built-in web client — shipped in src/www
  • Full telnet protocol — option negotiation, GMCP, MSP, MXP, and more over the telnet subprotocol
  • Static file serving — serve the client's HTML/JS/CSS from the driver
  • Compressionpermessage-deflate when the client supports it

WebSocket Server Configuration

WebSocket listeners are configured like any other port, with the websocket protocol keyword. A WebSocket port additionally requires the websocket http dir option, which names the directory of static files to serve. See the driver configuration for the full port syntax.

Basic WebSocket Port

# Non-secure WebSocket
external_port_1 : websocket 8080

# Directory of static files (the web client)
websocket http dir : www

Secure WebSocket (WSS)

For production, enable TLS on the port with a matching external_port_N_tls line. The cert=/key= pair must be on a single line (the config parser reads each line whole and does not support backslash continuation).

external_port_1 : websocket 8443
external_port_1_tls : cert=etc/cert.pem key=etc/key.pem
websocket http dir : www

Multiple WebSocket Ports

# Regular telnet
port number : 4000

# Non-secure WebSocket (development)
external_port_1 : websocket 8080

# Secure WebSocket (production)
external_port_2 : websocket 8443
external_port_2_tls : cert=etc/cert.pem key=etc/key.pem

websocket http dir : www

Static Files Directory

websocket http dir is passed to the web server as-is, so a relative path is resolved from the driver's working directory (use an absolute path if that is ambiguous). It should contain an index.html plus any CSS, JavaScript, and images. FluffOS ships a full-featured client in src/www.

# Relative to the driver's working directory
websocket http dir : www

# Absolute path
websocket http dir : /var/www/mud

WebSocket Subprotocols

A browser selects a subprotocol as the second argument to new WebSocket(). FluffOS offers three:

telnet

The full telnet protocol over WebSocket — option negotiation plus the mud protocols listed below (GMCP, MSP, MXP, etc.). This is the subprotocol the built-in client uses.

const ws = new WebSocket('ws://mud.example.com:8080', ['telnet']);

ascii

Plain text with no telnet framing — lightweight, useful for simple clients and debugging.

const ws = new WebSocket('ws://mud.example.com:8080', ['ascii']);

binary

Handled identically to the telnet subprotocol; retained for compatibility with older clients.

const ws = new WebSocket('ws://mud.example.com:8080', ['binary']);

A WebSocket port also serves the static files from websocket http dir over plain HTTP, so browsing to http://host:port/ returns index.html (this is ordinary file serving, not a WebSocket connection).

Built-in Web Client

FluffOS ships a modern web client in src/www (index.html). It provides:

  • Connection management — configurable address/port, ws:// or wss://, and subprotocol selection
  • Full telnet handling — IAC commands, subnegotiation, option negotiation, GMCP, MSP, NAWS, terminal type
  • A terminal UI — ANSI color, command history, auto-scroll, and a responsive, mobile-friendly layout

Deploying the Web Client

Make the client available under your websocket http dir:

# Copy the client into place
cp -r src/www /path/to/mudlib/www

# Or symlink it
ln -s /path/to/fluffos/src/www /path/to/mudlib/www
websocket http dir : www
external_port_1 : websocket 8080

Then browse to http://localhost:8080/.

Customizing the Web Client

The client is a single index.html. Two useful entry points:

  • setDefaultConfig() sets the default address, port, and subprotocol.
  • processGMCPData(data) handles incoming GMCP messages — extend it to drive status bars, maps, or custom panels.
setDefaultConfig() {
    this.protocolSelect.value = 'ws://';
    this.addressInput.value = 'mud.yourdomain.com';
    this.portInput.value = '8080';
    this.subprotocolSelect.value = 'telnet';
}

GMCP Over WebSocket

Over the telnet subprotocol the client negotiates GMCP (Generic Mud Communication Protocol), so the standard GMCP efuns work unchanged.

Server-Side GMCP

send_gmcp() sends to the current interactive object, so call it from within the user's object (it takes a single string containing the package name followed by JSON). has_gmcp() reports whether a given interactive object negotiated GMCP (defaulting to the current object).

// Called in the context of the user object
if (has_gmcp()) {
    send_gmcp("Char.Vitals { \"hp\": 100, \"mp\": 50, \"maxhp\": 100, \"maxmp\": 50 }");
    send_gmcp("Room.Info { \"name\": \"Market Square\", \"area\": \"City\" }");
}

Client-Side GMCP

The web client parses GMCP messages and dispatches them through processGMCPData():

processGMCPData(data) {
    switch (data.module) {
        case 'Char.Vitals':
            updateStatusBars(data.data);
            break;
        case 'Room.Info':
            updateRoomDisplay(data.data);
            break;
    }
}

Telnet Protocol Over WebSocket

The telnet subprotocol supports the standard telnet options and the common mud protocols. Negotiation is handled by the driver's telnet layer, identically to a raw telnet connection.

Supported telnet options

  • ECHO (1) — echo control
  • SUPPRESS_GO_AHEAD (3)
  • TERMINAL_TYPE (24)
  • NAWS (31) — window size
  • NEW_ENVIRON (39) — environment variables
  • CHARSET (42) — character set negotiation

Supported mud protocols

  • MSDP (69) — Mud Server Data Protocol
  • MSSP (70) — Mud Server Status Protocol
  • MCCP2 (86) — Mud Client Compression Protocol
  • MSP (90) — Mud Sound Protocol
  • MXP (91) — Mud eXtension Protocol
  • ZMP (93) — Zenith Mud Protocol
  • GMCP (201) — Generic Mud Communication Protocol

The built-in client performs all negotiation automatically.

Security Considerations

Use WSS in Production

Always serve production traffic over WSS (see TLS Support):

external_port_1 : websocket 8443
external_port_1_tls : cert=/etc/letsencrypt/live/mud.example.com/fullchain.pem key=/etc/letsencrypt/live/mud.example.com/privkey.pem

Validating Player Input

Input from a WebSocket client arrives through the same path as any interactive connection — the normal command and input handling on the user object — not through receive_message (which is the apply for output sent to the user). Validate and rate-limit player input where you process commands, exactly as you would for a telnet connection; there is nothing WebSocket-specific to do.

Performance

Compression

FluffOS advertises permessage-deflate and uses it with any client that supports it, reducing bandwidth for large or repetitive output.

Troubleshooting

Connection fails: confirm the port is open through any firewall.

sudo ufw allow 8080/tcp
sudo ufw allow 8443/tcp

Static files 404: verify websocket http dir points at the directory that contains index.html.

websocket http dir : www

WSS certificate errors: use a certificate the client trusts (e.g. Let's Encrypt); see TLS Support.

GMCP not working: confirm the client negotiated it.

if (!has_gmcp(user)) {
    write("Your client does not support GMCP.\n");
}

Enable WebSocket debug logging: the driver has a websocket debug category.

set_debug_level("websocket");

Testing WebSocket Connections

From a browser console:

const ws = new WebSocket('ws://localhost:8080', ['telnet']);
ws.onopen = () => console.log('Connected!');
ws.onmessage = (e) => console.log('Received:', e.data);
ws.send('look\n');

From the command line with wscat:

npm install -g wscat
wscat -c ws://localhost:8080 -s telnet

Reverse Proxying

You can place a WebSocket port behind a web server, for example to serve the mud client alongside a website or to terminate TLS at the proxy:

server {
    listen 80;
    server_name mud.example.com;

    location / {
        proxy_pass http://webapp:3000;
    }

    location /ws {
        proxy_pass http://fluffos:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}

See Also

References