The optional WebSocket listener is a second transport into the same IRC adapter. It does not run a gateway process and it does not have separate users, channels, history, cursors, or permissions.
IRC_WEBSOCKET_LISTEN enables the listener. The endpoint path is /irc and
query parameters are rejected so credentials cannot be smuggled into commonly
logged URLs. Without TLS it follows the same safe default as the IRC listener:
loopback is allowed and a non-loopback address is rejected unless
IRC_ALLOW_INSECURE_REMOTE=true is explicitly set. A remote plaintext override
does not advertise SASL PLAIN.
Deployments can set the same address and origin allowlist in the versioned
[listeners] and [websocket] TOML sections. See
server configuration; the environment examples below
remain supported as explicit overrides.
The same exact origin list authorizes the reference web client to perform
authenticated avatar GET requests against a separate control-listener port.
It does not enable cross-origin mutations or any other control API read. See
account avatars.
When IRC_TLS_CERT and IRC_TLS_KEY are configured, both the ordinary IRC and
WebSocket listeners use that identity. The WebSocket URL is then wss://, not
ws://. The current process-level TLS setting cannot make one listener
plaintext and the other encrypted.
Browsers always send an Origin header. List every web application allowed to
connect in the comma-separated IRC_WEBSOCKET_ORIGINS value:
IRC_WEBSOCKET_ORIGINS=https://chat.example.net,http://localhost:5173Entries are exact serialized HTTP(S) origins: scheme, host, and optional port,
with no path, query, fragment, wildcard, or user information. An absent or empty
allowlist denies every request carrying Origin. Native and terminal clients
normally omit that header and remain allowed. Origin checks are an abuse and
cross-site boundary, not authentication; private servers should still require
accounts and TLS. A reverse proxy must preserve the browser's Origin header.
The handshake must request at least one of the standard IRCv3 subprotocols:
text.ircv3.netsends and receives WebSocket text messages.binary.ircv3.netsends and receives WebSocket binary messages containing UTF-8 IRC data.
Both are supported. If a client offers both, the server selects the first supported token in the client's preference order. A message of the wrong WebSocket type closes the connection with status 1003.
Every WebSocket message contains exactly one IRC protocol line and must not
contain CR or LF. Conversely, every server IRC line is one WebSocket message
without a line terminator. Embedded line endings close the connection with
status 1002. The same bounded IRC line limit used by the stream listener is
enforced during WebSocket assembly, including fragmented messages. Only UTF-8
is accepted because the server advertises UTF8ONLY.
The implementation follows the IRCv3 WebSocket transport specification.
const irc = new WebSocket("wss://irc.example.net:8097/irc", [
"text.ircv3.net",
"binary.ircv3.net",
]);
irc.addEventListener("open", () => {
irc.send("CAP LS 302");
irc.send("NICK browser");
irc.send("USER browser 0 * :Browser client");
irc.send("CAP END");
});
irc.addEventListener("message", ({ data }) => {
console.log(data);
});This is deliberately only transport setup. A real client should negotiate capabilities, use SASL on WSS for durable identity, retain its device credential securely, page history by message ID, and advance read and delivery markers only in response to the appropriate user/client state.
The repository's reference web client implements those behaviors without a gateway and documents its browser-storage trust boundary.