Opcodes

The numeric wire contract between client and server, for anyone writing their own client.

You do not need this page to use Blackevin — @blackevin/client speaks all of it. You need it to write a client in a language that has no SDK yet.

The opcodes

opnamedirectionrole
1AttachC→Smark the channel attached — the gate for presence delivery
2DetachC→Sunmark it
3PublishC→Spublish a message
4MessageS→Ca delivered publish
5PresenceEnterC→Senter presence
6PresenceLeaveC→Sleave presence
7PresenceUpdateC→Supdate presence data
8PresenceS→Ca presence change
9AckS→Csuccess, for a request that carried an id
10NackS→Cfailure, with code and message
11HeartbeatC↔Skeepalive
12ConnectedS→Cwelcome, carrying connectionId
13SubscribeC→Ssubscribe to a channel
14UnsubscribeC→Sunsubscribe
15AuthC→Sswap the credential mid-session

The numbers are frozen. The names in this table are for humans; nothing on the wire carries them. A client that switches on the number will keep working across releases, and one that expects a name will not work at all.

Every frame

{
  op: number;   // required
  id?: string;  // client-generated; the server echoes it in Ack / Nack
}

id is what makes a request awaitable. Send it on anything whose outcome you care about — a Publish without one is fire-and-forget, because there is nothing for the Ack to name.

The frames that carry more

Publish and Message add channel, an optional name, and optional JSON data. Message also carries the publishing connectionId and a timestamp.

Presence frames add channel, clientId and optional data. The server's Presence adds action (enter / leave / update) and timestamp.

Subscribe may add rewind — deliver the last N persisted messages to this connection immediately after the Ack, oldest first. The server caps it at 100. It may also add filter: a string compiled server-side as a RegExp and applied after the structural channel match.

Auth carries accessToken or key. The server answers Ack and a fresh Connected.

The handshake

C → (open socket, credential in the query string)
S → Connected { connectionId }
C → Subscribe { id: "1", channel: "chat:lobby" }
S → Ack       { id: "1" }
S → Message   { channel: "chat:lobby", name: "…", data: {…} }

The credential goes on the connect URL:

ws://host/?key=secret.keyId
ws://host/?accessToken=<blackevin-jwt>

token is accepted as an alias for accessToken. A missing or invalid credential gets a Nack with code 401 and no further operations are served.

Nack codes

They are HTTP status codes, because they mean the same things:

codemeans
401missing, malformed, unknown or revoked credential
403authenticated, but the capability does not cover this channel and operation
429a connection ceiling was hit
4000disconnected by the server

A 403 leaves the connection open — one refused publish should not cost the session. A 401 does not: nothing further is served on that socket.

401 and 403 are terminal for a client: the credential will not start working. 429 is not — a slot frees when another client disconnects. See connection state.

Attach is not subscribe

They look redundant and are not.

  • Subscribe is about messages: it registers the channel matcher that decides what gets delivered.
  • Attach is about presence: it is the gate that decides whether this connection receives Presence frames for the channel.

@blackevin/client attaches implicitly on the first subscribe or publish, so most clients never send a bare Attach. One that only wants to watch presence — a dashboard that never reads messages — is the case where you would.

Not yet on the wire

Binary frames, and connection resume with recovery serials. A reconnect today re-sends Attach and Subscribe rather than resuming a stream, which is why rewind is how a client catches up.

On this page