Skip to content

Channels ​

A channel is a name. There is no create step, no delete step, and no registry. The channel exists while at least one client is connected to it and stops existing when the last one leaves.

Addressing ​

The channel name is the last path segment of the URL:

URLChannel
wss://host/room-8f2a1croom-8f2a1c
wss://host/a/b/room-8f2a1croom-8f2a1c
wss://host/room-8f2a1c?debug=1room-8f2a1c
wss://host/room-8f2a1c#anythingroom-8f2a1c

Three consequences worth internalising:

Leading path segments are ignored

wss://host/alice/secret and wss://host/bob/secret are the same channel. Nested paths do not namespace anything. If you want structure in your names, put it in the segment itself: team-alice--room-secret.

  • Query strings do not distinguish channels. They are available to the server but play no part in routing, so ?user=alice is a label for your own code, not an isolation mechanism.
  • Fragments never reach the server at all. #state is browser-local by definition.

For HTTP POST the channel must be a single path segment — POST /room-8f2a1c works, POST /a/b/room-8f2a1c does not route.

Naming rules ​

  • Minimum six characters. Shorter names are rejected: the websocket is accepted and then immediately closed with code 1008.
  • URL-safe characters only, since the name lives in a path segment. Stick to A–Z a–z 0–9 - _ . and you will never think about encoding.
  • Case sensitive. Room-A and room-a are different channels.
  • No other restriction. Names are not reserved, registered or rate limited.

The name is the password

Anyone who knows or guesses the channel name can read everything on it and write anything to it. There is no other authentication. Generate names with a CSPRNG and treat them exactly as you would treat a bearer token.

js
// good: 128 bits of entropy
const channel = crypto.randomUUID();

// bad: guessable, enumerable, or shared in a URL people can screenshot
const channel = `user-${userId}`;

Lifecycle ​

  1. The first client connects. The server adds the socket to a set keyed by the channel name, creating the set if it did not exist.
  2. More clients connect and join the same set.
  3. Clients disconnect and are removed from it.
  4. The last client leaves and the set is deleted. Nothing about the channel exists any more — not a record that it once did.

Because channels are pure convention, a message sent to a channel with no listeners is silently discarded. There is no error, no buffering, and no way to find out afterwards. If a client needs the current state on joining, it has to ask for it — see request/response.

Discovering who is there ​

There is no membership API, no join/leave event, and no way to count listeners. If your app needs presence, build it in the payload layer: presence shows a heartbeat approach that is about fifteen lines.

The channel web page ​

Opening a channel URL in a browser — https://router.metapage.io/room-8f2a1c — serves a small page rather than a websocket. It renders a QR code of the channel URL and acts as a metaframe bridged onto the channel, which makes "scan this to connect your phone" a one-step affair.

Add ?hidden=true to suppress the QR code.

That page is a convenience, not part of the protocol. Your own clients never need to load it.

Released under the MIT License.