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 area | calls |
| Feature group | calls |
| Статус | draft |
| Документ | docs/06-services/messenger/api/calls/media-token.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — участник звонка (Caller или Callee) |
| Auth policy / Permissions | permission messenger.calls.use |
| Scope (RBAC) | платформа (person-level) |
| Record-level | доступ по membership: actor.UserId ∈ {CallerUserId, CalleeUserId}; иначе MSG_CALL_FORBIDDEN |
| Tenant isolation | не применяется (ADR-0052); изоляция — по Caller/Callee |
| Audit | none — read-compute (сессия не меняется); токен не логируется |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/messenger/calls/{callId}/media-token |
| Success status | 200 (OK) |
| Idempotency header | не применяется — операция чтения-вычисления (Query), состояние не меняет; каждый вызов минтит новый токен |
| Correlation | сквозной trace через traceparent |
Тип операции — Query (POST-compute): HTTP-глагол POST, потому что операция минтит секрет по запросу, но
записи в хранилище нет — статус, комната и участники неизменны.
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
CallId | route | Guid | Да | != Empty; существующая сессия в Answered, где актор — участник (шаги 3–4) | — |
ActorUserId | identity claims | Guid | Да | из JWT; ∈ {Caller, Callee} | — |
Тела запроса нет.
Модель ответа MediaTokenModel
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Room | MediaJoinModel | Да | computed | AccessToken/Credential — Sensitive | Свежий дескриптор входа в ту же комнату (messenger-hub.md): новый AccessToken, актуальные IceServers, тот же RoomName/MediaDomain. |
Room.AccessToken (и TURN Credential) — Sensitive: короткоживущий, минтится при формировании ответа, в
БД не хранится, в логи не пишется (INV-CALL-6). RoomName/MediaDomain совпадают с зарезервированными при
StartCall — переиздаётся только секрет, комната прежняя.
Алгоритм
- Контекст и доступ. Операцию выполняет участник звонка (
User);ActorUserId— из JWT; требуется правоmessenger.calls.use. Медиа-звонки включены флагомCALLS_MEDIA_ENABLED, иначеMSG_CALLS_DISABLED. - Проверка входа.
CallIdнепустой; иначе ошибка валидации. Тела запроса нет. - Загрузка сессии. Сессия по
CallId; нет →MSG_CALL_NOT_FOUND. Контакт/чёрный список повторно не проверяются — гейт общения отработал приStartCall. - Право и состояние. Актор должен быть участником:
ActorUserId ∉ {Caller, Callee}→MSG_CALL_FORBIDDEN. Токен выдаётся только для установленного звонка:Status == Answered, иначе (Ringing/Declined/Missed/Ended/Failed) →MSG_CALL_INVALID_STATE(для входящего наRingingтокен раздаётся приглашением/ответом, не этим методом). - Выдача токена. Из
Roomсессии (RoomName/Domain) для роли актора минтится новыйMediaJoinModel: свежийAccessTokenс коротким TTL, актуальныеIceServers(STUN/TURN),ExpiresAt. Сессия не меняется — записи в хранилище нет,realtime_outbox/Kafka не задействованы. - Результат.
MediaTokenModel { Room }→ HTTP200 OK.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | CallId = Empty | «Проверьте параметры запроса» | no retry |
MSG_CALL_NOT_FOUND | 404 | сессии нет или актор не участник (скрытие) | «Звонок не найден» | no retry |
MSG_CALL_FORBIDDEN | 403 | актор не Caller/Callee | «Недостаточно прав» | no retry |
MSG_CALL_INVALID_STATE | 409 | Status != Answered (звонок не установлен или завершён) | «Звонок недоступен» | no retry (перечитать) |
MSG_CALLS_DISABLED | 503 | CALLS_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); dashboardpin-messenger.
Acceptance Criteria
- Happy path. Given сессия в
Answered, актор — участник,CALLS_MEDIA_ENABLED; WhenPOST /media-token; Then200, свежийMediaJoinModel(новыйAccessToken, актуальныеIceServers, тот жеRoomName/MediaDomain); состояние сессии не изменено. - Звонок не установлен. Given
Status != Answered(Ringing/Declined/Missed/Ended/Failed); WhenPOST; Then409 MSG_CALL_INVALID_STATE(наRingingтокен раздаётся приглашением/ответом). - Не участник. Given актор ∉
{Caller, Callee}; WhenPOST; Then403 MSG_CALL_FORBIDDEN. - Нет сессии. Given несуществующий
CallId; WhenPOST; Then404 MSG_CALL_NOT_FOUND. - Флаг выключен. Given
CALLS_MEDIA_ENABLED = false; WhenPOST; Then503 MSG_CALLS_DISABLED. - Без записи состояния. На любом вызове
realtime_outbox/Kafka не задействованы, статус/комната/участники неизменны;AccessToken/Credentialне хранятся и не в логи (INV-CALL-6).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (7): GET /api/messenger/calls — Журнал звонков (входящие / исходящие / пропущенные), POST /api/messenger/calls — Начать звонок, POST /api/messenger/calls/{callId}/answer — Принять звонок, POST /api/messenger/calls/{callId}/decline — Отклонить звонок, POST /api/messenger/calls/{callId}/end — Завершить звонок (hangup, call-лог), POST /api/messenger/conferences/{conferenceId}/media-token — Обновить токен входа в конференцию, SignalR Hub /hubs/messenger — MessengerHub (realtime-контракт)
- Use Cases (1): UC-MSG-005. Сигналинг аудио/видео-звонка
- calls (1): Жизненный цикл сессии звонка — 1:1 и конференция (messenger)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей, статусы,
INV-CALL-6,MediaRoom): call-session.md. - Дескриптор входа
MediaJoinModel(дом модели): messenger-hub.md. - Соседние операции: start-call.md, answer-call.md, decline-call.md, end-call.md, list-calls.md; контекст: README.
- ADR: 0055 свой мини-мессенджер, 0062 медиа-стек и запись; SDK realtime: 10-signalr-realtime.md.