# Логика установки соединения через сервер Ниже описан фактический flow звонка в текущей реализации SHiNE с опорой на сообщения, которые проходят через сервер. ## 1) Основные операции и типы сообщений Клиентские WS-операции: - `CallInviteBroadcast` — широковещательный входящий вызов пользователю. - `CallSignalToSession` — точечный сигнал в конкретную сессию. Серверные события клиенту: - `IncomingCallInvite` — уведомление о входящем вызове. - `IncomingCallSignal` — сигнал по активному `callId`. Коды `type` в `IncomingCallSignal`: - `100` — `INVITE` - `110` — `RINGING` - `120` — `ACCEPT` - `130` — `DECLINE_BUSY` - `140` — `TIMEOUT` - `150` — `HANGUP` - `200` — `OFFER` - `210` — `ANSWER` - `220` — `ICE` --- ## 2) Старт исходящего звонка 1. Инициатор создаёт `callId` и отправляет: - `CallInviteBroadcast(toLogin, callId, type=100)`. 2. Сервер находит все активные WS-сессии целевого логина и шлёт им `IncomingCallInvite`. 3. На устройствах callee появляется экран входящего вызова. --- ## 3) Ранние статусы до поднятия трубки Каждое устройство callee может отправить инициатору: - `RINGING (110)` — «звонок идёт». Для исходящего звонка инициатор фиксирует выбранную `remoteSessionId`: - первый валидный `RINGING`/`ACCEPT` выбирает сессию, - сигналы с тем же `callId`, но от других сессий этого же пользователя, игнорируются. Это защищает от гонок мультидевайса. --- ## 4) Принятие звонка на одном устройстве callee 1. На выбранном устройстве callee пользователь нажимает «Поднять»: - отправляется `ACCEPT (120)` в выбранную сессию инициатора. 2. Сервер дополнительно рассылает на **другие** сессии этого же callee: - `HANGUP (150)` с `data=accepted_on_other_device`. 3. Остальные устройства callee закрывают экран входящего вызова. Итог: активным остаётся один путь «инициатор ↔ выбранная сессия callee». --- ## 5) Обмен SDP (OFFER/ANSWER) После `ACCEPT`: 1. Инициатор формирует `RTCPeerConnection`, `createOffer()`, отправляет: - `OFFER (200)`. 2. Calee применяет `setRemoteDescription(offer)`, делает `createAnswer()`, отправляет: - `ANSWER (210)`. 3. Инициатор применяет `setRemoteDescription(answer)`. Защиты: - повторный `ACCEPT`/повторный старт `offer` игнорируется; - `ANSWER` обрабатывается только для исходящего звонка; - `ANSWER` без локального `offer` или дубликат в `stable` игнорируется. --- ## 6) Обмен ICE-кандидатами `ICE (220)` может приходить раньше SDP. Поэтому: - если `pc` ещё не создан — ICE кладётся в очередь; - если `pc` есть, но `remoteDescription` ещё нет — ICE тоже в очередь; - после установки `remoteDescription` очередь применяется (`addIceCandidate`). Это устраняет race «remote description was null». --- ## 7) Завершение и ошибки Нормальное завершение: - `HANGUP (150)` от одной стороны → вторая завершает звонок. Неуспех установки: - сторона, у которой setup не удался, шлёт `HANGUP (150)` с `data=setup_failed:...`, - вторая сторона сразу закрывает экран ожидания. Также пишутся `CallDeliveryReport`: - `call_connected`, `incoming_failed`, `outgoing_failed`, `unknown_error` и расширенная диагностика ICE/SDP. --- ## 8) Почему схема устойчива сейчас Текущая устойчивость обеспечивается тремя правилами: 1. **Одна выбранная сессия callee** для исходящего звонка. 2. **Принятие на одном устройстве закрывает входящий на остальных** через сервер. 3. **ICE буферизуется до готовности SDP/PC**, а не ломает handshake. Именно комбинация этих трёх пунктов закрывает основные причины «иногда дозванивается, иногда нет» при мультидевайсе.