Перейти к основному содержимому

POST /api/messenger/calls/{callId}/media-token — Обновить токен входа в звонок

Участник активного 1:1-звонка получает свежий дескриптор входа в ту же медиа-комнату: короткоживущий токен доступа и актуальные ICE-серверы. Комната и роль не меняются — операция только вычисляет новый токен взамен истёкшего. Нужна для длинных разговоров (звонок дольше TTL токена) и реконнектов после обрыва. Дом полей и инварианты — call-session.md.

Media-стек — внешний SFU (ADR-0062). Токены доступа/ICE/TURN — эфемерны, минтятся при входе и не персистятся (INV-CALL-6). Метод состояние сессии не меняет — только выдаёт новый MediaJoinModel. Медиа-звонки за флагом CALLS_MEDIA_ENABLED.

Назначение

Дать участнику установленного звонка (Caller или Callee) переполучить токен входа в медиа-комнату, когда текущий токен близок к истечению или соединение восстанавливается после разрыва. Owning-сервис — messenger, feature group calls, сценарий «звонок поверх контактов».

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areacalls
Feature groupcalls
Статусdraft
Документdocs/06-services/messenger/api/calls/media-token.md

Актор и доступ

ПроверкаЗначение
Actor typeUser — участник звонка (Caller или Callee)
Auth policy / Permissionspermission messenger.calls.use
Scope (RBAC)платформа (person-level)
Record-levelдоступ по membership: actor.UserId ∈ {CallerUserId, CalleeUserId}; иначе MSG_CALL_FORBIDDEN
Tenant isolationне применяется (ADR-0052); изоляция — по Caller/Callee
Auditnone — read-compute (сессия не меняется); токен не логируется

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/messenger/calls/{callId}/media-token
Success status200 (OK)
Idempotency headerне применяется — операция чтения-вычисления (Query), состояние не меняет; каждый вызов минтит новый токен
Correlationсквозной trace через traceparent

Тип операции — Query (POST-compute): HTTP-глагол POST, потому что операция минтит секрет по запросу, но записи в хранилище нет — статус, комната и участники неизменны.

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
CallIdrouteGuidДа!= Empty; существующая сессия в Answered, где актор — участник (шаги 3–4)
ActorUserIdidentity claimsGuidДаиз JWT; ∈ {Caller, Callee}

Тела запроса нет.

Модель ответа MediaTokenModel

ПолеТипОбязательностьИсточникMaskingОписание
RoomMediaJoinModelДаcomputedAccessToken/Credential — SensitiveСвежий дескриптор входа в ту же комнату (messenger-hub.md): новый AccessToken, актуальные IceServers, тот же RoomName/MediaDomain.

Room.AccessToken (и TURN Credential) — Sensitive: короткоживущий, минтится при формировании ответа, в БД не хранится, в логи не пишется (INV-CALL-6). RoomName/MediaDomain совпадают с зарезервированными при StartCall — переиздаётся только секрет, комната прежняя.

Алгоритм

  1. Контекст и доступ. Операцию выполняет участник звонка (User); ActorUserId — из JWT; требуется право messenger.calls.use. Медиа-звонки включены флагом CALLS_MEDIA_ENABLED, иначе MSG_CALLS_DISABLED.
  2. Проверка входа. CallId непустой; иначе ошибка валидации. Тела запроса нет.
  3. Загрузка сессии. Сессия по CallId; нет → MSG_CALL_NOT_FOUND. Контакт/чёрный список повторно не проверяются — гейт общения отработал при StartCall.
  4. Право и состояние. Актор должен быть участником: ActorUserId ∉ {Caller, Callee}MSG_CALL_FORBIDDEN. Токен выдаётся только для установленного звонка: Status == Answered, иначе (Ringing/Declined/Missed/Ended/Failed) → MSG_CALL_INVALID_STATE (для входящего на Ringing токен раздаётся приглашением/ответом, не этим методом).
  5. Выдача токена. Из Room сессии (RoomName/Domain) для роли актора минтится новый MediaJoinModel: свежий AccessToken с коротким TTL, актуальные IceServers (STUN/TURN), ExpiresAt. Сессия не меняется — записи в хранилище нет, realtime_outbox/Kafka не задействованы.
  6. Результат. MediaTokenModel { Room } → HTTP 200 OK.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400CallId = Empty«Проверьте параметры запроса»no retry
MSG_CALL_NOT_FOUND404сессии нет или актор не участник (скрытие)«Звонок не найден»no retry
MSG_CALL_FORBIDDEN403актор не Caller/Callee«Недостаточно прав»no retry
MSG_CALL_INVALID_STATE409Status != Answered (звонок не установлен или завершён)«Звонок недоступен»no retry (перечитать)
MSG_CALLS_DISABLED503CALLS_MEDIA_ENABLED = false«Звонки временно недоступны»retry позже

Совместимость и наблюдаемость

  • Новая операция — additive к contracts calls; смена маршрута/семантики — breaking. Kafka-контракта нет.
  • Logs: call media-token requested/issued/invalid-state (CallId, участники; AccessToken/Credential — никогда в логи); metrics: messenger_call_media_token_total{result}, latency p95; traces — сквозной trace (traceparent); dashboard pin-messenger.

Acceptance Criteria

  • Happy path. Given сессия в Answered, актор — участник, CALLS_MEDIA_ENABLED; When POST /media-token; Then 200, свежий MediaJoinModel (новый AccessToken, актуальные IceServers, тот же RoomName/MediaDomain); состояние сессии не изменено.
  • Звонок не установлен. Given Status != Answered (Ringing/Declined/Missed/Ended/Failed); When POST; Then 409 MSG_CALL_INVALID_STATE (на Ringing токен раздаётся приглашением/ответом).
  • Не участник. Given актор ∉ {Caller, Callee}; When POST; Then 403 MSG_CALL_FORBIDDEN.
  • Нет сессии. Given несуществующий CallId; When POST; Then 404 MSG_CALL_NOT_FOUND.
  • Флаг выключен. Given CALLS_MEDIA_ENABLED = false; When POST; Then 503 MSG_CALLS_DISABLED.
  • Без записи состояния. На любом вызове realtime_outbox/Kafka не задействованы, статус/комната/участники неизменны; AccessToken/Credential не хранятся и не в логи (INV-CALL-6).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы