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

Jitsi: именование комнат, admission и presence

Как медиа-комната (MediaRoom) отображается на MUC-комнату Jitsi, кто авторитет её жизненного цикла и как messenger узнаёт о входе/выходе участников. Комнаты создаются prosody/jicofo при входе клиента; авторитет admission и состава — серверные hook-и prosody, а не клиентские события.

Именование комнат

СессияИмя комнаты (MediaRoom.RoomName)
1:1-звонок (call-session.md)call-{CallSessionId}
Конференция (conference-session.md)conf-{ConferenceSessionId}

Имя детерминировано из id сессии (плюс nonce), уникальность среди активных — INV-CALL-6/INV-CONF-5. Причина: reservation-вебхук prosody несёт в теле только имя комнаты (см. ниже), поэтому по нему messenger должен однозначно найти сессию.

Деривация и fail-closed сверка

  • Формат: {prefix}-{SessionId:N} — префикс call/conf, SessionId в формате N (32 hex без дефисов), опциональный nonce-суффикс -{n} при пересоздании комнаты после Failed (уникальность среди активных). Пример: conf-3fa85f6457174562b3fc2c963f66afa6.
  • Разбор: по префиксу call-/conf-SessionKind; по хвосту → Guid → id сессии. Невалидный формат или неизвестный id → admission-вебхук отвечает 4xx (MSG_JITSI_ROOM_UNKNOWN), комната не поднимается.
  • Fail-closed match: имя из вебхука сверяется с сохранённым MediaRoom.RoomName найденной сессии байт-в-байт. Расхождение (даже при валидном формате и живой сессии) → отказ. Комната поднимается, только если имя из prosody точно равно тому, что messenger записал при старте сессии. Это второй гейт поверх room-claim JWT (jitsi-jwt.md), ограничивающего токен одной комнатой.

Authoritative lifecycle: mod_reservations

Комната PIN не создаётся императивно бэкендом — она поднимается, когда клиент с валидным JWT (jitsi-jwt.md) входит по имени. Перед созданием prosody-модуль mod_reservations перехватывает conference-IQ к jicofo (pre-iq/host, дочерний conference в namespace http://jitsi.org/protocol/focus) и буферизует его, пока messenger не подтвердит бронь HTTP-вызовом (fail-closed: без 2xx комната не создаётся — prosody шлёт reservation-error инициатору).

Wire-формат reservation-вебхука

mod_reservations штатно бьёт в {prefix}/conference и DELETE {prefix}/conference/{conflict_id}. PIN переименовывает сегмент пути conferencereservations (двухстрочная правка констант url в call_api_create_conference/room_destroyed/handle_conference_already_exist, инвентарь — jitsi-modifications.md), чтобы попадать в замороженный роут POST /api/messenger/internal/jitsi/reservations. Возвращаемый id = RoomName, поэтому DELETE/GET приходят по ключу имени комнаты (/reservations/{roomName}).

Admission — POST /internal/jitsi/reservations (тело application/x-www-form-urlencoded, http.formencode; NB — форма, не JSON, унаследовано от Jicofo):

Поле формыИсточник в prosodyПример
namenode комнаты (RoomReservation:get_room_name)conf-3fa85f64...afa6
start_timeJava-datetime yyyy-MM-dd'T'HH:mm:ss.SSSX (to_java_date_string)2026-07-15T10:03:22.000Z
mail_ownerjid.bare(creator_jid) — bare-JID инициатораb1a7c9@meet.pin.internal

Аутентификация вебхука — общая для internal/* (HMAC X-Pin-Signature + timestamp с replay-окном, либо mTLS; fail-closed). Заголовок несётся через reservations_api_headers (см. конфиг ниже).

ALLOW — 200/201 + JSON. messenger по name находит активную сессию (call-/conf-), выполняет fail-closed сверку имени и проверяет статус (Ringing/Provisioning/Active); при успехе ConferenceSession.Status: Provisioning → Active и отдаёт:

{
"name": "conf-3fa85f6457174562b3fc2c963f66afa6",
"id": "conf-3fa85f6457174562b3fc2c963f66afa6",
"mail_owner": "b1a7c9@meet.pin.internal",
"start_time": "2026-07-15T10:03:22.000Z",
"duration": 14400,
"max_occupants": 20,
"lobby": false
}
  • name — prosody сверяет с именем комнаты (lowercased); несовпадение → бронь отклоняется на его стороне.
  • id — корреляционный идентификатор брони; messenger возвращает RoomName, prosody запоминает его как conflict_id и шлёт с ним DELETE/повторный GET.
  • duration (секунды) — верхняя граница жизни комнаты: expires_at = start_time + duration, prosody сам разрушит комнату по истечении (evict_expired_reservations). Выставляем с запасом над политикой сессии.
  • max_occupants (опц.) — лимит участников конференции (требует reservations_enable_max_occupants); lobby (опц.) — не используем в MVP (false).

DENY — non-2xx. Любой не-2xx → prosody блокирует создание комнаты (reservation-error, occupant не входит). Тело ошибки — JSON { "message": "..." } (используется как текст ошибки). Коды:

Код от messengerСмыслРеакция prosody
404 MSG_JITSI_ROOM_UNKNOWNимя не разобрано / сессии нет / имя не совпалокомната не поднимается
409 (Conflict)бронь уже существуетprosody дозапрашивает GET /reservations/{id} — идемпотентно
4xx/5xx прочиеполитика/ошибкакомната не поднимается; клиент видит отказ входа
таймаут (reservations_api_timeout)messenger не ответилset_status_failed(500) — не поднимается

Финализация — DELETE /internal/jitsi/reservations/{roomName} на muc-room-destroyed: messenger освобождает бронь. Лайфсайкл-финализацию сессии (→ Ended) ведёт presence-канал (RoomDestroyed, ниже) и reaper (../background/conference-room-reaper.md); оба идемпотентны.

Конфиг vhost prosody (reservations)

Ключи — из mod_reservations.lua (module:get_option(...)):

VirtualHost "meet.pin.internal"
modules_enabled = { "reservations"; "pin_muc_presence"; /* + token-модули */ }

