WebSocket система
События реального времени, доставка сообщений, статусы активности и WebRTC сигнализация. Файл: srv/app/websocket/connection_manager.py
Обзор WebSocket точек входа
Бэкенд предоставляет две WebSocket конечные точки:
/ws— Основной канал для пользователей. Требует передачи JWT токена в параметре запроса?token={jwt}./ws/qr?room_id={uuid}— Канал сопряжения устройств через QR-код.
Обработка входящих JSON событий (Клиент → Сервер)
Каждое отправляемое клиентом сообщение по протоколу WebSocket должно представлять собой JSON-объект, содержащий поле type. Ниже подробно описаны все поддерживаемые входящие типы сообщений:
1. Пинг-Понг (ping)
Используется для поддержания активности соединения (keep-alive) и измерения задержки (RTT). Рекомендуемый интервал: каждые 15-30 секунд.
{
"type": "ping"
}
2. Управление статусом присутствия (set_online / set_offline)
Ручное управление статусом активности пользователя. Статус также переводится в offline автоматически при разрыве сокет-соединения.
{
"type": "set_online" // или "set_offline"
}
3. Отправка E2EE сообщения (private_message)
Отправка зашифрованного сообщения в личный чат. Поля content, reply_to_text и file_name должны быть зашифрованы ключом AES-GCM (через общий секрет X25519) и закодированы в Base64.
{
"type": "private_message",
"recipient_id": 102, // ID получателя сообщения (обязательно)
"content": "Base64AESGCMText...", // Зашифрованный текст сообщения (обязательно)
"temp_id": "client-temp-uuid-456", // Временный ID для сопоставления ACK-ответа (обязательно)
"message_type": "image", // text, image, video, voice_note, video_note, file (обязательно)
"reply_to_id": 1025, // ID сообщения, на которое создается ответ (опционально)
"file_id": "media-uuid-abcd", // UUID файла в медиа-хранилище (опционально)
"encrypted_file_key": "Base64Key...", // Зашифрованный ключ расшифровки файла (опционально)
"file_name": "Base64EncryptedName...",// Зашифрованное имя файла (опционально)
"file_size": 1048576 // Размер файла в байтах (опционально)
}
4. Отправка группового сообщения (group_message)
Отправка зашифрованного сообщения в групповой чат. Содержимое шифруется с использованием групповых ключей текущей эпохи группы.
{
"type": "group_message",
"group_id": -105, // ID группы (всегда отрицательный, обязательно)
"content": "2:Base64GroupCipher...", // Формат: "эпоха:шифртекст" (обязательно)
"temp_id": "client-group-uuid-789", // Временный ID (обязательно)
"message_type": "text" // Тип содержимого (обязательно)
}
5. Редактирование сообщения (edit_message)
Изменение текста сообщения в БД. Работает как для личных, так и для групповых чатов.
{
"type": "edit_message",
"message_id": 4820, // Серверный ID сообщения (обязательно)
"content": "Base64NewEncryptedText..."// Новый зашифрованный текст (обязательно)
}
6. Удаление сообщения (delete_message)
Полное удаление сообщения из базы данных. Сервер удалит запись и разошлет уведомление получателю.
{
"type": "delete_message",
"message_id": 4820 // Серверный ID сообщения (обязательно)
}
7. Подтверждение прочтения (read_receipt)
Сообщает серверу и собеседнику, что конкретное входящее сообщение было открыто и прочитано пользователем.
{
"type": "read_receipt",
"message_id": 4820 // ID прочитанного сообщения (обязательно)
}
8. Прочитать все сообщения в чате (read_all_chat)
Массовое подтверждение прочтения всех сообщений от конкретного контакта. Сервер обновит статус всех непрочитанных сообщений.
{
"type": "read_all_chat",
"contact_id": 102 // ID собеседника (обязательно)
}
9. Индикаторы набора текста (typing / stop_typing)
Оповещение собеседника о том, что пользователь вводит текст в поле ввода.
{
"type": "typing", // или "stop_typing"
"receiver_id": 102 // ID получателя (обязательно)
}
10. Инициация звонка (call_init)
Создание сессии WebRTC аудио/видео вызова. Сервер генерирует уникальный call_id и рассылает сигналы.
{
"type": "call_init",
"receiver_id": 102, // ID вызываемого пользователя (обязательно)
"sdp_offer": "v=0\no=- 87343...", // WebRTC Offer строка (обязательно)
"video_enabled": true // Флаг включения камеры (обязательно)
}
11. Сигнальные пакеты WebRTC (offer / answer / ice_candidate / video_toggle / request_keyframe)
Эти сообщения транслируются сервером получателю без изменений для установления Peer-to-Peer соединения.
{
"type": "ice_candidate",
"receiver_id": 102,
"candidate": "candidate:84213 1 UDP...",
"sdpMLineIndex": 0,
"sdpMid": "0"
}
12. Управление звонком (hangup / decline / call_accepted)
Сообщения управления звонком. Сервер дублирует их обоим участникам, а также отправляет фоновое FCM push-уведомление высокой приоритетности для синхронизации звонков на девайсах.
{
"type": "hangup", // hangup, decline, call_accepted
"receiver_id": 102,
"call_id": "call-uuid-987"
}
Исходящие JSON события (Сервер → Клиент)
События, генерируемые сервером и отправляемые клиентам. Клиент должен слушать WebSocket канал и обрабатывать эти сообщения:
1. Ответ на пинг (pong)
{
"type": "pong"
}
2. Изменение статуса контакта (user_online / user_offline)
Рассылается всем активным контактам пользователя, когда он входит в сеть или отключается от WebSocket.
{
"type": "user_online", // или "user_offline"
"user_id": 102
}
3. Квитанция об отправке (message_sent)
Подтверждение от сервера, что сообщение принято и записано в базу данных. Сопоставляется по temp_id.
{
"type": "message_sent",
"temp_id": "client-temp-uuid-456",
"id": 4820, // Серверный ID сообщения (присвоенный базой данных)
"timestamp": "2026-07-06T23:25:40", // Время создания на сервере
"receiver_id": 102
}
4. Квитанция о доставке (message_delivered)
Отправляется отправителю, когда получатель находится онлайн, и сообщение успешно доставлено в его сокет-сессию.
{
"type": "message_delivered",
"message_id": 4820,
"timestamp": "2026-07-06T23:25:41",
"sender_id": 100,
"receiver_id": 102
}
5. Квитанция о прочтении (message_read)
Отправляется отправителю, когда получатель прочитал сообщение (вызвал read_receipt).
{
"type": "message_read",
"message_id": 4820,
"timestamp": "2026-07-06T23:25:45",
"sender_id": 100,
"receiver_id": 102
}
6. Входящее личное E2EE сообщение (private_message)
Пересылается получателю в реальном времени. Включает зашифрованный контент и метаданные.
{
"type": "private_message",
"id": 4820,
"sender_id": 100,
"receiver_id": 102,
"content": "Base64AESGCMText...",
"timestamp": "2026-07-06T23:25:40",
"message_type": "image",
"reply_to_id": null,
"file_id": "media-uuid-abcd",
"encrypted_file_key": "Base64Key...",
"file_name": "Base64EncryptedName...",
"file_size": 1048576
}
7. Входящее групповое сообщение (group_message)
Пересылается всем участникам группы, кроме отправителя.
{
"type": "group_message",
"id": 5012,
"sender_id": 100,
"group_id": -105,
"content": "2:Base64GroupCipher...",
"timestamp": "2026-07-06T23:25:42",
"message_type": "text"
}
8. Оповещения об изменении сообщений (message_edited / message_deleted)
Оповещения о редактировании или удалении сообщений. Пересылаются собеседникам.
{
"type": "message_edited",
"message_id": 4820,
"content": "Base64NewEncryptedText..."
}
{
"type": "message_deleted",
"message_id": 4820
}
9. Оповещение о прочтении всего чата (all_chat_read)
Оповещает отправителя о том, что собеседник прочитал все сообщения в чате с ним.
{
"type": "all_chat_read",
"contact_id": 102 // Кто прочитал
}
10. Входящий индикатор набора текста (typing / stop_typing)
Пересылает статус набора текста от отправителя.
{
"type": "typing", // или "stop_typing"
"sender_id": 100
}
11. Создание вызова на стороне сервера (call_created)
Возвращается инициатору звонка, подтверждая успешный запуск сигнальной сессии.
{
"type": "call_created",
"call_id": "call-uuid-987"
}
12. Входящий вызов (call_init)
Пересылается вызываемому пользователю для запуска входящего звонка.
{
"type": "call_init",
"call_id": "call-uuid-987",
"sender_id": 100,
"sdp_offer": "v=0\no=- 87343...",
"video_enabled": true
}
Логика работы E2EE при доставке
WebSocket сервер действует как абсолютно «слепой» ретранслятор для шифртекстов. Сервер не имеет доступа к приватным ключам пользователей:
- Для private_message клиент должен получить публичный ключ получателя (через
GET /users/{user_id}), вычислить общий X25519 секрет через HKDF, зашифровать полезную нагрузку AES-GCM-256 и передать зашифрованный текст в сокет. - Для group_message клиент работает по протоколу группового шифрования. Каждое сокет-сообщение содержит префикс с индексом эпохи группы. Если клиент отстает по эпохам, он может вызвать REST API
GET /groups/{group_id}/sync_eventsдля скачивания пропущенных событий и синхронизации ключей перед тем, как расшифровать новые групповые сообщения.