~/goydacord инженерная сторона
Как это
устроено
Голосовой мессенджер с серверами, звонками, демонстрацией экрана и клиентом для Windows. Ниже — архитектура, ключевые решения и цифры, которые можно проверить.
- 351автотест бэкендаPHPUnit, CI на каждый пуш
- 580+тестов фронтендаJest + Testing Library
- 1080p·60показ экранаH.264 на видеокарте
- 98Lighthouse, десктопдоступность и SEO — 100
01 · архитектура
Кто с кем разговаривает
Страницы и API идут через nginx, события — по WebSocket, а голос, видео и экран — мимо бэкенда, прямо в медиасервер. Бэкенд только выдаёт токены комнат и следит за правами.
02 · решения
Что сделано и зачем
Каждое решение — ответ на конкретную проблему: что болело, как чинили и что получилось.
01Звук и видео
SFU вместо сетки P2P
В звонке на пятерых по схеме P2P каждый отправляет свой поток четырём людям — и упирается в исходящий канал. Голосовые каналы, личные и групповые звонки идут через свой LiveKit SFU: каждый отправляет поток один раз, сервер раздаёт.
Медиа ходит через один UDP-порт, для строгих сетей есть TCP и свой TURN (coturn).
Модерация — заглушить, отключить звук, перетащить в другой канал — проверяется правами на сервере и применяется прямо в комнате.
02Демонстрация экрана
1080p · 60 кадров без нагрузки на процессор
LiveKit по умолчанию кодирует видео в VP8 программно: на 1080p60 это около двух ядер процессора — у стримящего проседает игра.
Экран публикуется в H.264, и Chromium отдаёт кодирование видеокарте (NVENC, AMF, Quick Sync). Когда сеть не вывозит, держим частоту кадров, а не резкость: в игре плавность важнее.
Кодирует видеокарта, если умеет, — процессор остаётся игре.
03Шумодав
Нейросеть прямо в браузере
DeepFilterNet скомпилирован в WebAssembly и работает в AudioWorklet у самого пользователя: голос не уходит на сервер ради обработки. Вариант полегче — RNNoise, переключается в настройках.
Модель весит 17 МБ, поэтому nginx отдаёт её заранее сжатой (gzip_static), без сжатия на лету.
Клавиатура, кулер и улица вырезаются до отправки звука.
04Файлы
Гигабайты с докачкой
Файл уходит кусками по 16 МБ. Оборвалась связь — загрузка продолжается с того же места, а не с нуля.
Небольшие вложения шифруются на диске целиком, большие отдаются потоком с перемоткой (HTTP Range). У каждого аккаунта квота 2 ГБ; если на диске кончается место, первыми уходят давно не открытые файлы.
Видео на гигабайт отправляется так же, как картинка.
05Хранение
Переписка зашифрована в базе
Сообщения лежат в базе зашифрованными AES-256-GCM, у каждого записан номер ключа — ключи можно менять, не перешифровывая старую историю.
Честно о границах: это шифрование на сервере, а не сквозное между устройствами.
Утечка дампа базы не раскрывает переписку.
06Реалтайм
WebSocket и пуши
Сообщения, реакции, звонки, упоминания и ответы поддержки приходят по WebSocket через Laravel Reverb в приватные каналы пользователя.
Если вкладка закрыта — Web Push (VAPID): входящий звонок и личное сообщение приходят системным уведомлением.
Ответ поддержки виден сразу, без перезагрузки страницы.
07Надёжность
Звонок переживает сбои
Звонок восстанавливается после перезагрузки вкладки и перезапуска приложения. Присутствие держится на heartbeat, зависшие сессии убирает серверный крон.
Таймеры переподключения сами планируют следующий шаг и не зависят от перерисовок React. При деплое старые JS-чанки хранятся ещё 7 дней, чтобы открытая вкладка с идущим звонком не упала на ChunkLoadError.
Выкатка новой версии не обрывает текущие звонки.
08Windows
Десктоп-клиент на Electron
Автообновление скачивает только изменившиеся блоки установщика. Клиент видит запущенную игру и показывает её друзьям в статусе, отсеивая лаунчеры и античиты.
Пока идёт звонок, клиент поднимает приоритет своих процессов и отключает для них энергосбережение Windows (EcoQoS): иначе в полноэкранной игре система душит фон и голос начинает заикаться.
Голос не прерывается, даже когда игра на весь экран.
09Сеть в России
Разбор блокировки у провайдера
У части абонентов одного провайдера сайт открывался «через раз». Разобрали по шагам: TCP до сервера проходит, TLS с SNI обрывается, без SNI — работает, а соседние сайты из той же подсети /24 недоступны точно так же.
Вывод — фильтрация на оборудовании провайдера по подсети, а не ошибка на нашей стороне. Сайт и десктоп-клиент перевели на HTTP/2: одно TLS-соединение вместо шести.
Проверка из 40 точек по России (Globalping): сайт открылся в 39.
10Эта страница
Демо-вход в один клик
Кнопка «Открыть демо» создаёт временный аккаунт с готовым сервером, ролями, друзьями и перепиской — она хранится зашифрованной, как настоящая.
Гостю закрыто всё, через что можно написать реальным людям, у него своя квота на файлы, в статистику он не попадает, а через сутки его удаляет крон вместе с сервером и чатами.
Можно пощупать продукт без регистрации — и никому не помешать.
03 · качество
Как держим порядок
- Фронтенд разложен по Feature-Sliced Design: app, widgets, features, entities, shared.
- Бэкенд: маршрут → Form Request → контроллер → политика → сервис. Ожидаемые ошибки — типизированные исключения с единым форматом ответа.
- PHPStan (Larastan) 5-го уровня без baseline и Pint. CI на GitHub Actions: стиль, статический анализ, тесты.
- React Compiler со строгими правилами: без setState в эффектах и чтения ref во время рендера.
- Лимиты запросов именованные, у каждого свой счётчик: пинги не съедают лимит входа.
- Уважение к «меньше движения» в системе: анимации мягче, спиннеры не замирают.
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
Лучше один раз потрогать
Демо открывается за пару секунд: свой сервер, друзья и переписка уже на месте. Через сутки всё удалится само.