Skip to content

Client Web Protocol

Kit Oliynyk edited this page Nov 5, 2026 · 3 revisions

TL;DR: The web client and the server talk over one WebSocket with JSON frames {"command": name, "args": [...]} in both directions. The server sends text (console_out), a state object after every command (prompt) and about thirty other events. The client sends typed lines (console_in) and about twenty settings and editor commands. Output text carries a few HTML-like tags that the client turns into links, menus and colours. Use when: you write a client for DreamLand, add a server event or an RPC command, change what a prompt field holds, or emit a web tag from C++ or Fenia. Source of truth: mudjs/src/websock.js (transport), mudjs/src/manip.js (tags), plug-ins/iomanager/descriptor.cpp (wsHandlePayload, RPCRUN), plug-ins/iomanager/interprethandler.cpp (build_prompt), plug-ins/web/impl.cpp and plug-ins/comm/who.cpp (prompt listeners), plug-ins/output/mudtags.cpp (tag output), plug-ins/descriptor/websocketrpc.cpp (WebList, list frames) Verified: 2026-11-04 against dreamland_code 64cc9099, mudjs 990bf6d (event and command lists generated from both trees); run_left, config_state and config_changed rows 2026-11-05 against dreamland_code f0587220

Transport

  • URL: wss://dreamland.rocks/dreamland for the game. A client served from the page with #build in the address uses the build server instead (see Client-Web-Development). The engine listens on a plain WebSocket port (1234, dreamland_world/ports/1234.xml) and expects the subprotocol binary. The TLS and the /dreamland path come from a reverse proxy in front of it (unverified, not in the repos).
  • Frames are UTF-8 JSON text, one object per message: {"command": "console_in", "args": ["look\n"]}. The client sets binaryType = 'arraybuffer' and decodes with TextDecoder.
  • The server drops a whole frame, without closing the socket, if any argument is an array or an object. Arguments must be scalars (string, number, boolean). Structured data travels as one JSON string, or as base64 (macros_put, triggers_put, script_put).
  • One message may carry at most 512 KiB of payload. A larger one closes the socket.
  • The client names itself by version. The server's first frames are version (["DreamLand Web Client/2.1", nonce]) and msp ([true]). If the version string differs from the one built into the client, the client prints a message, drops its resume token and closes. Bump both strings together when the protocol breaks.
  • The first frame the client sends on a fresh socket is the answer to the codepage menu: console_in with 1, which selects KOI8-R, the encoding the client decodes (telnet.js maps KOI8 letters back to telnet byte names). A client of your own can pick 7 for UTF-8 instead. The language menu that follows is a normal game prompt.

Some commands need no character and work at the login prompt: console_in, resume, account_enter, chat_subscribe, list_subscribe, ping, check_name, client_src. Every other command is looked up in the RPC registry and needs a logged-in character. Without one the server logs "no character for non-console-in RPC" and ignores the frame.

Session resume

A phone tab is suspended after a few seconds in the background and its socket dies. To make that invisible:

  1. The server puts a short-lived token into every prompt (resume field) and into resume_ok.
  2. The client keeps it in sessionStorage (it dies with the tab).
  3. On a new socket the client sends resume <token> instead of logging in. The server answers resume_ok [nextToken] or resume_failed [reason].
  4. reason is final (token dead: quit, expired or the body left the world, go to login) or retry (the old socket is not linkdead yet, try again after a pause, up to five times).
  5. Any frame from the playing socket keeps the resume window open.

The client also sends ping every 25 seconds while in the world and after returning from the background. The server answers pong. Any incoming frame counts as proof of life. Fifteen seconds of silence closes the socket and starts a resume. Lines typed while the socket is in doubt are held (at most 20, for at most 15 seconds) and replayed after the resume lands.

Server to client: events

Generated from the handlers in mudjs/src and checked against the C++ senders. list is the exception: mudjs has no handler for it yet. Names ending in _data/_saved are built from the request name in C++ (<name>_get answers <name>_data, <name>_put answers <name>_saved).

