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