reservations_api_prefix = "https://messenger.pin.internal/api/messenger/internal/jitsi"
reservations_api_headers = {
["Authorization"] = "Bearer <svc>"; -- либо HMAC-заголовок X-Pin-Signature (см. internal/*)
}
reservations_api_timeout = 20 -- сек; таймаут не ретраится
reservations_api_retry_count = 3 -- ретраи на 5xx/сетевой сбой
reservations_api_retry_delay = 3 -- сек между ретраями
reservations_enable_max_occupants = true -- разрешить поле max_occupants из ответа
-- lobby/password support не включаем (MVP)

Так admission — авторитетный серверный гейт: даже с валидным JWT комната не поднимется, если messenger не подтвердил бронь (сессия отсутствует/завершена/имя не совпало).

Presence: кастомный модуль mod_pin_muc_presence

Ростер конференции (call-participant.md) — авторитетно серверный: кто реально в комнате, знает prosody, а не клиент. Небольшой кастомный модуль mod_pin_muc_presence.lua подписывается на MUC-события и шлёт их в messenger единым эндпоинтом POST /api/messenger/internal/jitsi/events (JSON, наша схема, та же HMAC-аутентификация internal/*).

Wire-формат presence-вебхука

{
"room": "conf-3fa85f6457174562b3fc2c963f66afa6",
"occupantJid": "conf-3fa8...afa6@conference.meet.pin.internal/9a1f",
"occupantUserId": "b1a7c9de-0000-4000-8000-000000000001",
"nick": "Анна Риелтор",
"role": "moderator",
"event": "OccupantJoined",
"occurredAt": "2026-07-15T10:03:25.118Z"
}
ПолеИсточник в prosodyЗаметка
roomnode комнатыключ для разбора сессии (как в admission)
occupantJidoccupant.nick/occupant.jidполный MUC-JID (с ресурсом)
occupantUserIdsession.jitsi_meet_context_user.idиз context.user.id JWT (token/util.lib.luajitsi_meet_context_user) → маппится на PIN UserId
nickdisplay-name occupant-аbest-effort
roleoccupant.role (moderator/participant)роль присвоена mod_token_affiliation из context.user.moderator
eventтип hook-аOccupantJoined / OccupantLeft / RecordingStatusChanged / RoomDestroyed
occurredAtсерверное время prosody (UTC)для упорядочивания и LastActivityAt

Хук → событие:

  • muc-occupant-joinedOccupantJoined;
  • muc-occupant-leftOccupantLeft (+ причина, если доступна);
  • статус jibri (RecordingStatusChanged) — метаданные записи (recording-pipeline.md);
  • muc-room-destroyedRoomDestroyedлайфсайкл-канал финализации (ConferenceSession → Ended); параллельный DELETE /reservations/{roomName} чистит бронь. Оба идемпотентны.

messenger сверяет presence-события с командами join/leave; при расхождении побеждает серверное событие (INV-CPART-5) — клиент мог не долететь. Screen-share prosody видит best-effort; живой mute/камера не персистятся (принадлежат media-плоскости).

Почему кастомный модуль, а не готовые механизмы

  • prosody-egress (thirdparty/jitsi/infra-provisioning/nomad/prosody-egress.hcl) — проприетарный egress-канал 8x8, не часть открытой поставки и завязан на их инфраструктуру; тянуть его ради двух хук-ов избыточно.
  • Готового muc_webhooks-модуля в открытой поставке jitsi-meet/resources/prosody-plugins нет — presence нужно эмитить самим.
  • Серверные hook-и (muc-occupant-joined/left) — авторитетный источник состава (надёжнее клиентских USER_JOINED/USER_LEFT, которые могут теряться при разрыве). Поэтому — минимальный собственный модуль по образцу mod_muc_census/mod_measure_message_count.

Поток admission + presence (sequence)

Маппинг на сущности

Prosody-фактСущность/поле
admission POST /reservations подтверждёнConferenceSession.Status: Provisioning → Active
OccupantJoinedCallParticipant.State → Joined, JoinedAt; пересчёт ActiveParticipantCount/PeakParticipantCount
OccupantLeftCallParticipant.State → Left, LeftAt, LeaveReason, DurationSeconds
RoomDestroyed / комната опустелаConferenceSession.Status → Ended (LastParticipantLeft/EmptyTimeout)
RecordingStatusChangedстатус записи (jibri) — recording-pipeline.md

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

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

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