Skip to content

Подключение и логин

Клиент начинает работу с 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 потерян, но сервер еще дает клиенту шанс вернуться без разрыва конференции и без удаления устройств.

Серверная логика:

  1. При не hard logout command-сессия помечается как stale.
  2. Сервер запускает delayed cleanup с grace 20s.
  3. Если за это время тот же пользователь переподключается с тем же control_instance_id, сервер ищет stale command-сессию этого экземпляра.
  4. Если stale найдена, сервер переносит состояние на новый socket через RestoreStaleCommandSession.
  5. ConferenceContainer заменяет старую session pointer на новую через ReplaceClientSession.
  6. В connect_response выставляется restore_stale_session=true и возвращается stale_conference_tag.
  7. Если клиент не вернулся за 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:

  1. ставит флаг _wasReconnect;
  2. открывает новый WebSocket;
  3. отправляет обычный connect_request с тем же control_instance_id;
  4. при connect_response эмитит connected;
  5. если это reconnect, эмитит reconnected и заново запрашивает группы/контакты;
  6. передает 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.