SHiNE-server/Логика_доставки_почты.md

5.0 KiB
Raw Blame History

Логика доставки почты (Signed Messages v2)

Что отправляет клиент

  • Клиент формирует пару подписанных блоков с одинаковой базой (baseKey):
  • incoming — сообщение для получателя (targetLogin = toLogin).
  • outgoing — копия для отправителя (targetLogin = fromLogin).
  • Пара отправляется методом ReceiveOutcomingMessage (старое имя SendMessagePair оставлено для совместимости).

Что делает сервер при ReceiveOutcomingMessage

  1. Валидирует поля запроса (incomingBlobB64, outgoingBlobB64).
  2. Разбирает оба блока и проверяет, что это корректная пара.
  3. Проверяет пользователей и криптоподписи по каждому блоку.
  4. Пытается сохранить обе записи одной транзакцией:
  • либо добавляются обе записи,
  • либо при дубле/конфликте не добавляется ни одна.
  1. Если пара реально добавилась в БД, сервер запускает realtime-доставку в активные сессии целевых пользователей.
  2. Если это дубль, дальнейшая доставка не выполняется (повтор не разгоняется).

Как сообщение доходит до клиента (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 для очереди пересылки. Возможен сценарий: запись уже сохранена в БД, но сервер перезагрузился до пересылки дальше. Это нужно доработать отдельно.