Подключение и логин
Клиент начинает работу с CommandLoop. Остальные каналы открываются после получения access_token.
Начиная с JWT-flow CommandLoop поддерживает два способа входа:
- первичный вход по
login/password; - повторный вход по
connect_request.access_token, если токен еще валиден.
При первичном входе сервер возвращает access_token и, если включен JWT-secret, refresh_token. Дополнительные каналы BlobChannel и WSMedia всегда подключаются по access_token, а не по логину/паролю.
Для web-client дополнительно используется стабильный control_instance_id. Это идентификатор экземпляра control-сессии браузерной вкладки/установленного PWA, который хранится в localStorage под ключом vg_control_instance_id и отправляется в каждом connect_request. Сервер использует его, чтобы отличить обычный новый логин от восстановления той же вкладки после потери WebSocket.
sequenceDiagram
participant Client
participant ControlWS as CommandLoop WebSocket
participant Processor
participant ClientContainer
Client->>ControlWS: open WebSocket
Client->>Processor: connect_request(login/password or access_token)
Processor->>ClientContainer: AddClient(session, credentials, CommandLoop)
ClientContainer-->>Processor: client id, connection id
Processor-->>Client: connect_response(result=OK, access_token, refresh_token, grants)
Processor-->>Client: conferences_list
Client->>Processor: contact_list / group_list / other commands
Web-client connect_request
ControlWS открывает WebSocket к control endpoint и отправляет:
{
"connect_request": {
"login": "user",
"password": "...",
"client_version": 603,
"control_instance_id": "ci_..."
}
}
Возможен повторный вход по access token:
{
"connect_request": {
"access_token": "jwt-or-opaque-token",
"client_version": 603,
"control_instance_id": "ci_..."
}
}
Правила для control_instance_id:
- генерируется на клиенте один раз и переиспользуется между reconnect/F5/PWA resume;
- не является секретом и не заменяет
access_token; - должен быть стабильным для одной вкладки/установленного web-client экземпляра;
- нужен только control-сессии, media/blob каналы авторизуются
access_token; - при поврежденном или отсутствующем значении web-client генерирует новый
ci_*.
connect_response
При успешном логине сервер возвращает:
{
"connect_response": {
"result": 1,
"id": 228800001,
"connection_id": 1,
"access_token": "...",
"refresh_token": "...",
"grants": 4294967295,
"max_output_bitrate": 2500,
"restore_stale_session": true,
"stale_conference_tag": "radio"
}
}
Ключевые поля:
| Поле | Назначение |
|---|---|
id |
client_id пользователя. |
connection_id |
Идентификатор текущего command connection внутри сервера. |
access_token |
Bearer token для control/media/blob/REST. |
refresh_token |
Долгий token только для REST refresh. |
max_output_bitrate |
Серверное ограничение исходящего bitrate. |
restore_stale_session |
Сервер восстановил старую command-сессию вместо чистого нового входа. |
stale_conference_tag |
Конференция, к которой была привязана восстановленная stale-сессия. |
После connect_response сервер сразу присылает актуальные списки (group_list, contact_list, conferences_list). Клиент не должен считать отсутствие отдельного запроса ошибкой.
Access token
Если на сервере задан VG_JWT_SECRET или [Auth] JwtSecret, access_token является JWT HS256.
Минимальные claims:
| Claim | Назначение |
|---|---|
iss |
Издатель, сейчас VideoGrace. |
aud |
Аудитория токена, сейчас videograce. |
typ |
Тип токена, для access token значение access. |
sub |
client_id пользователя. |
login |
Логин пользователя. |
name |
Отображаемое имя пользователя. |
grants |
Битовая маска прав. |
scope |
Разрешенные каналы: control, media, blob, api. |
jti |
Уникальный идентификатор токена для точечной отзываемости. |
iat |
Время выпуска. |
exp |
Время истечения. |
Если JWT-secret не задан, сервер может вернуть legacy opaque token. Новый код должен считать токен непрозрачной строкой и не полагаться на формат JWT на клиенте.
Refresh token
refresh_token не является JWT. Это случайный секрет, который хранится в БД только в виде HMAC-хеша. Клиент использует его для получения нового access_token через REST:
POST /api/v1.0?auth_refresh
Content-Type: application/json
{
"refresh_token": "..."
}
Ответ:
{
"result": 200,
"access_token": "...",
"token_type": "Bearer"
}
Refresh token нельзя передавать в BlobChannel, WSMedia или CAN jobs. Его область применения - только обновление access token.
Stale command session
Stale - это не logout. Это временное состояние старой command-сессии, когда control WebSocket потерян, но сервер еще дает клиенту шанс вернуться без разрыва конференции и без удаления устройств.
Серверная логика:
- При не hard logout command-сессия помечается как
stale. - Сервер запускает delayed cleanup с grace
20s. - Если за это время тот же пользователь переподключается с тем же
control_instance_id, сервер ищет stale command-сессию этого экземпляра. - Если stale найдена, сервер переносит состояние на новый socket через
RestoreStaleCommandSession. ConferenceContainerзаменяет старую session pointer на новую черезReplaceClientSession.- В
connect_responseвыставляетсяrestore_stale_session=trueи возвращаетсяstale_conference_tag. - Если клиент не вернулся за grace, выполняется hard cleanup: клиент удаляется из конференции, устройства disconnect-ятся.
Если stale-сессии нет, но с тем же control_instance_id еще видна активная command-сессия, сервер может пометить ее stale и принять новую как владельца того же экземпляра. Это защищает от ситуации, когда браузер создал новый WebSocket, а старый socket еще не дошел до server-side close.
sequenceDiagram
participant Browser
participant ControlWS
participant Processor
participant Clients as ClientContainer
participant Conf as ConferenceContainer
ControlWS--xProcessor: network loss / 1006
Processor->>Clients: MarkCommandSessionStale()
Processor-->>Processor: delayed cleanup 20s
Browser->>ControlWS: reconnect
ControlWS->>Processor: connect_request(login/password or access_token, control_instance_id)
Processor->>Clients: FindStaleCommandSessions(client_id, control_instance_id)
Processor->>Clients: RestoreStaleCommandSession(stale, new_socket)
Processor->>Conf: ReplaceClientSession(stale, new_socket)
Processor-->>ControlWS: connect_response(restore_stale_session=true, stale_conference_tag)
ControlWS-->>Browser: reconnected event
Web-client reconnect
ControlWS reconnect запускается в нескольких случаях:
oncloseсautoReconnect=true;online;visibilitychangeпри возврате вкладки в foreground;focus;pageshow;touchstartна мобильных браузерах;- connect watchdog, если WebSocket слишком долго остается в
CONNECTING.
Backoff reconnect: 1000 * 2^retry, максимум 30000ms.
На reconnect ControlWS:
- ставит флаг
_wasReconnect; - открывает новый WebSocket;
- отправляет обычный
connect_requestс тем жеcontrol_instance_id; - при
connect_responseэмититconnected; - если это reconnect, эмитит
reconnectedи заново запрашивает группы/контакты; - передает
restore_stale_session,stale_conference_tag,connection_idвVideograceClient.
VideograceClient при restore_stale_session=true не делает полный rejoin. Он сохраняет конференцию и media-сессии живыми, выставляет isInConference=true, восстанавливает activeConferenceTag и запускает media recovery.
Каналы после логина
flowchart LR
Login[CommandLoop connect_response]
Token[access_token]
Refresh[refresh_token]
Blob[BlobChannel connect_request with access_token]
Media[WSMedia connect_request with access_token]
API[REST API Authorization: Bearer]
Renew[POST auth_refresh]
Login --> Token
Login --> Refresh
Token --> Blob
Token --> Media
Token --> API
Refresh --> Renew --> Token
Инварианты логина
access_tokenавторизуетCommandLoop, дополнительные media/blob каналы и REST API.refresh_tokenиспользуется только для выпуска новогоaccess_token.control_instance_idсвязывает reconnect с конкретным web-client экземпляром, но не дает прав доступа.CommandLoopостаётся источником правды по конференциям, устройствам и сообщениям.WSMediaне должен пытаться логиниться логином/паролем; он подключается поaccess_token.- Один клиент может иметь несколько transport sessions, но только control session управляет lifecycle.
- Сервер должен уметь отозвать конкретный JWT по
jti, не ломая все пользовательские сессии. - После stale restore клиент не должен заново создавать конференцию или дублировать локальные устройства; нужно восстановить transport поверх существующего device lifecycle.