Backend / WebSocket

WebSocket система

События реального времени, доставка сообщений, статусы активности и WebRTC сигнализация. Файл: srv/app/websocket/connection_manager.py

Обзор WebSocket точек входа

Бэкенд предоставляет две WebSocket конечные точки:

  1. /ws — Основной канал для пользователей. Требует передачи JWT токена в параметре запроса ?token={jwt}.
  2. /ws/qr?room_id={uuid} — Канал сопряжения устройств через QR-код.

Обработка входящих JSON событий (Клиент → Сервер)

Каждое отправляемое клиентом сообщение по протоколу WebSocket должно представлять собой JSON-объект, содержащий поле type. Ниже подробно описаны все поддерживаемые входящие типы сообщений:

1. Пинг-Понг (ping)

Используется для поддержания активности соединения (keep-alive) и измерения задержки (RTT). Рекомендуемый интервал: каждые 15-30 секунд.

Client Request
{
  "type": "ping"
}

2. Управление статусом присутствия (set_online / set_offline)

Ручное управление статусом активности пользователя. Статус также переводится в offline автоматически при разрыве сокет-соединения.

Client Request
{
  "type": "set_online" // или "set_offline"
}

3. Отправка E2EE сообщения (private_message)

Отправка зашифрованного сообщения в личный чат. Поля content, reply_to_text и file_name должны быть зашифрованы ключом AES-GCM (через общий секрет X25519) и закодированы в Base64.

Client Request
{
  "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)

Отправка зашифрованного сообщения в групповой чат. Содержимое шифруется с использованием групповых ключей текущей эпохи группы.

Client Request
{
  "type": "group_message",
  "group_id": -105,                      // ID группы (всегда отрицательный, обязательно)
  "content": "2:Base64GroupCipher...",   // Формат: "эпоха:шифртекст" (обязательно)
  "temp_id": "client-group-uuid-789",    // Временный ID (обязательно)
  "message_type": "text"                 // Тип содержимого (обязательно)
}

5. Редактирование сообщения (edit_message)

Изменение текста сообщения в БД. Работает как для личных, так и для групповых чатов.

Client Request
{
  "type": "edit_message",
  "message_id": 4820,                   // Серверный ID сообщения (обязательно)
  "content": "Base64NewEncryptedText..."// Новый зашифрованный текст (обязательно)
}

6. Удаление сообщения (delete_message)

Полное удаление сообщения из базы данных. Сервер удалит запись и разошлет уведомление получателю.

Client Request
{
  "type": "delete_message",
  "message_id": 4820                    // Серверный ID сообщения (обязательно)
}

7. Подтверждение прочтения (read_receipt)

Сообщает серверу и собеседнику, что конкретное входящее сообщение было открыто и прочитано пользователем.

Client Request
{
  "type": "read_receipt",
  "message_id": 4820                    // ID прочитанного сообщения (обязательно)
}

8. Прочитать все сообщения в чате (read_all_chat)

Массовое подтверждение прочтения всех сообщений от конкретного контакта. Сервер обновит статус всех непрочитанных сообщений.

Client Request
{
  "type": "read_all_chat",
  "contact_id": 102                      // ID собеседника (обязательно)
}

9. Индикаторы набора текста (typing / stop_typing)

Оповещение собеседника о том, что пользователь вводит текст в поле ввода.

Client Request
{
  "type": "typing", // или "stop_typing"
  "receiver_id": 102                     // ID получателя (обязательно)
}

10. Инициация звонка (call_init)

Создание сессии WebRTC аудио/видео вызова. Сервер генерирует уникальный call_id и рассылает сигналы.

Client Request
{
  "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 соединения.

Client Request
{
  "type": "ice_candidate",
  "receiver_id": 102,
  "candidate": "candidate:84213 1 UDP...",
  "sdpMLineIndex": 0,
  "sdpMid": "0"
}

12. Управление звонком (hangup / decline / call_accepted)

Сообщения управления звонком. Сервер дублирует их обоим участникам, а также отправляет фоновое FCM push-уведомление высокой приоритетности для синхронизации звонков на девайсах.

Client Request
{
  "type": "hangup", // hangup, decline, call_accepted
  "receiver_id": 102,
  "call_id": "call-uuid-987"
}

Исходящие JSON события (Сервер → Клиент)

События, генерируемые сервером и отправляемые клиентам. Клиент должен слушать WebSocket канал и обрабатывать эти сообщения:

1. Ответ на пинг (pong)

Server Event
{
  "type": "pong"
}

2. Изменение статуса контакта (user_online / user_offline)

Рассылается всем активным контактам пользователя, когда он входит в сеть или отключается от WebSocket.

Server Event
{
  "type": "user_online", // или "user_offline"
  "user_id": 102
}

3. Квитанция об отправке (message_sent)

Подтверждение от сервера, что сообщение принято и записано в базу данных. Сопоставляется по temp_id.

Server Event
{
  "type": "message_sent",
  "temp_id": "client-temp-uuid-456",
  "id": 4820,                           // Серверный ID сообщения (присвоенный базой данных)
  "timestamp": "2026-07-06T23:25:40",   // Время создания на сервере
  "receiver_id": 102
}

4. Квитанция о доставке (message_delivered)

Отправляется отправителю, когда получатель находится онлайн, и сообщение успешно доставлено в его сокет-сессию.

Server Event
{
  "type": "message_delivered",
  "message_id": 4820,
  "timestamp": "2026-07-06T23:25:41",
  "sender_id": 100,
  "receiver_id": 102
}

5. Квитанция о прочтении (message_read)

Отправляется отправителю, когда получатель прочитал сообщение (вызвал read_receipt).

Server Event
{
  "type": "message_read",
  "message_id": 4820,
  "timestamp": "2026-07-06T23:25:45",
  "sender_id": 100,
  "receiver_id": 102
}

6. Входящее личное E2EE сообщение (private_message)

Пересылается получателю в реальном времени. Включает зашифрованный контент и метаданные.

Server Event
{
  "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)

Пересылается всем участникам группы, кроме отправителя.

Server Event
{
  "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)

Оповещения о редактировании или удалении сообщений. Пересылаются собеседникам.

Server Event (Edited)
{
  "type": "message_edited",
  "message_id": 4820,
  "content": "Base64NewEncryptedText..."
}
Server Event (Deleted)
{
  "type": "message_deleted",
  "message_id": 4820
}

9. Оповещение о прочтении всего чата (all_chat_read)

Оповещает отправителя о том, что собеседник прочитал все сообщения в чате с ним.

Server Event
{
  "type": "all_chat_read",
  "contact_id": 102 // Кто прочитал
}

10. Входящий индикатор набора текста (typing / stop_typing)

Пересылает статус набора текста от отправителя.

Server Event
{
  "type": "typing", // или "stop_typing"
  "sender_id": 100
}

11. Создание вызова на стороне сервера (call_created)

Возвращается инициатору звонка, подтверждая успешный запуск сигнальной сессии.

Server Event
{
  "type": "call_created",
  "call_id": "call-uuid-987"
}

12. Входящий вызов (call_init)

Пересылается вызываемому пользователю для запуска входящего звонка.

Server Event
{
  "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 для скачивания пропущенных событий и синхронизации ключей перед тем, как расшифровать новые групповые сообщения.