Censorship-resistant P2P video chat. Connect directly with anyone — no accounts, no servers required. All traffic is disguised as regular HTTPS to bypass Deep Packet Inspection.
See ARCHITECTURE.md for full technical design.
- Run
launch.sh(macOS/Linux) orlaunch.bat(Windows) from thestandalone/folder - A browser opens at
localhost:8043 - User A clicks Create Invite → gets a code block
- User A sends the code to User B via any messenger (Telegram, WhatsApp, Signal, email, etc.)
- User B clicks Join with Invite → pastes the code → gets a response code
- User B sends the response code back to User A
- User A pastes the response → peer-to-peer video call established!
No port forwarding, no domain names. The code exchange happens through whatever channel the users already have. Video, audio, and text are end-to-end encrypted via WebRTC.
Note: A local HTTP server is required because browsers block WebRTC (STUN/ICE) from
file://URLs. The launcher starts one automatically onlocalhost:8043.
For users who prefer a traditional server-based setup:
- Both users agree on a secret token and server IP (via phone, email, another messenger)
- User A opens the client, enters the token, clicks Create Room
- User B opens the client, enters the same token, clicks Join Room
- The signaling server matches them by token hash, then clients connect directly via WebRTC
vchat/
├── packages/
│ ├── shared/ # Shared TypeScript types and utilities
│ ├── server/ # Node.js signaling + relay server
│ ├── web/ # React + Vite web client
│ └── mobile/ # React Native client (iOS + Android)
└── package.json # npm workspaces root
- Node.js ≥ 22.11
- npm ≥ 10
- OpenSSL (for TLS cert generation, server mode only)
- For mobile:
- Xcode ≥ 15 (iOS)
- Android Studio + Android SDK (Android)
- CocoaPods (
gem install cocoapods) - JDK 17
Build the standalone Direct Connect package:
npm install
npm run build:standaloneOutput: standalone/ folder containing:
index.html— self-contained app (≈ 227 KB)launch.sh/launch.bat/launch.ps1— platform launchersserve.py— Python HTTP server
Run launch.sh (macOS/Linux) or launch.bat (Windows). Both users need this folder.
npm installnpm run build:shared# Start both server and web client
npm run dev
# Or separately:
npm run dev:server # Signaling server (https://0.0.0.0:8443)
npm run dev:web # Web client (https://localhost:5173)The dev server:
- Runs on port 8443 with a self-signed TLS cert (auto-generated in
packages/server/certs/) - Serves a fake "CloudSync Solutions" website on
GET /to mimic a real site - Accepts WSS connections for signaling
The web client:
- Runs on port 5173 with Vite's dev SSL
- Both Direct Connect and Server mode available
cd packages/mobile
# iOS
npx pod-install ios # Install CocoaPods deps
npm run ios # Build & run on iOS simulator
# Android
npm run android # Build & run on Android emulatorNote: Camera/mic require a physical device — simulators have limited WebRTC support.
npm run build:standaloneProduces a standalone/ folder with index.html and launcher scripts. Run launch.sh (macOS/Linux) or launch.bat (Windows) to serve the app on localhost:8043. Both users need this folder and any messenger to exchange codes.
npm run build:web
# Output: packages/web/dist/Includes both Direct Connect and Server mode. Deploy to any static host or serve from a VPS.
npm run build:server
node packages/server/dist/index.js --port 443The server auto-generates a self-signed cert on first run. To use your own:
node dist/index.js --port 443 --cert /path/to/server.crt --key /path/to/server.keyDirect Connect lets two users establish a peer-to-peer video call without any server. It works by exchanging WebRTC signaling data through any existing communication channel.
- Create Invite: The app creates a WebRTC offer, gathers all ICE candidates, and encodes everything into a single base64 blob (~4 KB of text).
- Exchange codes: Users exchange the invite code and response code through any messenger they already have access to.
- Connect: Once both sides have exchanged codes, a direct WebRTC connection is established.
- WebRTC SDP (Session Description Protocol) — codec info, media capabilities
- ICE candidates — network addresses for connectivity
- DTLS fingerprint — for end-to-end encryption verification
- Stealth level — traffic shaping settings
The codes contain no identifying information. They are one-time use and expire when the browser tab is closed.
Any VPS provider works (Hetzner, DigitalOcean, Vultr, etc.). Requirements:
- Static IPv4 address
- Port 443 open (inbound TCP)
- Node.js ≥ 22 installed
- 512 MB RAM is enough
git clone <your-repo-url> vchat
cd vchat
npm install
npm run build:shared
npm run build:server
sudo node packages/server/dist/index.js --port 443Create /etc/systemd/system/vchat.service:
[Unit]
Description=VChat Signaling Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/vchat
ExecStart=/usr/bin/node packages/server/dist/index.js --port 443
Restart=always
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/vchat/packages/server/certs
[Install]
WantedBy=multi-user.targetsudo systemctl enable vchat
sudo systemctl start vchatcurl -k https://<server-ip> # Should show fake website
openssl s_client -connect <server-ip>:443 # Should show valid TLSDeploy to several VPS providers in different countries for resilience. If one IP gets blocked, users switch to another. Both users must select the same server.
The creator selects the stealth level (the joiner inherits it automatically):
| Mode | What it does | Trade-off |
|---|---|---|
| Medium (default) | Packet padding, timing jitter | Good video quality, basic DPI evasion |
| High | Bandwidth cap (1 Mbps), chaff traffic, uniform packet sizes | Lower video quality (~480p), stronger DPI evasion |
- Video/audio/text are end-to-end encrypted via WebRTC DTLS
- Text messages are ephemeral — exist only during the active session
- Direct Connect invite codes contain no identifying information and are one-time use
- Direct Connect requires no server trust — DTLS fingerprints verify the peer
- No reliance on third-party tunnel domains that could be blocked or monitored
- Server stores nothing to disk — all state is in memory
- Server sees only SHA-256 hashes of tokens, never plaintext
- Server logs nothing — no IPs, timestamps, or connection records