К содержанию
Попробовать

~/goydacord инженерная сторона

Как это
устроено

Голосовой мессенджер с серверами, звонками, демонстрацией экрана и клиентом для Windows. Ниже архитектура, основные решения и цифры.

Смотреть схему
  • 354теста бэкендаPHPUnit, CI на каждый пуш
  • 580+тестов фронтендаJest + Testing Library
  • 1080p·60показ экранаH.264 на видеокарте
  • 98Lighthouse, десктопдоступность и SEO: 100

01 · архитектура

Кто с кем разговаривает

Страницы и API идут через nginx, события по WebSocket, а голос, видео и экран идут мимо бэкенда, прямо в медиасервер. Бэкенд только выдаёт токены комнат и следит за правами.

Архитектура SonetCordБраузер, приложение для Windows и телефон ходят через nginx по HTTPS к Next.js, Laravel API и Reverb. Laravel хранит данные в MySQL и Redis и отправляет Web Push. Голос, видео и экран идут напрямую в LiveKit SFU, а в строгих сетях через TURN.HTTPSсобытиятокены комнат, модерацияWebRTC: голос, видео, экранTURNБраузерNext.js 16 · React 19WindowsElectron 37Телефонбраузер + Web PushnginxHTTP/2 · TLSNext.jsSSR-страницы и приложениеLaravel ReverbWebSocket, приватные каналыLaravel 12 APISanctum · политики · сервисыMySQLпереписка в AES-256-GCMRedisкэш, лимиты, превьюWeb PushVAPID, звонки и ЛСLiveKit SFUUDP 7882 · TCP 7881coturnTURN для строгих сетейзапросы, WebSocketмедиа WebRTC

02 · решения

Что сделано и зачем

Что пришлось решать по ходу и чем это закончилось.

  1. 01Звук и видео

    SFU вместо сетки P2P

    В звонке на пятерых по схеме P2P каждый отправляет свой поток четырём людям и упирается в исходящий канал. Поэтому голосовые каналы, личные и групповые звонки идут через свой LiveKit SFU: каждый отправляет поток один раз, дальше его раздаёт сервер.

    Медиа ходит через один UDP-порт, для строгих сетей есть TCP и свой TURN (coturn).

    Модерацию (заглушить, отключить звук, перетащить в другой канал) проверяют права на сервере, и применяется она прямо в комнате.

  2. 02Демонстрация экрана

    1080p · 60 кадров без нагрузки на процессор

    LiveKit по умолчанию кодирует видео в VP8 программно, и на 1080p60 это около двух ядер процессора. У того, кто стримит, начинает проседать игра.

    Экран публикуется в H.264, и Chromium отдаёт кодирование видеокарте (NVENC, AMF, Quick Sync). Когда сеть не вывозит, жертвуем резкостью и держим частоту кадров: в игре плавность важнее.

    Если видеокарта умеет кодировать H.264, процессор остаётся игре.

  3. 03Шумодав

    Нейросеть прямо в браузере

    DeepFilterNet скомпилирован в WebAssembly и работает в AudioWorklet у самого пользователя: голос не уходит на сервер ради обработки. Есть вариант полегче, RNNoise, он переключается в настройках.

    Модель весит 17 МБ, поэтому nginx отдаёт её заранее сжатой (gzip_static), без сжатия на лету.

    Клавиатура, кулер и улица вырезаются до отправки звука.

  4. 04Файлы

    Гигабайты с докачкой

    Файл уходит кусками по 16 МБ. Если связь оборвалась, загрузка продолжается с того же места.

    Небольшие вложения шифруются на диске целиком, большие отдаются потоком с перемоткой (HTTP Range). У каждого аккаунта квота 2 ГБ; если на диске кончается место, первыми уходят давно не открытые файлы.

    Видео на гигабайт отправляется так же, как картинка.

  5. 05Хранение

    Переписка зашифрована в базе

    Сообщения лежат в базе зашифрованными (AES-256-GCM), у каждого записан номер ключа, так что ключи можно менять, не перешифровывая старую историю.

    Шифрование серверное, сквозного между устройствами нет.

    Утечка дампа базы не раскрывает переписку.

  6. 06Реалтайм

    WebSocket и пуши

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

    Когда вкладка закрыта, работает Web Push (VAPID): входящий звонок и личное сообщение приходят системным уведомлением.

    Ответ поддержки виден сразу, без перезагрузки страницы.

  7. 07Надёжность

    Звонок переживает сбои

    Звонок восстанавливается после перезагрузки вкладки и перезапуска приложения. Присутствие держится на heartbeat, зависшие сессии убирает серверный крон.

    Таймеры переподключения сами планируют следующий шаг и не зависят от перерисовок React. При деплое старые JS-чанки хранятся ещё 7 дней, чтобы открытая вкладка с идущим звонком не упала на ChunkLoadError.

    Выкатка новой версии не обрывает текущие звонки.

  8. 08Windows

    Десктоп-клиент на Electron

    Автообновление скачивает только изменившиеся блоки установщика. Клиент видит запущенную игру и показывает её друзьям в статусе, отсеивая лаунчеры и античиты.

    Пока идёт звонок, клиент поднимает приоритет своих процессов и отключает для них энергосбережение Windows (EcoQoS): иначе в полноэкранной игре система душит фон и голос начинает заикаться.

    Голос не прерывается, даже когда игра на весь экран.

  9. 09Сеть в России

    Разбор блокировки у провайдера

    У части абонентов одного провайдера сайт открывался через раз. Разбирали по шагам: TCP до сервера проходит, TLS с SNI обрывается, без SNI работает, а соседние сайты из той же подсети /24 недоступны точно так же.

    Вывод: провайдер фильтрует по подсети, наш сервер тут ни при чём. Сайт и десктоп-клиент перевели на HTTP/2, чтобы ходить одним TLS-соединением вместо шести.

    Проверка из 40 точек по России (Globalping): сайт открылся в 39.

  10. 10Эта страница

    Демо-вход в один клик

    Кнопка «Открыть демо» создаёт временный аккаунт с готовым сервером, ролями, друзьями и перепиской. Переписка хранится зашифрованной, как у всех.

    Гостю закрыто всё, через что можно написать реальным людям, у него своя квота на файлы, в статистику он не попадает, а через сутки его удаляет крон вместе с сервером и чатами.

    Продукт можно пощупать без регистрации и никому при этом не помешать.

03 · качество

Как держим порядок

04 · стек

На чём написано

Фронтенд
Next.js 16React 19TypeScriptRedux Toolkit + RTK QueryTailwind CSS 4livekit-client
Бэкенд
PHP 8.4Laravel 12SanctumReverbMySQLRedis
Медиа
WebRTCLiveKit SFUcoturnDeepFilterNet · WASMRNNoise
Десктоп
Electron 37electron-builderelectron-updater
Инфраструктура
nginx · HTTP/2systemdпланировщик LaravelGitHub Actions

Лучше один раз потрогать

Демо открывается за пару секунд: свой сервер, друзья и переписка уже на месте. Через сутки всё удалится само.

Завести аккаунт