8.6 KiB
8.6 KiB
Задача 02: Web Push + подписанный API отправки личных сообщений
Контекст (по текущему состоянию проекта)
- Уже есть JSON WebSocket API для личных сообщений:
SendDirectMessage,AckIncomingMessage,UpsertPushToken. - Сейчас серверный fallback-пуш реализован через FCM (
FcmPushSender) и ключfcm.server.key. - Клиент уже регистрирует service worker и токен Firebase, затем аплоадит push token на сервер.
Цель
Добавить полностью рабочий сценарий доставки личных сообщений с приоритетом:
- онлайн-доставка в активную WebSocket-сессию;
- если не подтверждено — Web Push;
- поддержать отдельный API отправки без авторизации, где доступ проверяется цифровой подписью Ed25519 по
deviceKeyотправителя.
Предварительная спецификация подписанного пакета (v1)
ВАЖНО: финально фиксируется после уточнений по endian/кодировкам/лимитам.
Пакет (binary):
prefix— ASCII-константа, напримерSHINE_MESSAGE.toLoginLen— 1 байт.toLogin— ASCII, длина =toLoginLen.fromLoginLen— 1 байт.fromLogin— ASCII, длина =fromLoginLen.timeMs— 8 байт (unix ms).nonce32— 4 байта случайное число.messageType— 4 байта.targetMode— 1 байт:0= всем сессиям пользователя,1= конкретной сессии.
- Если
targetMode=1:
sessionIdLen— 1 байт,sessionId— ASCII.
messageLen— 2 байта.messageBytes— бинарные данные длинойmessageLen.signature64— 64 байта, Ed25519 подпись всего блока безsignature64.
Ограничения (первичный draft):
- общий размер пакета ≤ 4000 байт;
- логины/префикс/идентификатор сессии — ASCII;
- повторы отсекаются по
(fromLogin, timeMs, nonce32)в окне TTL.
Сервер: что доработать
1) Новый endpoint без авторизации
Операция (через WS JSON обертку) условно SendSignedDirectMessage:
- принимает пакет (base64 binary blob);
- парсит и валидирует формат;
- достает
fromLogin, поднимаетdeviceKeyпользователя; - проверяет подпись Ed25519;
- проверяет анти-replay (time window + nonce);
- отправляет сообщение по правилам маршрутизации;
- пишет результат (messageId, каналы доставки, причины недоставки).
2) Маршрутизация доставки
Для targetMode=1:
- если целевая сессия онлайн и ACK пришел вовремя — успех;
- иначе отправка в Web Push этой сессии (если есть subscription).
Для targetMode=0:
- обход всех сессий пользователя;
- сначала online delivery + ACK;
- для непринятых/офлайн — Web Push по соответствующим subscription;
- если subscription отсутствует — тихий skip.
3) Миграция от FCM к Web Push
- добавить конфиг VAPID (
webpush.public.key,webpush.private.key,webpush.subject); - хранить на сервере не только token, а web-push subscription (endpoint + keys);
- сделать отправщик Web Push и заменить/расширить текущий
FcmPushSender.
4) Безопасность
- строгая ASCII-валидация логинов/sessionId;
- лимиты длины всех полей;
- rate limit на endpoint;
- audit-лог неуспешных проверок подписи/формата;
- защита от replay.
Клиент (shine-UI): что доработать
-
Перейти на стандартный Web Push flow:
- регистрация service worker;
PushManager.subscribe(...)с VAPID public key;- отправка subscription на сервер (
UpsertPushSubscriptionили расширениеUpsertPushToken).
-
Service worker:
pushhandler получает payload целиком;- показывает системное уведомление;
- при клике открывает/фокусирует нужный чат.
-
Online-сообщения:
- сохранить текущий event-канал
IncomingDirectMessage; - обязателен ACK (
AckIncomingMessageуже есть).
- сохранить текущий event-канал
-
Keep-alive:
- UI отправляет
Pingраз в 60 секунд при активной сессии.
- UI отправляет
Документация
Сделать отдельный документ настройки Web Push:
- как сгенерировать VAPID ключи;
- какие параметры прописать на сервере и в UI;
- как проверить локально e2e (онлайн + офлайн пуш);
- ограничения payload и рекомендации по ретраям.
Этапы реализации (предложение)
- Зафиксировать бинарный формат + валидации.
- Реализовать серверный parser/validator/signature verify/replay guard.
- Реализовать Web Push sender + storage subscription.
- Подключить новый endpoint и маршрутизацию доставки.
- Обновить UI (subscription + service worker + ping timer).
- Добавить интеграционные тесты (online ACK / offline push / bad signature / replay / oversize).
- Добавить документацию.
Что нужно уточнить до разработки
- Endian для
timeMs/nonce/messageType/messageLen(big-endian или little-endian). - Что именно подписывается: строго весь префикс..messageBytes (без подписи) — подтвердить.
- Диапазон допустимых
messageType. - TTL окна для анти-replay (например 5 минут / 15 минут).
- Лимиты длин для login/session/message.
- Можно ли временно оставить FCM как fallback, пока не готов Web Push в проде.
- Формат сообщения в
messageBytes: opaque bytes или UTF-8 строка.
Статус реализации (12.04.2026)
Что уже внедрено в коде
SendDirectMessageпереведён на signed-binary payload (blobB64) без обязательной авторизации WS-сессии.- Внедрён бинарный парсер пакета формата
SHiNE_msg + version(1) + ... + signature64. - Проверка подписи Ed25519 делается по
deviceKeyотправителя черезshine-server-crypto(Ed25519Util). - Добавлен anti-replay guard
(from_login, time_ms, nonce)с TTL 15 минут. - Добавлено историческое хранилище
signed_direct_messages_historyс сырым пакетомraw_packet. - Логика доставки: сначала WS+ACK, затем fallback на Web Push (по подписке конкретной session).
- Поле типа сообщения переведено на
uint16, пока поддерживается только1. - Для
targetMode=1при несуществующей сессии возвращаетсяsuccessсsessionNotFound=trueиdelivered=0. - UI переведён с Firebase/FCM на браузерный
PushManager.subscribe+ Service Workerpush. - Добавлен keep-alive ping из UI раз в 60 секунд при авторизованной сессии.
Что настроить в окружении
- В
application.propertiesзадать:webpush.vapid.publicwebpush.vapid.privatewebpush.vapid.subject
- В
shine-UI/index.htmlзадать публичный VAPID ключ вwindow.__SHINE_WEBPUSH_VAPID_PUBLIC_KEY__.