Event Arguments Meaning
console_out text Game text with ANSI colour codes or the tags below.
prompt object State after each command. See Prompt fields.
version version, nonce Protocol version and the per-socket nonce for [cmd=] tags.
msp on Sound lines (MSP) may be played.
notify text Pop-up notice.
alert text Modal alert().
editor_open text, arg Open the text editor (answer to webedit).
cs_edit subject, body Open the Fenia script editor (answer to cs edit).
resume_ok next token Resume accepted.
resume_failed reason final or retry.
pong none Answer to ping. The client has no handler, any frame is enough.
nanny_step step Login dialogue reached a real question (sent by game scripts).
account_enter_ok / account_enter_failed none Answer to account_enter.
check_name_result {name, ok, reason} Answer to check_name.
account_chars {current, account, chars} Characters of the account.
chat object One chat message: id (channel command name, stable across languages), kind (room, personal, area, world, race, group, mob, quest), dir (in or out), text (as printed, with colour tags), at (unix time), peer ({name, anon, npc, key}), and area, quest, step where they apply. Sent only after chat_subscribe.
chat_state {on, playing, who} Answer to chat_subscribe.
list object Structured copy of a list a command prints. See List frames. Sent only after list_subscribe.
config_schema object Settings schema and current values.
config_state {playing, who, screenreader} Sent on entering the world (playing: 1, the character's name, screenreader: 1 or 0) and on leaving it (playing: 0 only).
config_changed {key, value} A setting changed (from the game or another tab). Sent only after the client asked for config_schema, except screenreader, which is always sent.
config_result {key, ...} Answer to config_set.
run_left {path} A run stopped. path is the rest of the speedwalk as run takes it (3n2eN), empty when the run finished. A run dropped without a stop message (input flush, a trap) sends nothing. Written straight to the socket, so it arrives before the room text of the same pulse.
aliases_char / aliases_acct / aliases_acct_saved object Aliases of the character and of the account.
macros_data / macros_saved object Macros.
triggers_data / triggers_saved object Client triggers.
script_data / script_saved object The player's client script.
autobuff_list object Autobuff lines (with a seq that matches the request).
widget_help_result {what, arg, text} Text for a help sheet, see widget_help.
boss_phase kind, tier A boss changed phase (control or panic flashes the screen). Sent by game scripts.

Client to server: commands

Command Arguments Notes
console_in line with trailing newline One typed line. Character-less.
resume token Character-less.
account_enter token One-use entry token minted by the site after login. Character-less. Never travels as console_in, so it stays out of the command log.
ping none Character-less.
check_name candidate name Character-less. Availability only.
client_src label Character-less. The ?src= label of the link the visitor came by, kept for newcomer reports. Up to 32 characters of a-z 0-9 - _.
chat_subscribe 1 or off Character-less.
list_subscribe none or off Character-less. Turns list frames on for this socket, or off. No answer frame.
account_chars none
config_schema none
config_set key, value
aliases_get, aliases_acct_get none
aliases_acct_put base64
macros_get none
macros_put acct or char, base64 At most 32 KiB decoded per list, one put per second.
triggers_get none
triggers_put acct or char, base64
script_get none
script_put base64
autobuff none
autobuff_prefs list, tag or set, order, off, own, tag
editor_save text Result of editor_open.
cs_eval subject, body Post a Fenia script from the editor.
widget_help what, arg what is quest, affects or whois (name of up to 32 letters). Runs the info command and returns the text as widget_help_result. It is not a second console_in.

New server-side RPC commands are one RPCRUN(name) block (rpccommandmanager.h) that receives the character and a list of string arguments.

List frames

A client that sent list_subscribe gets a list frame from these commands: skills, glist (all groups, or one group), help with several matches, socials, commands (both views), vault, the note boards' list, areas and quest request. The text output does not change. The frame arrives before the text, because the text goes through the output buffer.

{"kind": "skills", "title": "", "rows": [{
  "label": "bash, 75%", "id": "bash", "section": "6",
  "data": {"level": 6, "available": true, "learned": 75, "real": 75, "moves": 13},
  "actions": [{"verb": "practice", "cmd": "practice bash"}, {"verb": "help", "cmd": "help 676"}]
}]}
  • kind names the list: skills, glist, group, help, socials, commands, vault, notes, areas, quest_request.
  • label, title and section are in the reader's language, colour codes stripped. id, section and data are optional.
  • cmd uses English command names, so it works in every language.
  • Verbs in use: help, read, practice, open, do, social_at, get, filter, request. A client shows its own label for a verb it knows and the plain command otherwise. open and filter answer with another list. social_at still needs a target name appended.
  • Reserved for future lists, not sent yet: look, whois, tell, path, buy.
  • vault rows with a section are type filters, rows without one are stored items.
  • data keys by kind: skills has level and available always, plus learned, real and mana or moves when available. group has usable. areas has low and high for areas with levels. vault item rows have level. notes has unread.

C++: WebList in plug-ins/descriptor/websocketrpc.h (row, action, data, send, active). It checks the subscription itself. Fenia has no way to send this frame yet: ch.writeWSCommand accepts only scalar arguments and does not check list_subscribe.

Prompt fields

prompt arrives after every command that ends in a prompt. Fixed fields are always present. Block fields are sent only when their value changed since the last prompt. When a block empties, its value is the string none, sent once, and the client hides that panel. The client merges each frame into window.mudprompt.

Fixed fields (build_prompt):

Field Meaning
hit, max_hit, mana, max_mana, move, max_move Vitals.
vnum, area Room vnum and area file name, for the map.
sex neutral, male, female or either.
lang Display language: en, ru or ua.
resume Resume token (web only).
fight Health percent of the current opponent, 0 out of a fight.
rage, blind, faerie, hide, fade, poison, mind 0 or 1. Screen effects. They are flavour only, the real state is in the text and the affects panel.

Block fields (listeners in plug-ins/web/impl.cpp and plug-ins/comm/who.cpp):

Field Content
room, zone Names in the viewer's language.
exits {e: visible letters, h: hidden letters, l: e/r/u letter set}.
sect {i: icon, n: name}.
time {h: hour, tod: time of day word, l: light word}.
date {d: day, m: month name, y: year, s: season}.
w {i: weather icon, m: weather text}.
group {ln: leader name, leader: member, pc: [member], npc: [member]}. A member is {sees, hit, max_hit, health, hit_clr, level, tnl}.
det, enh, pro, trv, mal, cln, cd Affect columns (detect, enhance, protect, travel, malediction, clan, cooldown): lists of {n: label, d: ticks left}. d is -1 for permanent.
affsn Flat list of the English names of active affects, used by the autobuff gate.
p1 {ps: [6 permanent stats], cs: [6 current stats]} in the order of the stat table.
p2 {h: hitroll, d: damroll, a: armor, s: saves, pos: position, posf: position flags}.
q Personal quest {t: time left, i: info line}.
aq Area quest {t, i}.
sp 1 when the character has any castable spell (the spells button).
fcmd List of combat command names for the buttons, empty out of combat.
who {t: total, p: [{n, s, r, cn, cc}]}: name, sex letter, race (two letters), clan letter and colour class.

The same object is sent to telnet clients that negotiated GMCP. See Client-Mudlet-and-GMCP.

Output tags

The text in console_out is plain text plus a few HTML-like tags. The server only emits them for web readers. Telnet readers get the plain label. The { markup that produces them is described in Text-Markup. mudjs/src/manip.js turns the tags into elements.

Tag Client result
<c c='fgbg'>text</c> A span with that class. The class is fg + b (bright) or d (dark) + the colour letter, for example fgbg bright green, fgdr dark red.
<hc>look plaque</hc> Click sends the text as a command.
<hc cmd='path 3001'>label</hc> Click sends cmd instead of the label. Only shapes on the allowlist SAFE_HC_COMMANDS are honoured, others fall back to the label.
<hh>quest</hh>, <hh id='333'>text</hh> Click sends help <id or text>.
<hl>https://...</hl> External link in a new tab. Text must start with http.
<hg>name</hg> Click sends glist <name>.
<hs>6e10nd</hs> Click sends run <path>.
<m c="drop $,wear $" l="repair $" i="1773732900">short</m> Item menu. c entries sit above the divider, l entries (only valid in this room, for example at a repairer) below it. $ becomes the item id i. The entries are command names in the viewer's language.
[map=midgaard.are] Button that opens the zone page on the maps site.
[read=sign знак,see=sign] Clickable word that sends examine 'sign знак'. The see word must be one of the words before the comma. The tag keeps its historical name read.
[look=door,see=door] Clickable word that sends look door, used for exits.
[cmd=quest $1,see=13,nonce=r4U78eAa] Clickable text that sends the command with $1 replaced by the see text. The nonce must equal the one from version, otherwise the tag is left as plain text. Built by web_cmd(ch, cmd, fmt) in C++ and .webcmd(ch, cmd, label) in Fenia.

The nonce exists because player-typed text can reach other players with tags intact. A forged [cmd=...] would have to guess the nonce of the reader's socket.

Rules for new tags

  • Emit tags only inside {Iw ... {Ix so telnet and screen-reader readers keep a plain-text twin. See Accessibility.
  • Anything the player must do stays reachable as typed text. A click is a shortcut.
  • A new cmd shape for <hc> needs an entry in SAFE_HC_COMMANDS, anchored and as narrow as the command that emits it.
  • Strip {h tags from any text a player typed before sending it to other players.

See also

Clone this wiki locally