Skip to content

Guarantees & limits ​

The precise contract. Build against this rather than against observed behaviour.

What is guaranteed ​

Sender exclusion. A message sent over a websocket is never delivered back to the socket that sent it. Enforced by socket identity, never by anything in the payload.

Payload fidelity. Messages are delivered byte for byte. The router does not parse, validate, transform, truncate or re-encode. A text frame arrives as a text frame and a binary frame arrives as a binary frame. The only messages that do not arrive untouched are the ones that do not arrive at all — ping-prefixed keepalives.

Per-sender ordering to a given receiver. Two messages from the same sender arrive at any particular receiver in the order they were sent.

Exactly-once delivery. A message reaches each eligible socket exactly once, however many sockets are on the channel.

Immediate channels. No provisioning. A channel is live the moment a client connects to the name.

Binary and text are both first-class. Neither is converted to the other.

What is not guaranteed ​

Delivery. Best-effort only. No acknowledgements, no retries, no error to the sender when delivery fails. A message sent to a channel with no listeners is discarded silently.

Ordering between different senders. Two clients sending concurrently may be observed in either order, and two receivers may observe them in different orders. There is no global sequence.

Durability. Nothing is stored, ever. No history, no replay, no catch-up for late joiners.

Survival across restarts. A deploy or crash closes every connection with code 1001 ("going away"). Reconnecting clients get a working channel again, but missed messages are gone. This is routine, not exceptional — clients must reconnect.

Reach across hosts. Two hosts are two independent routers. A channel on one is unrelated to the same name on the other. See Architecture.

Latency bounds. Typically well under a millisecond plus network, but nothing is promised and nothing is prioritised.

Backpressure. Nothing throttles senders. A slow receiver is dropped from individual messages rather than slowing anyone else down — see below.

Limits ​

Channel name lengthminimum 6 characters; no maximum beyond URL limits
Message size1 MiB by default, changeable live from the admin panel, hard ceiling 16 MiB
Frame typetext and binary both routed
Unflushed data per socket8 MiB, after which messages to that socket are dropped
Messages per secondnot rate limited; you are expected to be reasonable
Clients per channelnot limited; fan-out cost grows linearly
Concurrent channelsnot limited
Message retentionnone

On the two size limits ​

The configurable limit (admin panel, persisted) is the policy you choose. Exceeding it closes the socket with code 1009 (Message Too Big) or answers a POST with 413.

The hard ceiling (WS_HARD_MAX_MESSAGE_BYTES) is enforced by the websocket library before a frame is ever assembled in memory. It exists because one process has no second instance to absorb a hostile payload, and the configurable limit cannot be raised above it.

On the backpressure drop ​

Once a socket has more than 8 MiB queued and unflushed, further messages to that socket are dropped and a warning is logged.

Delivery is best-effort by design, and with one process a single client that stops reading would otherwise grow the heap until the box dies. Dropping for that one socket keeps every other client on the channel unaffected. The sender is not told, consistent with everything else here.

Security model ​

The complete model, stated plainly:

  • The channel name is the only secret. Anyone who knows it has full read and write access.
  • Names never expire and cannot be revoked. Rotating means agreeing on a new one.
  • No authentication, no authorization, no per-client identity.
  • Transport is encrypted (wss://, https://), but the router process sees plaintext.
  • CORS is open to every origin, deliberately: clients are arbitrary pages.
  • Names appear in URLs, so they leak through history, referrers, screenshots and screen shares.
  • Payloads are never logged. Channel names are, because they are the routing key an operator needs.
  • The admin panel is a separate secret and does not exist unless ADMIN_PASSWORD is set. It has no route that can read channel traffic.

Concretely: fine for a presentation remote, a workshop demo, a dashboard of non-sensitive telemetry, or peer components inside one product. Not fine, on its own, for personal data, credentials, or anything with a compliance requirement. For those, either put your own authenticated service in front of it, or encrypt payloads client-side so the router only ever moves ciphertext.

Failure behaviour ​

FailureResult
Process restartsEvery socket closes with 1001. Clients must reconnect.
Process crashespm2 restarts it; it gives up after 10 rapid failures rather than crash-looping.
Slow clientMessages to it are dropped past 8 MiB unflushed. Nobody else is affected.
Channel name too shortUpgrade refused with HTTP 400 before the socket is accepted.
Reserved pathUpgrade refused with HTTP 404.
Oversize frameSocket closed with 1009.
Oversize POST413, message discarded.
POST to an empty channel200 OK; the message is discarded.
Certificate renewal failsLogged and retried; the existing certificate keeps serving.
First certificate issuance failsThe process does not start. There is nothing to serve.

Version compatibility ​

There is no protocol version and no negotiation. The wire format is "a websocket, and bytes". Any change to that would be a new endpoint rather than a silent change to this one.

Note for anyone who used the Redis-era deployment: the client-visible contract is unchanged except that binary frames are now routed instead of dropped, and a configurable size limit now exists. Nothing that worked before stops working.

Released under the MIT License.