Протокол
На этой странице описан формат передачи данных — для отладки и для написания клиента на другом языке. Официальные клиенты реализуют его полностью.
Транспорт
- WebSocket upgrade на путь, переданный в
MapDarkWs. Браузеры отправляют заголовокOrigin, который сервер может проверять поAllowedOrigins. - Необязательный query-параметр (по умолчанию
token) передаётся в аутентификатор сервера. - Только текстовые фреймы. Сейчас сервер обрабатывает бинарный фрейм как текст с теми же байтами, а .NET-клиент закрывает сокет с кодом 1003; не полагайтесь на бинарные фреймы.
- Сообщения больше серверного
MaxMessageSizeBytes(1 MiB) закрывают соединение с кодом 1009.
JSON-сообщения
| Направление | Формат |
|---|---|
| Запрос | { "id": string, "action": string, "data"?: unknown } |
| Успех | { "id": string, "data"?: unknown } |
| Ошибка | { "id": string, "error": string, "data"?: unknown } |
| Рассылка | { "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" } }
- Идентификаторы запросов выбирает клиент. Они должны быть непустыми и не могут быть
@или@auth; запрос с зарезервированным id отклоняется, а его действие не выполняется. - Ответы несут id запроса и могут приходить в любом порядке.
- Ответ считается ошибкой всякий раз, когда в нём есть поле
error. dataопускается только тогда, когда сервер использовал перегрузку без данных (Ok(),PublishAsync(target, action)). Перегрузки с данными всегда записывают их, а для значения null — какnull.- Имена полей конверта зафиксированы атрибутами
JsonPropertyName:id,action,dataиerror. Политики именованияJsonOptionsприменяются к свойствам прикладных DTO внутриdata(по умолчанию camelCase), а не к именам конверта. - Ответ
invalid-requestна запрос с зарезервированным id использует пустой id, чтобы его нельзя было спутать с рассылкой или устаревшим управляющим ответом.
Текстовые команды
Системные команды и ответы на них передаются обычным текстом, без JSON и id:
| Команда | Успех | Ошибка |
|---|---|---|
auth:<token> | auth:success | auth:failed |
logout | logout:success | Соединение обрывается, если logout не удаётся завершить |
ping | pong | Нет |
Поскольку у ответов нет id, клиент должен отправлять не более одной команды
auth:/logout за раз и дожидаться ответа на неё. Если ответ не пришёл вовремя,
клиенту следует закрыть сокет, чтобы запоздавший ответ не был принят за ответ на
следующую команду. Команды обрабатываются по порядку вместе с запросами в том же
соединении.
Регулярно отправляйте ping: сервер на .NET 8 закрывает соединения, молчащие дольше
ReceiveIdleTimeout (2 минуты). Отсутствие pong сообщает клиенту, что соединение
мертво.
Коды ошибок
| Код | Значение |
|---|---|
darkws:error:invalid-action | Такого действия нет |
darkws:error:invalid-request | Некорректный JSON, отсутствующие или неверные поля либо payload, который не удаётся привязать |
darkws:error:authorization-required | Действию нужна сессия |
darkws:error:request-failed | Обработчик неожиданно завершился с ошибкой |
darkws:error:busy | Очередь запросов соединения оставалась заполненной |
auth:failed | Текстовый ответ на отклонённую команду auth: |
Серверы могут переименовать коды darkws:error:* через DarkWsOptions. Все
остальные коды приходят из приложения.
Коды закрытия и HTTP-статусы
| Код | Кто отправляет | Причина |
|---|---|---|
| HTTP 400 | Сервер | Это не WebSocket-запрос |
| HTTP 401 | Сервер | Аутентификатор при upgrade выбросил исключение |
| HTTP 403 | Сервер | Origin не разрешён |
| 1000 | Любая сторона | Нормальное закрытие |
| 1003 | .NET-клиент | Сервер отправил бинарный фрейм |
| 1008 | Сервер | auth:/logout пришла, когда очередь команд была заполнена |
| 1009 | Любая сторона | Сообщение превысило лимит размера |
Контракт жизненного цикла клиентов
Оба клиента говорят на одном протоколе, но придерживаются разных политик жизненного цикла:
| Случай | Браузерный DarkWs | .NET DarkWsClient |
|---|---|---|
| Сокет обрывается при отключённом переподключении | Ожидающие вызовы завершаются ошибкой; фоновых повторов нет. Следующий запрос или отправка открывает новый сокет. | Ожидающие вызовы завершаются ошибкой. Последующие вызовы завершаются ошибкой до явного ConnectAsync. |
Автоматическая аутентификация получает auth:failed | Вызовы в очереди отклоняются с ErrorResponse. Срабатывает sessionRestoreFailed, затем open сигнализирует о готовности без сессии; последующие вызовы уходят анонимно. | Готовность завершается ошибкой DarkWsResponseException. Состояние становится Disconnected с указанием причины, и повторы прекращаются до ConnectAsync. |
| Провайдер токена выбрасывает исключение | То же событие об ошибке и анонимный откат, если сокет ещё открыт. | Постоянная ошибка готовности до ConnectAsync. |
| Провайдер токена не возвращает токен | Подключается анонимно, без sessionRestoreFailed. | Постоянная ошибка готовности. |
В браузере connected и open отражают состояние транспорта и не доказывают, что
аутентификация прошла успешно. В .NET Connected наступает после успешного
восстановления. LazyDarkWs следует браузерной политике; его close() дополнительно
позволяет переподключиться при следующем использовании.
Ни один из клиентов не хранит токены, переданные для ручной аутентификации.
Переподключения используют только query приложения или провайдер токена.