Skip to main content
Version: 5.x

Protocol

This page describes the wire format, for debugging and for writing a client in another language. The official clients implement all of it.

Transport​

  • A WebSocket upgrade to the path passed to MapDarkWs. Browsers send an Origin header that the server may check against AllowedOrigins.
  • An optional query parameter (token by default) is passed to the server's authenticator.
  • Text frames only. The server currently treats a binary frame as text with the same bytes, and the .NET client closes the socket with 1003; do not rely on binary frames.
  • Messages larger than the server's MaxMessageSizeBytes (1 MiB) close the connection with 1009.

JSON messages​

DirectionShape
Request{ "id": string, "action": string, "data"?: unknown }
Success{ "id": string, "data"?: unknown }
Error{ "id": string, "error": string, "data"?: unknown }
Broadcast{ "id": "@", "action": string, "data"?: unknown }
→ { "id": "7f3c", "action": "math:sum", "data": { "left": 2, "right": 3 } }
← { "id": "7f3c", "data": { "value": 5 } }

→ { "id": "7f3d", "action": "message:delete", "data": { "id": "42" } }
← { "id": "7f3d", "error": "message:forbidden" }

← { "id": "@", "action": "message:created", "data": { "text": "hi" } }
  • The client chooses request ids. They must be non-empty and must not be @ or @auth; a request with a reserved id is rejected without running its action.
  • Responses carry the request's id and may arrive in any order.
  • A response is an error whenever it has an error field.
  • data is omitted only when the server used an overload without data (Ok(), PublishAsync(target, action)). Overloads with data always write it, as null for a null value.
  • Envelope names are fixed as id, action, data, and error by JsonPropertyName attributes. JsonOptions naming policies apply to application DTO properties inside data (camelCase by default), not to those envelope names.
  • The invalid-request reply to a request with a reserved id uses an empty id, so it cannot be mistaken for a broadcast or a legacy control reply.

Text commands​

System commands and their replies are plain text, without JSON or ids:

CommandSuccessFailure
auth:<token>auth:successauth:failed
logoutlogout:successThe connection fails if logout cannot complete
pingpongNone

Because replies have no id, clients must send at most one auth:/logout at a time and wait for its reply. If a reply does not arrive in time, the client should close the socket, so that a late reply cannot be read as the answer to the next command. Commands are processed in order with requests on the same connection.

Send ping regularly; a .NET 8 server closes connections that are silent for ReceiveIdleTimeout (2 minutes). A missing pong tells the client the connection is dead.

Error codes​

CodeMeaning
darkws:error:invalid-actionNo such action
darkws:error:invalid-requestMalformed JSON, missing or invalid fields, or a payload that cannot be bound
darkws:error:authorization-requiredThe action requires a session
darkws:error:request-failedThe handler failed unexpectedly
darkws:error:busyThe connection's request queue stayed full
auth:failedText reply to a rejected auth: command

Servers can rename the darkws:error:* codes through DarkWsOptions. Every other code comes from the application.

Close and HTTP status codes​

CodeSent byCause
HTTP 400ServerNot a WebSocket request
HTTP 401ServerThe upgrade authenticator threw
HTTP 403ServerThe Origin is not allowed
1000EitherNormal closure
1003.NET clientThe server sent a binary frame
1008ServerAn auth:/logout arrived when the command queue was full
1009EitherA message exceeded the size limit

Client lifecycle contract​

Both clients speak the same protocol but keep different lifecycle policies:

CaseBrowser DarkWs.NET DarkWsClient
Socket drops with reconnect disabledPending calls fail; no background retry. The next request or send opens a new socket.Pending calls fail. Later calls fail until an explicit ConnectAsync.
Automatic authentication receives auth:failedQueued calls reject with ErrorResponse. sessionRestoreFailed fires, then open signals readiness without a session; later calls go out anonymously.Readiness fails with DarkWsResponseException. The state becomes Disconnected with the reason, and retries stop until ConnectAsync.
Token provider throwsSame failure event and anonymous fallback if the socket is still open.Permanent readiness failure until ConnectAsync.
Token provider returns no tokenConnects anonymously, without sessionRestoreFailed.Permanent readiness failure.

In the browser, connected and open report the transport and do not prove that authentication succeeded. In .NET, Connected follows successful restoration. LazyDarkWs follows the browser policy; its close() additionally lets the next use reconnect.

Neither client keeps tokens passed to manual authentication. Reconnects use only the application's query or token provider.