Vibe-X

A messenger I build and run in production on my own — from the database schema to nginx on a VDS. Chats with WebSocket updates, attachments, reactions, read and presence states, a Call Hub mode with voice and video calls over WebRTC, and opt-in end-to-end encrypted secret chats behind a feature flag. Beyond the web there is a Tauri desktop shell and a React Native mobile client.

  • React 18
  • TypeScript
  • Vite
  • WebSocket
  • WebRTC
  • Java 21
  • Spring Boot 3
  • PostgreSQL
  • Redis
  • Docker
  • Full stack solo: Java 21 and Spring Boot 3 on the backend, React 18 on the frontend, deployed to my own VDS.
  • Real-time layer over WebSocket: message delivery, typing indicators, presence and unread counters.
  • Voice and video calls over WebRTC with signalling on top of WebSocket and a self-hosted TURN server.
  • Opt-in end-to-end encrypted secret chats behind a feature flag.
  • Three clients on one API: web, desktop (Tauri) and mobile (React Native).

How it was built

A side project with no team and no client: a messenger where I work through what product CRM development does not cover — real-time, WebRTC and my own infrastructure.

Problem

  • A WebSocket connection drops constantly: a sleeping laptop, the metro, switching from Wi-Fi to mobile data.
  • The access token cannot go into the socket URL — it would end up in nginx access logs.
  • Calls need signalling: the offer/answer and ICE candidate exchange has to survive the same drops.
  • One API serves three different clients.

Solution

  • Reconnect with exponential backoff: 1.5 seconds, each next attempt 1.6x longer, capped at 10 seconds.
  • Instant reconnect on the online event instead of waiting for the timer — a restored connection does not make you wait.
  • Authentication via Sec-WebSocket-Protocol: the token travels in the handshake header rather than the query string, and the server derives identity from the token.
  • Connection status lifted into application state so the UI honestly shows reconnecting instead of silence.

Role

Sole developer

Stack

Full-stack: Java 21 + React 18

Clients

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

Infrastructure

Own VDS, Docker, nginx, TURN server

WebSocket reconnect with exponential backoff
// Auth via Sec-WebSocket-Protocol: ['vibex.auth', token] keeps the token
// out of the URL and out of access logs. The server validates it and
// derives the identity from the token.
const socket = new WebSocket(`${wsBaseUrl}/ws/chats/${activeChatId}`, [
  'vibex.auth',
  token,
]);

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

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

// The connection is back — reconnect now, do not wait for the next tick.
const handleOnline = () => {
  if (!shouldReconnectRef.current) return;
  reconnectDelayMsRef.current = 1500;
  connect(true);
};
Component preview
online
Созвон в 18:00?
Да, буду✓✓