Vibe-X

Мессенджер, который я делаю и держу в проде самостоятельно — от схемы базы до nginx на VDS. Чаты с WebSocket-обновлениями, вложения, реакции, статусы прочтения и присутствия, режим Call Hub с голосовыми и видеозвонками на WebRTC, секретные чаты со сквозным шифрованием за фиче-флагом. Кроме веба есть desktop-оболочка на Tauri и мобильный клиент на React Native.

  • React 18
  • TypeScript
  • Vite
  • WebSocket
  • WebRTC
  • Java 21
  • Spring Boot 3
  • PostgreSQL
  • Redis
  • Docker
  • Полный цикл в одиночку: бэкенд на Java 21 и Spring Boot 3, фронтенд на React 18, деплой на собственный VDS.
  • Real-time слой на WebSocket: доставка сообщений, индикатор набора текста, присутствие и счётчики непрочитанного.
  • Голосовые и видеозвонки на WebRTC с сигналингом поверх WebSocket и своим TURN-сервером.
  • Секретные чаты со сквозным шифрованием за фиче-флагом.
  • Три клиента на одном API: web, desktop на Tauri и mobile на React Native.

Как это было устроено

Пет-проект без команды и без заказчика: мессенджер, на котором я разбираю то, что не встречается в продуктовой разработке CRM — real-time, WebRTC, собственная инфраструктура.

Задача

  • WebSocket-соединение рвётся постоянно: спящий ноутбук, метро, переключение Wi-Fi на мобильную сеть.
  • Токен доступа нельзя класть в URL сокета — он осядет в логах доступа nginx.
  • Звонки требуют сигналинга: обмен offer/answer и ICE-кандидатами должен пережить те же обрывы связи.
  • Один и тот же API обслуживает три разных клиента.

Решение

  • Переподключение с экспоненциальной задержкой: 1.5 секунды, каждая следующая попытка в 1.6 раза дольше, потолок 10 секунд.
  • Мгновенное переподключение по событию online вместо ожидания таймера — возврат связи не заставляет ждать.
  • Авторизация через Sec-WebSocket-Protocol: токен уходит в заголовке рукопожатия, а не в строке запроса, сервер выводит личность из токена.
  • Статус соединения поднят в состояние приложения, интерфейс честно показывает переподключение вместо тишины.

Роль

Единственный разработчик

Стек

Full-stack: Java 21 + React 18

Клиенты

Web, desktop (Tauri), mobile (React Native)

Инфраструктура

Свой VDS, Docker, nginx, TURN-сервер

Переподключение WebSocket с экспоненциальной задержкой
// Авторизация через Sec-WebSocket-Protocol: ['vibex.auth', token] —
// токен не попадает в URL и в логи доступа. Сервер валидирует его
// и выводит личность из токена.
const socket = new WebSocket(`${wsBaseUrl}/ws/chats/${activeChatId}`, [
  'vibex.auth',
  token,
]);

socket.onopen = () => {
  reconnectAttemptsRef.current = 0;
  reconnectDelayMsRef.current = 1500;
  emitStatus('open');
};

socket.onclose = (event) => {
  emitStatus('closed', { closeCode: event.code });
  if (!shouldReconnectRef.current) return;

  reconnectAttemptsRef.current += 1;
  const delay = reconnectDelayMsRef.current;
  reconnectDelayMsRef.current = Math.min(
    Math.round(reconnectDelayMsRef.current * 1.6),
    10_000,
  );
  reconnectTimeoutRef.current = window.setTimeout(() => connect(true), delay);
};

// Связь вернулась — подключаемся сразу, не дожидаясь следующего тика таймера.
const handleOnline = () => {
  if (!shouldReconnectRef.current) return;
  reconnectDelayMsRef.current = 1500;
  connect(true);
};
Визуал компонента
online
Созвон в 18:00?
Да, буду✓✓