# Логика доставки почты (Signed Messages v2) ## Что отправляет клиент - Клиент формирует **пару подписанных блоков** с одинаковой базой (`baseKey`): - `incoming` — сообщение для получателя (`targetLogin = toLogin`). - `outgoing` — копия для отправителя (`targetLogin = fromLogin`). - Пара отправляется методом `ReceiveOutcomingMessage` (старое имя `SendMessagePair` оставлено для совместимости). ## Что делает сервер при `ReceiveOutcomingMessage` 1. Валидирует поля запроса (`incomingBlobB64`, `outgoingBlobB64`). 2. Разбирает оба блока и проверяет, что это корректная пара. 3. Проверяет пользователей и криптоподписи по каждому блоку. 4. Пытается сохранить обе записи **одной транзакцией**: - либо добавляются **обе** записи, - либо при дубле/конфликте не добавляется **ни одна**. 5. Если пара реально добавилась в БД, сервер запускает realtime-доставку в активные сессии целевых пользователей. 6. Если это дубль, дальнейшая доставка не выполняется (повтор не разгоняется). ## Как сообщение доходит до клиента (WS + WebPush) 1. После успешной записи сервер пытается доставить сообщение во все активные сессии `targetLogin`. 2. Для каждой сессии сервер сначала создаёт/проверяет запись доставки в `signed_message_session_delivery` (pending). 3. Если сессия онлайн, сервер шлёт `SignedMessageArrived` по WebSocket. 4. Если сессия офлайн и тип сообщения входящий текст (`TYPE_INCOMING_TEXT`), сервер пробует WebPush (если у сессии сохранены `endpoint/p256dh/auth`). 5. При следующем логине/переподключении сервер дочитывает pending-сообщения и повторно отправляет их в эту сессию как backlog. ## Подтверждение доставки (ACK) - Используется метод `AckSessionDelivery`. - Клиент отправляет ACK после обработки `SignedMessageArrived` с `messageKey`. - Сервер помечает `(messageKey, sessionId)` как `delivered=1`, и это сообщение перестаёт быть pending для этой сессии. ### Важно про безопасность ACK - `AckSessionDelivery` требует авторизованную WS-сессию (`ctx.isAuthenticatedUser()`). - `sessionId` берётся сервером из текущего `ConnectionContext`, а не из payload запроса. - Поэтому подтвердить доставку «просто зная messageKey/sessionId» без авторизованной сессии нельзя. ## Почему допускаются дубли сети - В модели с несколькими серверами возможны повторные пересылки одного и того же сообщения. - Дедупликация делается на уровне БД по ключам записи. - За счёт этого «шторм» затухает: сервер, который уже видел сообщение, больше его не разгоняет. ## Будущая мультисерверная схема (цель) - Клиент может отправить `ReceiveOutcomingMessage` на любой из своих серверов. - Сервер-источник: - сохраняет пару, - рассылает исходящее по серверам пользователя A, - рассылает входящее по серверам пользователя B. - Остальные серверы повторяют тот же принцип, но благодаря дедупликации повторная пересылка быстро прекращается. ## Важные текущие ограничения - **A) Реальной мультисерверности пока нет.** Сейчас фактически предполагается один сервер на пользователя. - **B) Нет полноценного graceful shutdown для очереди пересылки.** Возможен сценарий: запись уже сохранена в БД, но сервер перезагрузился до пересылки дальше. Это нужно доработать отдельно.