{"id":48827905,"url":"https://github.com/rudra1806/relay","last_synced_at":"2026-04-14T19:00:27.556Z","repository":{"id":347123225,"uuid":"1187827805","full_name":"rudra1806/relay","owner":"rudra1806","description":"A full-stack real-time chat application built with Express.js backend and React frontend, featuring user authentication and messaging capabilities.","archived":false,"fork":false,"pushed_at":"2026-03-27T03:10:15.000Z","size":320,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T07:42:35.043Z","etag":null,"topics":["arcjet","bcrypt","chat-application","cloudinary","express","full-stack","jwt-authentication","mern-stack","mongodb","mongoose","nodejs","react","realtime-chat","socket-io","vite","websocket","zustand"],"latest_commit_sha":null,"homepage":"https://relay.iamrudra.tech/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rudra1806.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-21T08:15:06.000Z","updated_at":"2026-03-27T03:10:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rudra1806/relay","commit_stats":null,"previous_names":["rudra1806/relay"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rudra1806/relay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudra1806%2Frelay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudra1806%2Frelay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudra1806%2Frelay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudra1806%2Frelay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rudra1806","download_url":"https://codeload.github.com/rudra1806/relay/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rudra1806%2Frelay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31810741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T18:05:02.291Z","status":"ssl_error","status_checked_at":"2026-04-14T18:05:01.765Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arcjet","bcrypt","chat-application","cloudinary","express","full-stack","jwt-authentication","mern-stack","mongodb","mongoose","nodejs","react","realtime-chat","socket-io","vite","websocket","zustand"],"created_at":"2026-04-14T19:00:18.941Z","updated_at":"2026-04-14T19:00:27.530Z","avatar_url":"https://github.com/rudra1806.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Relay - Real-Time Chat Application\n\n\u003cdiv align=\"center\"\u003e\n\n![Relay Logo](frontend/public/relay-icon.svg)\n\n**A modern, zero-knowledge end-to-end encrypted, real-time messaging platform with voice \u0026 video calling, built with the MERN stack**\n\n[![Node.js](https://img.shields.io/badge/Node.js-≥20.0.0-339933?style=for-the-badge\u0026logo=node.js\u0026logoColor=white)](https://nodejs.org/)\n[![React](https://img.shields.io/badge/React-19.2.4-61DAFB?style=for-the-badge\u0026logo=react\u0026logoColor=black)](https://reactjs.org/)\n[![MongoDB](https://img.shields.io/badge/MongoDB-8.10.1-47A248?style=for-the-badge\u0026logo=mongodb\u0026logoColor=white)](https://www.mongodb.com/)\n[![Socket.IO](https://img.shields.io/badge/Socket.IO-4.8.1-010101?style=for-the-badge\u0026logo=socket.io\u0026logoColor=white)](https://socket.io/)\n[![Express](https://img.shields.io/badge/Express-4.21.2-000000?style=for-the-badge\u0026logo=express\u0026logoColor=white)](https://expressjs.com/)\n[![WebRTC](https://img.shields.io/badge/WebRTC-Calling-FF6F00?style=for-the-badge\u0026logo=webrtc\u0026logoColor=white)](https://webrtc.org/)\n[![E2EE](https://img.shields.io/badge/E2EE-NaCl%20Box-00D9FF?style=for-the-badge\u0026logo=lock\u0026logoColor=white)](https://nacl.cr.yp.to/)\n[![License](https://img.shields.io/badge/License-ISC-yellow?style=for-the-badge)](LICENSE)\n\n[Features](#-features) • [Calling](#-voice--video-calling) • [E2EE Security](#-end-to-end-encryption-e2ee) • [Installation](#-installation) • [API Docs](#-api-documentation) • [Deployment](#-deployment)\n\u003c/div\u003e\n\n---\n\n## 📋 Table of Contents\n\n- [Features](#-features)\n- [Architecture](#-architecture)\n- [Technology Stack](#-technology-stack)\n- [System Requirements](#-system-requirements)\n- [Installation](#-installation)\n- [Configuration](#-configuration)\n- [API Documentation](#-api-documentation)\n- [Database Schema](#-database-schema)\n- [Security Features](#-security-features)\n- [End-to-End Encryption](#-end-to-end-encryption-e2ee)\n- [Voice \u0026 Video Calling](#-voice--video-calling)\n- [Real-Time Communication](#-real-time-communication)\n- [Deployment](#-deployment)\n- [Project Structure](#-project-structure)\n- [Contributing](#-contributing)\n- [License](#-license)\n\n---\n\n## 🎯 Overview\n\n**Relay** is a production-ready, full-stack real-time chat application with **zero-knowledge end-to-end encryption** and **peer-to-peer voice \u0026 video calling** that enables users to communicate securely through text, image messages, and real-time calls. Built with modern web technologies, it features military-grade encryption, WebRTC calling, responsive design, robust security measures, and seamless real-time updates powered by WebSocket technology.\n\n### 🔐 Privacy \u0026 Security First\n\nRelay implements **true zero-knowledge architecture** — your messages are encrypted on your device before transmission, and the server never has access to your private keys or plaintext content. Even if the server is compromised, your conversations remain private.\n\n### Key Highlights\n\n- **🔐 Zero-Knowledge E2EE**: Client-side encryption with NaCl (X25519 + XSalsa20-Poly1305) — server never sees plaintext\n- **🔑 BIP39 Recovery Phrase**: 12-word mnemonic for deterministic key derivation and account recovery\n- **🛡️ Password-Protected Keys**: AES-256-GCM encryption with PBKDF2 (100,000 iterations) for private key storage\n- **🔄 Key Recovery**: Restore encrypted messages across devices using recovery phrase\n- **🤝 Contact Request System**: Privacy-first messaging with mutual consent required\n- **✉️ Email Verification**: Secure OTP-based email verification for new signups\n- **🔑 Password Reset**: Forgot password with OTP verification and encryption key recovery\n- **⚡ Real-Time Messaging**: Instant encrypted message delivery using Socket.IO\n- **📞 Voice \u0026 Video Calling**: Peer-to-peer WebRTC calls with camera flip and call controls\n- **📸 Rich Media Support**: Send text messages and images with Cloudinary integration\n- **🛡️ Advanced Security**: Multi-layered protection with Arcjet (bot detection, rate limiting, shield)\n- **🚦 Smart Rate Limiting**: Balanced protection (500 req/min) with automatic retry and graceful degradation\n- **👥 User Presence**: Real-time online/offline status and typing indicators\n- **✓ Read Receipts**: Track message delivery and read status\n- **📱 Discord-Style Sidebar**: Two-tab interface (Messages + All Contacts)\n- **🌓 Dark/Light Theme**: System-aware theme switching with smooth transitions\n- **📱 Responsive Design**: Mobile-first approach with smooth animations\n- **🚀 Production Ready**: Optimized for deployment with comprehensive error handling\n\n---\n\n## ✨ Features\n\n### Core Functionality\n\n#### 🔐 End-to-End Encryption\n- ✅ **Zero-knowledge architecture** — server never sees plaintext messages or private keys\n- ✅ **Client-side encryption** with NaCl box (X25519 key exchange + XSalsa20-Poly1305 AEAD)\n- ✅ **BIP39 recovery phrase** — 12-word mnemonic for deterministic key derivation\n- ✅ **Password-protected key storage** — AES-256-GCM with PBKDF2 (100,000 iterations)\n- ✅ **Session key caching** — IndexedDB for seamless experience without password re-entry\n- ✅ **Key recovery** — restore access to encrypted messages across devices\n- ✅ **Forward secrecy** — unique nonce per message prevents replay attacks\n\n#### 👤 Authentication \u0026 Security\n- ✅ User authentication (signup/login/logout) with JWT (7-day expiry)\n- ✅ Email verification with secure OTP (6-digit, 10-minute expiry)\n- ✅ Password reset with OTP verification and encryption key recovery\n- ✅ HTTP-only secure cookies with SameSite=Strict (CSRF protection)\n- ✅ bcrypt password hashing (10 rounds dev, 12 production)\n- ✅ Timing-safe OTP comparison (prevents timing attacks)\n- ✅ Rate limiting on OTP resend (60-second cooldown)\n- ✅ Automatic cleanup of unverified users (24-hour retention)\n\n#### 🤝 Contact Management\n- ✅ Contact request system (send/accept/decline/cancel)\n- ✅ Privacy enforcement (can only message contacts)\n- ✅ Real-time contact request notifications\n- ✅ Real-time contact acceptance updates\n- ✅ Contact search and user discovery\n- ✅ Remove contacts functionality\n- ✅ Duplicate request prevention\n- ✅ Connection status indicators (none/connected/pending/received)\n\n#### 💬 Messaging Features\n- ✅ Real-time one-on-one encrypted messaging\n- ✅ Image sharing with automatic optimization (Cloudinary)\n- ✅ Message history and persistence\n- ✅ Unread message counters with real-time updates\n- ✅ Read receipts and message status\n- ✅ Message encryption/decryption on client side\n- ✅ Automatic retry on rate limits\n\n#### 📞 Voice \u0026 Video Calling\n- ✅ One-on-one voice calls with full-duplex audio\n- ✅ One-on-one video calls with live video streaming\n- ✅ Incoming call modal with accept/decline actions\n- ✅ Call controls: mute, camera toggle, flip camera, end call\n- ✅ Camera flip (front ↔ back) for mobile devices\n- ✅ PiP (picture-in-picture) local video preview\n- ✅ Call duration timer with live indicator\n- ✅ Auto-timeout for unanswered calls (30 seconds)\n- ✅ Busy/unavailable/offline detection\n- ✅ WebRTC DTLS-SRTP transport encryption\n- ✅ Graceful disconnection recovery (5-second grace period)\n\n#### 🎨 User Interface\n- ✅ Discord-style sidebar with two tabs (Messages + All Contacts)\n- ✅ User profile management with avatar upload\n- ✅ Dark/Light theme switching with system preference detection\n- ✅ Responsive mobile-first design\n- ✅ Smooth animations with Framer Motion\n- ✅ Image lightbox for full-screen preview\n- ✅ Toast notifications for user feedback\n- ✅ Loading states and error handling\n\n### Real-Time Features\n- 🔴 Online/offline user status\n- ⌨️ Typing indicators\n- 📨 Instant message delivery\n- 📞 Real-time voice \u0026 video calls (WebRTC)\n- 🔔 Real-time contact request notifications\n- 🤝 Real-time contact acceptance updates\n- 🚫 Real-time contact removal notifications\n- 👥 Active users list\n\n### Security Features\n\n#### 🔐 Cryptographic Security\n- 🔐 **Zero-knowledge E2EE** — messages encrypted client-side, server never sees plaintext\n- 🔑 **NaCl box encryption** — X25519 (Curve25519 ECDH) + XSalsa20-Poly1305 (authenticated encryption)\n- 🔑 **BIP39 recovery phrase** — 12-word mnemonic for deterministic key derivation (512-bit seed)\n- 🛡️ **Password-protected keys** — AES-256-GCM wrapping with PBKDF2-SHA256 (100,000 iterations)\n- 🔄 **Unique nonces** — 24-byte random nonce per message (prevents replay attacks)\n- 🔐 **Session key caching** — IndexedDB with session-encrypted storage\n- 🔑 **Key recovery** — restore encrypted messages using recovery phrase\n\n#### 🛡️ Application Security\n- 🔒 **JWT authentication** — 7-day expiry with HTTP-only cookies\n- ✉️ **Email verification** — secure 6-digit OTP with 10-minute expiry\n- 🔑 **Password reset** — OTP verification + encryption key recovery\n- 🛡️ **Arcjet security suite** — bot detection, rate limiting, SQL injection \u0026 XSS prevention\n- 🍪 **Secure cookies** — HTTP-only, Secure flag (production), SameSite=Strict\n- 🔐 **bcrypt hashing** — 10 rounds (dev), 12 rounds (production)\n- 🚫 **CSRF protection** — SameSite cookie policy\n- ⚡ **Input validation** — comprehensive request validation and sanitization\n- ⏱️ **Timing attack prevention** — constant-time OTP comparison with crypto.timingSafeEqual()\n- 🚦 **Smart rate limiting** — 500 requests/minute with automatic retry and exponential backoff\n- 🔄 **Fail-open strategy** — graceful degradation on service failures\n- 🧹 **Automatic cleanup** — unverified users deleted after 24 hours (production only)\n\n\n---\n\n## 🏗️ Architecture\n\n### High-Level System Architecture\n\n```\n┌────────────────────────────────────────────────────────────────┐\n│                         CLIENT LAYER                           │\n│  ┌──────────────────────────────────────────────────────────┐  │\n│  │  React Frontend (Vite)                                   │  │\n│  │  - Zustand State Management                              │  │\n│  │  - React Router (SPA)                                    │  │\n│  │  - Socket.IO Client                                      │  │\n│  │  - Axios HTTP Client                                     │  │\n│  │  - Verification Modal (OTP Input)                        │  │\n│  │  ┌────────────────────────────────────────────────────┐  │  │\n│  │  │  E2EE Layer (Client-Side Only)                     │  │  │\n│  │  │  - NaCl Box (X25519 + XSalsa20-Poly1305)           │  │  │\n│  │  │  - BIP39 Recovery Phrase Generation                │  │  │\n│  │  │  - AES-256-GCM Key Wrapping (Web Crypto API)       │  │  │\n│  │  │  - PBKDF2 Password-Based Key Derivation            │  │  │\n│  │  │  - IndexedDB Session Cache                         │  │  │\n│  │  └────────────────────────────────────────────────────┘  │  │\n│  └──────────────────────────────────────────────────────────┘  │\n└────────────────────────────────────────────────────────────────┘\n                              ↕ HTTP/WebSocket (encrypted payloads)\n┌────────────────────────────────────────────────────────────────┐\n│                      MIDDLEWARE LAYER                          │\n│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │\n│  │   Arcjet     │  │     CORS     │  │   Cookie Parser      │  │\n│  │  Protection  │  │              │  │                      │  │\n│  └──────────────┘  └──────────────┘  └──────────────────────┘  │\n│  ┌──────────────────────────────────────────────────────────┐  │\n│  │            JWT Authentication Middleware                 │  │\n│  └──────────────────────────────────────────────────────────┘  │\n└────────────────────────────────────────────────────────────────┘\n                              ↕\n┌────────────────────────────────────────────────────────────────┐\n│                      APPLICATION LAYER                         │\n│  ┌──────────────────────────────────────────────────────────┐  │\n│  │  Express.js Server (Zero-Knowledge — never sees          │  │\n│  │  plaintext messages or private keys)                     │  │\n│  │  ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │  │\n│  │  │ Auth Routes  │ │ Message      │ │ Contact Routes    │ │  │\n│  │  │ - /signup    │ │ Routes       │ │ - /search         │ │  │\n│  │  │ - /verify    │ │ - /contacts  │ │ - /request        │ │  │\n│  │  │ - /login     │ │ - /chats     │ │ - /accept         │ │  │\n│  │  │ - /logout    │ │ - /:id (get) │ │ - /decline        │ │  │\n│  │  │ - /check     │ │ - /send/:id  │ │ - /cancel         │ │  │\n│  │  │ - /update    │ │ - /read/:id  │ │ - /remove         │ │  │\n│  │  │ - /forgot    │ │              │ │                   │ │  │\n│  │  │ - /reset     │ │              │ │                   │ │  │\n│  │  └──────────────┘ └──────────────┘ └───────────────────┘ │  │\n│  │  ┌─────────────────────────────────────────────────────┐ │  │\n│  │  │ Encryption Routes (opaque blob storage only)        │ │  │\n│  │  │ - GET  /public-key/:userId                          │ │  │\n│  │  │ - PUT  /keys                                        │ │  │\n│  │  └─────────────────────────────────────────────────────┘ │  │\n│  └──────────────────────────────────────────────────────────┘  │\n│  ┌──────────────────────────────────────────────────────────┐  │\n│  │  Socket.IO Server (WebSocket)                            │  │\n│  │  - Connection management                                 │  │\n│  │  - Real-time event handling                              │  │\n│  │  - Online users tracking                                 │  │\n│  │  - WebRTC call signaling (SDP/ICE relay)                 │  │\n│  └──────────────────────────────────────────────────────────┘  │\n│  ┌──────────────────────────────────────────────────────────┐  │\n│  │  Cleanup Service (Background)                            │  │\n│  │  - Runs every 6 hours (production only)                  │  │\n│  │  - Deletes unverified users \u003e 24 hours old               │  │\n│  └──────────────────────────────────────────────────────────┘  │\n└────────────────────────────────────────────────────────────────┘\n                              ↕\n┌────────────────────────────────────────────────────────────────┐\n│                       DATA LAYER                               │\n│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │\n│  │   MongoDB    │  │  Cloudinary  │  │      Resend          │  │\n│  │   Database   │  │  (Images)    │  │  (Email/OTP)         │  │\n│  │  (encrypted  │  │              │  │                      │  │\n│  │   blobs only)│  │              │  │                      │  │\n│  └──────────────┘  └──────────────┘  └──────────────────────┘  │\n└────────────────────────────────────────────────────────────────┘\n```\n\n\n### Authentication Flow Sequence Diagram\n\n#### Signup with Email Verification\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Arcjet\n    participant Server\n    participant MongoDB\n    participant Resend\n\n    Client-\u003e\u003eServer: POST /api/auth/signup\n    Server-\u003e\u003eArcjet: Validate request (bot detection, rate limit)\n    Arcjet--\u003e\u003eServer: Request allowed\n    Server-\u003e\u003eServer: Validate input data\n    Server-\u003e\u003eMongoDB: Check if user exists\n    MongoDB--\u003e\u003eServer: User not found\n    Server-\u003e\u003eServer: Hash password (bcrypt)\n    Server-\u003e\u003eServer: Generate 6-digit OTP\n    Server-\u003e\u003eMongoDB: Create user (isVerified: false)\n    MongoDB--\u003e\u003eServer: User created\n    Server-\u003e\u003eResend: Send OTP email\n    Resend--\u003e\u003eServer: Email sent\n    Server--\u003e\u003eClient: 201 Created (user data, no token)\n    Client-\u003e\u003eClient: Show verification modal\n    \n    Note over Client,Resend: User receives OTP email and enters code\n    \n    Client-\u003e\u003eServer: POST /api/auth/verify-email (+ publicKey, encryptedPrivateKey, keyIv, keySalt)\n    Server-\u003e\u003eServer: Validate OTP format\n    Server-\u003e\u003eMongoDB: Find user with OTP\n    MongoDB--\u003e\u003eServer: User found\n    Server-\u003e\u003eServer: Check OTP expiry (10 min)\n    Server-\u003e\u003eServer: Compare OTP (constant-time)\n    Server-\u003e\u003eMongoDB: Mark verified + store E2EE key material\n    MongoDB--\u003e\u003eServer: User updated\n    Server-\u003e\u003eServer: Generate JWT token\n    Server-\u003e\u003eServer: Set HTTP-only cookie\n    Server-\u003e\u003eResend: Send welcome email (async)\n    Server--\u003e\u003eClient: 200 OK (user data + key material + token)\n    Client-\u003e\u003eClient: Cache keys in IndexedDB (session-encrypted)\n    Client-\u003e\u003eClient: Show 12-word recovery phrase modal\n    Client-\u003e\u003eClient: User confirms phrase saved\n    Client-\u003e\u003eClient: Navigate to chat\n    Client-\u003e\u003eServer: Connect WebSocket\n```\n\n#### Login Flow\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Arcjet\n    participant Server\n    participant JWT\n    participant MongoDB\n\n    Client-\u003e\u003eServer: POST /api/auth/login\n    Server-\u003e\u003eArcjet: Validate request (bot detection, rate limit)\n    Arcjet--\u003e\u003eServer: Request allowed\n    Server-\u003e\u003eServer: Validate input data\n    Server-\u003e\u003eMongoDB: Find user by email\n    MongoDB--\u003e\u003eServer: User found\n    Server-\u003e\u003eServer: Compare password (bcrypt)\n    \n    alt User not verified\n        Server--\u003e\u003eClient: 403 Forbidden (requiresVerification: true)\n        Client-\u003e\u003eClient: Show verification modal\n        Note over Client,Server: User can resend OTP or verify\n    else User verified\n        Server-\u003e\u003eJWT: Generate token\n        JWT--\u003e\u003eServer: JWT token\n        Server-\u003e\u003eServer: Set HTTP-only cookie\n        Server--\u003e\u003eClient: 200 OK (user data + encrypted key bundle)\n        Client-\u003e\u003eClient: Decrypt private key with password (AES-GCM + PBKDF2)\n        Client-\u003e\u003eClient: Cache keys in IndexedDB (session-encrypted)\n        Client-\u003e\u003eServer: Connect WebSocket\n    end\n```\n\n#### OTP Resend Flow\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n    participant MongoDB\n    participant Resend\n\n    Client-\u003e\u003eServer: POST /api/auth/resend-otp\n    Server-\u003e\u003eServer: Validate email format\n    Server-\u003e\u003eMongoDB: Find user\n    MongoDB--\u003e\u003eServer: User found\n    \n    alt Already verified\n        Server--\u003e\u003eClient: 400 Bad Request\n    else Rate limited (\u003c 60s since last OTP)\n        Server--\u003e\u003eClient: 429 Too Many Requests (retryAfter: X)\n    else Allowed\n        Server-\u003e\u003eServer: Generate new 6-digit OTP\n        Server-\u003e\u003eMongoDB: Update user with new OTP\n        MongoDB--\u003e\u003eServer: User updated\n        Server-\u003e\u003eResend: Send OTP email\n        Resend--\u003e\u003eServer: Email sent\n        Server--\u003e\u003eClient: 200 OK (success message)\n        Client-\u003e\u003eClient: Show countdown timer (60s)\n    end\n```\n\n### Message Flow Sequence Diagram (with E2EE)\n\n```mermaid\nsequenceDiagram\n    participant Sender\n    participant SenderSocket\n    participant Server\n    participant MongoDB\n    participant Cloudinary\n    participant ReceiverSocket\n    participant Receiver\n\n    Sender-\u003e\u003eSender: Encrypt text with nacl.box(text, nonce, receiverPK, senderSK)\n    Sender-\u003e\u003eServer: POST /api/message/send/:id (encrypted text + nonce + raw image)\n    Server-\u003e\u003eServer: Validate JWT \u0026 user\n    alt Image included\n        Server-\u003e\u003eCloudinary: Upload image (raw base64)\n        Cloudinary--\u003e\u003eServer: Image URL\n    end\n    Server-\u003e\u003eMongoDB: Save message (ciphertext + nonce + image URL)\n    Note over Server: Server NEVER sees plaintext message content\n    MongoDB--\u003e\u003eServer: Message saved\n    Server--\u003e\u003eSender: 201 Created (message with ciphertext)\n    Sender-\u003e\u003eSender: Decrypt own message for local display\n    Server-\u003e\u003eReceiverSocket: Emit 'newMessage' event (ciphertext)\n    ReceiverSocket-\u003e\u003eReceiver: Decrypt with nacl.box.open(ciphertext, nonce, senderPK, receiverSK)\n    Receiver-\u003e\u003eReceiver: Display decrypted message\n    Receiver-\u003e\u003eServer: PATCH /api/message/read/:id\n    Server-\u003e\u003eMongoDB: Mark messages as read\n    Server-\u003e\u003eSenderSocket: Emit 'messagesRead' event\n    SenderSocket-\u003e\u003eSender: Update read status\n```\n\n\n### Real-Time Communication Flow\n\n```mermaid\nsequenceDiagram\n    participant User1\n    participant Socket1\n    participant Server\n    participant Socket2\n    participant User2\n\n    User1-\u003e\u003eServer: Connect (with JWT cookie)\n    Server-\u003e\u003eServer: Authenticate socket\n    Server-\u003e\u003eSocket1: Connection established\n    Server-\u003e\u003eSocket1: Emit 'getOnlineUsers'\n    \n    User2-\u003e\u003eServer: Connect (with JWT cookie)\n    Server-\u003e\u003eServer: Authenticate socket\n    Server-\u003e\u003eSocket2: Connection established\n    Server-\u003e\u003eSocket1: Emit 'getOnlineUsers' (updated)\n    Server-\u003e\u003eSocket2: Emit 'getOnlineUsers'\n    \n    User1-\u003e\u003eSocket1: Emit 'typing' {receiverId: User2}\n    Socket1-\u003e\u003eServer: Forward typing event\n    Server-\u003e\u003eSocket2: Emit 'userTyping' {senderId: User1}\n    Socket2-\u003e\u003eUser2: Show typing indicator\n    \n    User1-\u003e\u003eSocket1: Emit 'stopTyping' {receiverId: User2}\n    Socket1-\u003e\u003eServer: Forward stopTyping event\n    Server-\u003e\u003eSocket2: Emit 'userStopTyping' {senderId: User1}\n    Socket2-\u003e\u003eUser2: Hide typing indicator\n    \n    User1-\u003e\u003eServer: Send message via HTTP\n    Server-\u003e\u003eSocket2: Emit 'newMessage'\n    Socket2-\u003e\u003eUser2: Display new message\n    \n    User1-\u003e\u003eServer: Disconnect\n    Server-\u003e\u003eSocket2: Emit 'getOnlineUsers' (updated)\n```\n\n---\n\n## 🛠️ Technology Stack\n\n### Frontend\n| Technology | Version | Purpose |\n|-----------|---------|---------|\n| **React** | 19.2.4 | UI library for building component-based interfaces |\n| **Vite** | 8.0.1 | Fast build tool and development server |\n| **Zustand** | 5.0.12 | Lightweight state management |\n| **React Router** | 7.13.2 | Client-side routing |\n| **Socket.IO Client** | 4.8.3 | Real-time WebSocket communication |\n| **Axios** | 1.13.6 | HTTP client for API requests |\n| **Framer Motion** | 12.38.0 | Animation library |\n| **Lucide React** | 1.0.1 | Icon library |\n| **date-fns** | 4.1.0 | Date formatting utilities |\n| **React Hot Toast** | 2.6.0 | Toast notifications |\n| **TweetNaCl** | 1.0.3 | E2EE cryptographic operations (X25519 ECDH, XSalsa20-Poly1305 AEAD) |\n| **TweetNaCl-util** | 0.15.1 | Encoding utilities for TweetNaCl (base64, UTF-8) |\n| **bip39** | 3.1.0 | BIP39 12-word recovery phrase generation \u0026 deterministic seed derivation |\n| **buffer** | 6.0.3 | Buffer polyfill for bip39 in browser environments |\n| **Web Crypto API** | Native | PBKDF2-SHA256 key derivation \u0026 AES-256-GCM key wrapping |\n\n### Backend\n| Technology | Version | Purpose |\n|-----------|---------|---------|\n| **Node.js** | ≥20.0.0 | JavaScript runtime |\n| **Express.js** | 4.21.2 | Web application framework |\n| **MongoDB** | 8.10.1 | NoSQL database |\n| **Mongoose** | 8.10.1 | MongoDB ODM |\n| **Socket.IO** | 4.8.1 | Real-time bidirectional communication |\n| **JWT** | 9.0.2 | Authentication tokens |\n| **bcryptjs** | 2.4.3 | Password hashing |\n| **Cloudinary** | 2.5.1 | Image storage and optimization |\n| **Resend** | 6.0.2 | Transactional email service |\n| **Arcjet** | 1.0.0-beta.10 | Security suite (bot detection, rate limiting) |\n| **CORS** | 2.8.6 | Cross-origin resource sharing |\n| **Cookie Parser** | 1.4.7 | Cookie parsing middleware |\n\n\n---\n\n## 💻 System Requirements\n\n- **Node.js**: Version 20.0.0 or higher\n- **npm**: Version 9.0.0 or higher\n- **MongoDB**: Version 5.0 or higher (or MongoDB Atlas account)\n- **Modern Browser**: Chrome, Firefox, Safari, or Edge (latest versions)\n\n---\n\n## 📦 Installation\n\n### 1. Clone the Repository\n\n```bash\ngit clone https://github.com/yourusername/relay.git\ncd relay\n```\n\n### 2. Install Dependencies\n\nInstall dependencies for both backend and frontend:\n\n```bash\n# Install root dependencies\nnpm install\n\n# Install backend dependencies\ncd backend\nnpm install\n\n# Install frontend dependencies\ncd ../frontend\nnpm install\n```\n\n### 3. Environment Configuration\n\nCreate a `.env` file in the `backend` directory:\n\n```bash\ncd backend\ncp .env.example .env\n```\n\nEdit the `.env` file with your configuration (see [Configuration](#-configuration) section).\n\n### 4. Start Development Servers\n\n#### Option 1: Start Both Servers Separately\n\n```bash\n# Terminal 1 - Start backend server\ncd backend\nnpm run dev\n\n# Terminal 2 - Start frontend server\ncd frontend\nnpm run dev\n```\n\n#### Option 2: Use Concurrently (if configured)\n\n```bash\nnpm run dev\n```\n\nThe application will be available at:\n- **Frontend**: http://localhost:5173\n- **Backend**: http://localhost:3000\n\n---\n\n## ⚙️ Configuration\n\n### Backend Environment Variables\n\nCreate a `.env` file in the `backend` directory with the following variables:\n\n```env\n# Server Configuration\nPORT=3000\nNODE_ENV=development\n\n# Database Configuration\nMONGO_URI=mongodb://localhost:27017/relay\n# Or use MongoDB Atlas:\n# MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/relay?retryWrites=true\u0026w=majority\n\n# JWT Configuration\nJWT_SECRET=your_super_secret_jwt_key_change_this_in_production\n\n# Email Configuration (Resend)\nRESEND_API_KEY=your_resend_api_key\nSENDER_EMAIL=noreply@yourdomain.com\nSENDER_NAME=Relay Team\n\n# Client Configuration\nCLIENT_URL=http://localhost:5173\n\n# Cloudinary Configuration (for image uploads)\nCLOUDINARY_CLOUD_NAME=your_cloud_name\nCLOUDINARY_API_KEY=your_api_key\nCLOUDINARY_API_SECRET=your_api_secret\n\n# Arcjet Security Configuration\nARCJET_KEY=your_arcjet_key\nARCJET_ENV=development\n```\n\n### Service Setup Instructions\n\n#### MongoDB Setup\n1. **Local MongoDB**: Install MongoDB locally or use Docker\n2. **MongoDB Atlas** (Recommended):\n   - Create account at [mongodb.com/cloud/atlas](https://www.mongodb.com/cloud/atlas)\n   - Create a new cluster\n   - Get connection string and add to `MONGO_URI`\n\n#### Cloudinary Setup\n1. Create account at [cloudinary.com](https://cloudinary.com)\n2. Get credentials from dashboard\n3. Add to environment variables\n\n#### Resend Setup\n1. Create account at [resend.com](https://resend.com)\n2. Verify your domain\n3. Generate API key\n4. Add to environment variables\n\n#### Arcjet Setup\n1. Create account at [arcjet.com](https://arcjet.com)\n2. Create new site\n3. Get site key\n4. Add to environment variables\n\n\n---\n\n## 📡 API Documentation\n\n### Base URL\n```\nDevelopment: http://localhost:3000/api\nProduction: https://your-domain.com/api\n```\n\n### Authentication Endpoints\n\n#### 1. Sign Up\n```http\nPOST /api/auth/signup\nContent-Type: application/json\n\n{\n  \"name\": \"John Doe\",\n  \"email\": \"john@example.com\",\n  \"password\": \"securePassword123\"\n}\n```\n\n**Response (201 Created):**\n```json\n{\n  \"_id\": \"user_id\",\n  \"name\": \"John Doe\",\n  \"email\": \"john@example.com\",\n  \"profilePic\": \"\",\n  \"isVerified\": false,\n  \"createdAt\": \"2026-01-01T00:00:00.000Z\",\n  \"message\": \"Signup successful. Please check your email for verification code.\"\n}\n```\n\n**Note:** After signup, a 6-digit OTP is sent to the user's email. The user must verify their email before logging in.\n\n#### 2. Verify Email\n```http\nPOST /api/auth/verify-email\nContent-Type: application/json\n\n{\n  \"email\": \"john@example.com\",\n  \"otp\": \"123456\"\n}\n```\n\n**Response (200 OK):**\n```json\n{\n  \"_id\": \"user_id\",\n  \"name\": \"John Doe\",\n  \"email\": \"john@example.com\",\n  \"profilePic\": \"\",\n  \"isVerified\": true,\n  \"createdAt\": \"2026-01-01T00:00:00.000Z\",\n  \"message\": \"Email verified successfully\"\n}\n```\n\n**Error Responses:**\n- `400 Bad Request`: Invalid OTP format or already verified\n- `401 Unauthorized`: Invalid OTP\n- `404 Not Found`: User not found\n\n#### 3. Resend OTP\n```http\nPOST /api/auth/resend-otp\nContent-Type: application/json\n\n{\n  \"email\": \"john@example.com\"\n}\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"A new verification code has been sent to your email\",\n  \"email\": \"john@example.com\"\n}\n```\n\n**Error Responses:**\n- `400 Bad Request`: Email already verified\n- `429 Too Many Requests`: Rate limit exceeded (60-second cooldown)\n\n**Note:** Rate limited to one request per 60 seconds per email address.\n\n#### 4. Login\n```http\nPOST /api/auth/login\nContent-Type: application/json\n\n{\n  \"email\": \"john@example.com\",\n  \"password\": \"securePassword123\"\n}\n```\n\n**Response (200 OK):**\n```json\n{\n  \"_id\": \"user_id\",\n  \"name\": \"John Doe\",\n  \"email\": \"john@example.com\",\n  \"profilePic\": \"https://cloudinary.com/...\",\n  \"createdAt\": \"2026-01-01T00:00:00.000Z\",\n  \"updatedAt\": \"2026-01-01T00:00:00.000Z\"\n}\n```\n\n#### 5. Logout\n```http\nPOST /api/auth/logout\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Logged out successfully\"\n}\n```\n\n#### 6. Check Authentication\n```http\nGET /api/auth/check\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n{\n  \"user\": {\n    \"_id\": \"user_id\",\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"profilePic\": \"https://cloudinary.com/...\"\n  }\n}\n```\n\n#### 7. Update Profile\n```http\nPUT /api/auth/update-profile\nAuthorization: Required (JWT Cookie)\nContent-Type: application/json\n\n{\n  \"name\": \"John Updated\",\n  \"profilePic\": \"data:image/jpeg;base64,...\"\n}\n```\n\n#### 8. Forgot Password\n```http\nPOST /api/auth/forgot-password\nContent-Type: application/json\n\n{\n  \"email\": \"john@example.com\"\n}\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Password reset code has been sent to your email\",\n  \"email\": \"john@example.com\"\n}\n```\n\n**Error Responses:**\n- `403 Forbidden`: Email not verified\n- `429 Too Many Requests`: Rate limit exceeded (60-second cooldown)\n\n**Note:** A 6-digit OTP is sent to the user's email. Rate limited to one request per 60 seconds per email address.\n\n#### 9. Reset Password\n```http\nPOST /api/auth/reset-password\nContent-Type: application/json\n\n{\n  \"email\": \"john@example.com\",\n  \"otp\": \"123456\",\n  \"newPassword\": \"newSecurePassword123\"\n}\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Password reset successfully. You can now login with your new password.\"\n}\n```\n\n**Error Responses:**\n- `400 Bad Request`: Invalid OTP format or no reset code found\n- `401 Unauthorized`: Invalid OTP\n- `403 Forbidden`: Email not verified\n\n**Note:** OTP expires after 10 minutes. After successful reset, user can login with the new password.\n\n\n### 4. Contact Request System\n\n**Overview:**\nThe contact request system implements privacy-first messaging where users must be contacts before they can message each other. This prevents unsolicited messages and gives users control over who can reach them.\n\n**Add Contact Flow:**\n```mermaid\nsequenceDiagram\n    participant UserA\n    participant Server\n    participant MongoDB\n    participant Socket\n    participant UserB\n\n    UserA-\u003e\u003eServer: POST /api/contacts/request/:userId\n    Server-\u003e\u003eMongoDB: Check if already contacts\n    MongoDB--\u003e\u003eServer: Not contacts\n    Server-\u003e\u003eMongoDB: Check for existing request\n    MongoDB--\u003e\u003eServer: No existing request\n    Server-\u003e\u003eMongoDB: Create contact request\n    MongoDB--\u003e\u003eServer: Request created\n    Server--\u003e\u003eUserA: 201 Created\n    Server-\u003e\u003eSocket: Emit 'contactRequest' to UserB\n    Socket-\u003e\u003eUserB: Show notification + badge\n    \n    UserB-\u003e\u003eServer: GET /api/contacts/requests/pending\n    Server-\u003e\u003eMongoDB: Find pending requests\n    MongoDB--\u003e\u003eServer: Requests list\n    Server--\u003e\u003eUserB: 200 OK (requests)\n    \n    UserB-\u003e\u003eServer: PATCH /api/contacts/accept/:requestId\n    Server-\u003e\u003eMongoDB: Update request status\n    Server-\u003e\u003eMongoDB: Add to both users' contacts\n    MongoDB--\u003e\u003eServer: Updated\n    Server--\u003e\u003eUserB: 200 OK\n    Server-\u003e\u003eSocket: Emit 'contactAccepted' to UserA\n    Socket-\u003e\u003eUserA: Update UI + refresh contacts\n    UserB-\u003e\u003eUserB: Refresh contacts list\n    \n    Note over UserA,UserB: Both users can now message each other\n```\n\n**Key Features:**\n- ✅ Mutual consent required before messaging\n- ✅ Search users by name or email\n- ✅ Send/accept/decline/cancel requests\n- ✅ Real-time notifications for new requests\n- ✅ Real-time updates when requests are accepted\n- ✅ Remove contacts anytime\n- ✅ Backend enforces privacy (can't message non-contacts)\n- ✅ Duplicate request prevention\n- ✅ Can resend after decline\n\n**Privacy Enforcement:**\n```javascript\n// Backend checks before allowing messages\nconst isContact = sender.contacts.some(\n  (contactId) =\u003e contactId.toString() === receiverId\n);\n\nif (!isContact) {\n  return res.status(403).json({ \n    message: \"You can only message your contacts\" \n  });\n}\n```\n\n**UI Components:**\n- **Add Contact Modal**: Search users and send requests\n- **Contact Requests Modal**: View received and sent requests\n- **Sidebar**: Two-tab interface (Messages + All Contacts)\n- **Chat Header**: Remove contact option in menu\n\n**Real-Time Updates:**\n- New request → Receiver gets notification badge\n- Request accepted → Sender's contacts list refreshes\n- Contact removed → Both users' chats clear and contacts refresh\n\n\n### Message Endpoints\n\n#### 1. Get All Contacts\n```http\nGET /api/message/contacts\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n[\n  {\n    \"_id\": \"user_id\",\n    \"name\": \"Jane Doe\",\n    \"email\": \"jane@example.com\",\n    \"profilePic\": \"https://cloudinary.com/...\"\n  }\n]\n```\n\n#### 2. Get Chat Partners\n```http\nGET /api/message/chats\nAuthorization: Required (JWT Cookie)\n```\n\nReturns only users you've had conversations with.\n\n#### 3. Get Messages with User\n```http\nGET /api/message/:userId\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n[\n  {\n    \"_id\": \"message_id\",\n    \"senderId\": \"sender_user_id\",\n    \"receiverId\": \"receiver_user_id\",\n    \"text\": \"Hello!\",\n    \"image\": null,\n    \"isRead\": true,\n    \"createdAt\": \"2026-01-01T00:00:00.000Z\",\n    \"updatedAt\": \"2026-01-01T00:00:00.000Z\"\n  }\n]\n```\n\n#### 4. Send Message\n```http\nPOST /api/message/send/:userId\nAuthorization: Required (JWT Cookie)\nContent-Type: application/json\n\n{\n  \"text\": \"Hello, how are you?\",\n  \"image\": \"data:image/jpeg;base64,...\" // Optional\n}\n```\n\n**Response (201 Created):**\n```json\n{\n  \"_id\": \"message_id\",\n  \"senderId\": \"sender_user_id\",\n  \"receiverId\": \"receiver_user_id\",\n  \"text\": \"Hello, how are you?\",\n  \"image\": \"https://cloudinary.com/...\",\n  \"isRead\": false,\n  \"createdAt\": \"2026-01-01T00:00:00.000Z\",\n  \"updatedAt\": \"2026-01-01T00:00:00.000Z\"\n}\n```\n\n#### 5. Mark Messages as Read\n```http\nPATCH /api/message/read/:userId\nAuthorization: Required (JWT Cookie)\n```\n\nMarks all messages from the specified user as read.\n\n**Response (200 OK):**\n```json\n{\n  \"modifiedCount\": 5\n}\n```\n\n---\n\n### Contact Endpoints\n\n#### 1. Get All Contacts\n```http\nGET /api/contacts\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n[\n  {\n    \"_id\": \"user_id\",\n    \"name\": \"Jane Doe\",\n    \"email\": \"jane@example.com\",\n    \"profilePic\": \"https://cloudinary.com/...\"\n  }\n]\n```\n\n#### 2. Search Users\n```http\nGET /api/contacts/search?query=john\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n[\n  {\n    \"_id\": \"user_id\",\n    \"name\": \"John Smith\",\n    \"email\": \"john@example.com\",\n    \"profilePic\": \"https://cloudinary.com/...\",\n    \"connectionStatus\": \"none\" // \"none\" | \"connected\" | \"pending\" | \"received\"\n  }\n]\n```\n\n**Connection Status:**\n- `none`: No connection\n- `connected`: Already in contacts\n- `pending`: You sent them a request\n- `received`: They sent you a request\n\n#### 3. Send Contact Request\n```http\nPOST /api/contacts/request/:userId\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (201 Created):**\n```json\n{\n  \"_id\": \"request_id\",\n  \"senderId\": \"your_user_id\",\n  \"receiverId\": \"other_user_id\",\n  \"status\": \"pending\",\n  \"createdAt\": \"2026-01-01T00:00:00.000Z\"\n}\n```\n\n**Error Responses:**\n- `400 Bad Request`: Cannot send to yourself, already contacts, or request already exists\n- `404 Not Found`: User not found\n\n#### 4. Get Pending Requests (Received)\n```http\nGET /api/contacts/requests/pending\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n[\n  {\n    \"_id\": \"request_id\",\n    \"senderId\": {\n      \"_id\": \"user_id\",\n      \"name\": \"John Doe\",\n      \"email\": \"john@example.com\",\n      \"profilePic\": \"https://cloudinary.com/...\"\n    },\n    \"status\": \"pending\",\n    \"createdAt\": \"2026-01-01T00:00:00.000Z\"\n  }\n]\n```\n\n#### 5. Get Sent Requests\n```http\nGET /api/contacts/requests/sent\nAuthorization: Required (JWT Cookie)\n```\n\nReturns requests you've sent that are still pending.\n\n#### 6. Accept Contact Request\n```http\nPATCH /api/contacts/accept/:requestId\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Contact request accepted\",\n  \"request\": { ... }\n}\n```\n\n**Error Responses:**\n- `403 Forbidden`: Not authorized to accept this request\n- `404 Not Found`: Request not found\n\n#### 7. Decline Contact Request\n```http\nPATCH /api/contacts/decline/:requestId\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Contact request declined\"\n}\n```\n\n#### 8. Cancel Sent Request\n```http\nDELETE /api/contacts/cancel/:requestId\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Contact request cancelled\"\n}\n```\n\n#### 9. Remove Contact\n```http\nDELETE /api/contacts/:userId\nAuthorization: Required (JWT Cookie)\n```\n\n**Response (200 OK):**\n```json\n{\n  \"message\": \"Contact removed\"\n}\n```\n\n**Note:** This removes the contact from both users' contact lists and deletes any pending requests between them.\n\n\n### WebSocket Events\n\n#### Client → Server Events\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `typing` | `{ receiverId: string }` | Notify that user is typing |\n| `stopTyping` | `{ receiverId: string }` | Notify that user stopped typing |\n| `call:initiate` | `{ receiverId, callType }` | Start a voice or video call |\n| `call:accepted` | `{ callerId, callType }` | Accept an incoming call |\n| `call:rejected` | `{ callerId }` | Decline an incoming call |\n| `call:offer` | `{ offer, receiverId }` | Send WebRTC SDP offer |\n| `call:answer` | `{ answer, callerId }` | Send WebRTC SDP answer |\n| `call:ice-candidate` | `{ candidate, peerId }` | Relay ICE candidate |\n| `call:toggle-media` | `{ peerId, mediaType, enabled }` | Notify mute/camera toggle |\n| `call:ended` | `{ peerId }` | End the call |\n\n#### Server → Client Events\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `getOnlineUsers` | `string[]` | Array of online user IDs |\n| `newMessage` | `Message` | New message received |\n| `userTyping` | `{ senderId: string }` | User started typing |\n| `userStopTyping` | `{ senderId: string }` | User stopped typing |\n| `messagesRead` | `{ readBy: string }` | Messages marked as read |\n| `contactRequest` | `{ request: ContactRequest }` | New contact request received |\n| `contactAccepted` | `{ userId: string, requestId: string }` | Your contact request was accepted |\n| `contactRemoved` | `{ removedBy: string }` | Someone removed you as a contact |\n| `call:incoming` | `{ callerId, callerName, callerPic, callType }` | Incoming call notification |\n| `call:accepted` | `{ callerId }` | Call was accepted by receiver |\n| `call:rejected` | `{ callerId }` | Call was rejected |\n| `call:offer` | `{ offer, callerId }` | WebRTC SDP offer from caller |\n| `call:answer` | `{ answer }` | WebRTC SDP answer from receiver |\n| `call:ice-candidate` | `{ candidate }` | ICE candidate from peer |\n| `call:toggle-media` | `{ mediaType, enabled }` | Remote peer toggled media |\n| `call:ended` | `{ reason }` | Call was ended |\n| `call:busy` | `{ message }` | Receiver is busy on another call |\n| `call:unavailable` | `{}` | Receiver is offline |\n\n---\n\n## 🗄️ Database Schema\n\n### User Model\n\n```javascript\n{\n  _id: ObjectId,\n  name: String (required, trimmed),\n  email: String (required, unique, lowercase, trimmed),\n  password: String (required, hashed),\n  profilePic: String (default: ''),\n  isVerified: Boolean (default: false, indexed),\n  otp: String (select: false, for security),\n  otpExpiry: Date (select: false, indexed for cleanup),\n  lastOTPSentAt: Date (select: false, for rate limiting),\n  resetPasswordOTP: String (select: false, for password reset),\n  resetPasswordOTPExpiry: Date (select: false, for password reset),\n  lastResetOTPSentAt: Date (select: false, for rate limiting),\n  // E2EE key material (zero-knowledge — server stores opaque blobs)\n  publicKey: String (base64-encoded 32-byte NaCl box public key),\n  encryptedPrivateKey: String (base64-encoded AES-GCM ciphertext of secret key),\n  keyIv: String (base64-encoded 12-byte AES-GCM IV),\n  keySalt: String (base64-encoded 16-byte PBKDF2 salt),\n  contacts: [ObjectId] (ref: 'User', array of contact user IDs),\n  createdAt: Date (auto),\n  updatedAt: Date (auto)\n}\n\n// Indexes\n- email: 1 (unique)\n- isVerified: 1 (for cleanup queries)\n- otpExpiry: 1 (for expiration checks)\n```\n\n### ContactRequest Model\n\n```javascript\n{\n  _id: ObjectId,\n  senderId: ObjectId (ref: 'User', required, indexed),\n  receiverId: ObjectId (ref: 'User', required, indexed),\n  status: String (enum: ['pending', 'accepted', 'declined'], default: 'pending', indexed),\n  createdAt: Date (auto),\n  updatedAt: Date (auto)\n}\n\n// Indexes\n- senderId: 1\n- receiverId: 1\n- { senderId: 1, receiverId: 1 } (compound, unique - prevents duplicates)\n- { receiverId: 1, status: 1 } (for efficient pending request queries)\n```\n\n### Message Model\n\n```javascript\n{\n  _id: ObjectId,\n  senderId: ObjectId (ref: 'User', required, indexed),\n  receiverId: ObjectId (ref: 'User', required, indexed),\n  text: String (max: 5000 chars, trimmed — stores encrypted ciphertext when E2EE),\n  image: String (URL, trimmed — plaintext Cloudinary URL),\n  nonce: String (base64-encoded NaCl nonce — present when message is E2EE),\n  isRead: Boolean (default: false),\n  createdAt: Date (auto),\n  updatedAt: Date (auto)\n}\n\n// Indexes\n- senderId: 1\n- receiverId: 1\n- { senderId: 1, receiverId: 1, createdAt: -1 } (compound)\n\n// Validation\n- At least one of 'text' or 'image' must be present\n- If 'nonce' is present, 'text' contains encrypted ciphertext (not plaintext)\n```\n\n### Entity Relationship Diagram\n\n```\n┌─────────────────────────┐\n│         User            │\n├─────────────────────────┤\n│ _id: ObjectId (PK)      │\n│ name: String            │\n│ email: String (unique)  │\n│ password: String        │\n│ profilePic: String      │\n│ contacts: [ObjectId]    │◄────┐\n│ createdAt: Date         │     │\n│ updatedAt: Date         │     │ many-to-many\n└─────────────────────────┘     │ (self-referencing)\n           │                    │\n           │ 1                  │\n           │                    │\n           │ sends/receives     │\n           │                    │\n           │ *                  │\n           ▼                    │\n┌─────────────────────────┐     │\n│       Message           │     │\n├─────────────────────────┤     │\n│ _id: ObjectId (PK)      │     │\n│ senderId: ObjectId (FK) │─────┘\n│ receiverId: ObjectId(FK)│─────┐\n│ text: String            │     │\n│ image: String           │     │\n│ isRead: Boolean         │     │\n│ createdAt: Date         │     │\n│ updatedAt: Date         │     │\n└─────────────────────────┘     │\n                                │\n           ┌────────────────────┘\n           │\n           ▼\n┌─────────────────────────┐\n│   ContactRequest        │\n├─────────────────────────┤\n│ _id: ObjectId (PK)      │\n│ senderId: ObjectId (FK) │\n│ receiverId: ObjectId(FK)│\n│ status: String          │\n│ createdAt: Date         │\n│ updatedAt: Date         │\n└─────────────────────────┘\n```\n\n\n---\n\n## 🔒 Security Features\n\n### 1. Arcjet Security Suite\n\nRelay implements comprehensive security through Arcjet:\n\n#### Bot Detection\n- Blocks automated traffic and malicious bots\n- Allows legitimate bots (search engines, monitoring services)\n- Real-time threat detection\n\n#### Rate Limiting\n- Sliding window algorithm\n- 500 requests per IP per 60-second window (balanced for UX and security)\n- Automatic retry with exponential backoff on rate limit errors\n- Prevents abuse and DDoS attacks while allowing legitimate users\n- Fail-open strategy on service errors for better availability\n\n#### Shield Protection\n- SQL injection prevention\n- XSS attack mitigation\n- Common vulnerability protection\n\n### 2. Authentication Security\n\n```\n┌─────────────────────────────────────────────────────────┐\n│              Authentication Security Layers             │\n├─────────────────────────────────────────────────────────┤\n│  1. Password Hashing (bcrypt, 10 rounds)                │\n│  2. JWT Token Generation (7-day expiry)                 │\n│  3. HTTP-only Cookies (prevents XSS)                    │\n│  4. Secure Flag (HTTPS only in production)              │\n│  5. SameSite=Strict (CSRF protection)                   │\n│  6. Token Verification on Protected Routes              │\n└─────────────────────────────────────────────────────────┘\n```\n\n### 3. Input Validation\n\n- Request body validation\n- File type and size validation for images\n- MongoDB ObjectId validation\n- Email format validation\n- Password strength requirements\n\n### 4. Error Handling\n\n- Sanitized error messages (no sensitive data exposure)\n- Different error handling for development vs production\n- Comprehensive logging for debugging\n- Graceful degradation on service failures\n\n### 5. CORS Configuration\n\n```javascript\n// Development: localhost:5173\n// Production: Configured CLIENT_URL only\n{\n  origin: process.env.CLIENT_URL,\n  credentials: true\n}\n```\n\n### 6. End-to-End Encryption (E2EE)\n\nRelay implements **true zero-knowledge end-to-end encryption** — the server never has access to plaintext message content or private encryption keys. Even if the server is compromised, your conversations remain private.\n\n#### 🔐 Cryptographic Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                    E2EE Cryptographic Stack                          │\n├─────────────────────────────────────────────────────────────────────┤\n│  Key Exchange:      X25519 (Curve25519 ECDH)                        │\n│  Encryption:        XSalsa20-Poly1305 (NaCl box — authenticated)    │\n│  Key Wrapping:      AES-256-GCM (Web Crypto API)                    │\n│  Key Derivation:    PBKDF2-SHA256 (100,000 iterations)              │\n│  Recovery:          BIP39 12-word mnemonic → 512-bit seed           │\n│  Session Cache:     IndexedDB + sessionStorage (encrypted)          │\n│  Nonce Generation:  24-byte cryptographically random per message    │\n└─────────────────────────────────────────────────────────────────────┘\n```\n\n#### 🛡️ How It Works\n\n**Your messages are safe because:**\n\n1. **Messages are encrypted on your device** before they ever leave your browser. The server only receives unreadable ciphertext.\n2. **The server is zero-knowledge** — it stores encrypted blobs of your private key but can never decrypt them (they're locked with your password).\n3. **Only you and your conversation partner** have the keys needed to read messages.\n4. **A 12-word recovery phrase** lets you restore access to your encrypted messages if you change devices or forget your password.\n5. **Unique nonces per message** prevent replay attacks and ensure forward secrecy.\n6. **Authenticated encryption** (Poly1305 MAC) ensures messages haven't been tampered with.\n\n#### 🔄 Key Lifecycle\n\n| Event | What Happens |\n|-------|-------------|\n| **Signup** | 1. Client generates BIP39 recovery phrase (12 words)\u003cbr\u003e2. Derives 512-bit seed → first 32 bytes = secret key\u003cbr\u003e3. Generates X25519 key pair from secret key\u003cbr\u003e4. Encrypts private key with password (AES-256-GCM + PBKDF2 100k iterations)\u003cbr\u003e5. Stores encrypted blob + public key on server\u003cbr\u003e6. Shows recovery phrase modal (user must save it) |\n| **Login** | 1. Server returns encrypted key blob + public key\u003cbr\u003e2. Client decrypts private key with password\u003cbr\u003e3. Keys cached in IndexedDB (session-encrypted)\u003cbr\u003e4. Ready to encrypt/decrypt messages |\n| **Page Refresh** | Keys restored from IndexedDB session cache (no password prompt) |\n| **Send Message** | 1. Generate random 24-byte nonce\u003cbr\u003e2. Encrypt text with nacl.box(text, nonce, receiverPK, senderSK)\u003cbr\u003e3. Send ciphertext + nonce to server\u003cbr\u003e4. Server stores opaque blob (never sees plaintext) |\n| **Receive Message** | 1. Receive ciphertext + nonce from server\u003cbr\u003e2. Decrypt with nacl.box.open(ciphertext, nonce, senderPK, receiverSK)\u003cbr\u003e3. Display plaintext in UI |\n| **Password Change** | 1. Private key re-encrypted with new password\u003cbr\u003e2. Updated blob sent to server\u003cbr\u003e3. Old password no longer works |\n| **Forgot Password (with phrase)** | 1. User enters 12-word recovery phrase\u003cbr\u003e2. Re-derives original key pair (deterministic)\u003cbr\u003e3. Re-encrypts with new password\u003cbr\u003e4. All old messages remain readable |\n| **Forgot Password (no phrase)** | 1. New key pair generated\u003cbr\u003e2. Old encrypted messages become permanently unreadable\u003cbr\u003e3. Fresh start with new encryption keys |\n| **Logout** | All keys cleared from memory, IndexedDB, and sessionStorage |\n\n#### 🔒 What Is Encrypted vs. What Is Not\n\n| Content | Encrypted? | Algorithm | Details |\n|---------|:----------:|-----------|---------|\n| Message text | ✅ | NaCl box (X25519 + XSalsa20-Poly1305) | Encrypted client-side before transmission |\n| Private key at rest | ✅ | AES-256-GCM + PBKDF2 (100k iterations) | Password-protected, server stores opaque blob |\n| Private key in transit | ✅ | AES-256-GCM ciphertext | Only encrypted blob transmitted |\n| Image files | ❌ | N/A | Uploaded to Cloudinary server-side (known limitation) |\n| Image URLs | ❌ | N/A | Stored as plaintext Cloudinary URLs |\n| Message metadata | ❌ | N/A | senderId, receiverId, timestamps, read status, nonce |\n| Public keys | ❌ | N/A | Public data by design — shared openly for key exchange |\n\n\u003e **⚠️ Known Limitation:** Image encryption is not currently implemented. Since the server handles Cloudinary uploads, images are stored unencrypted. Fully encrypting images would require client-side encryption and direct-to-Cloudinary uploads with signed presets. Text messages remain fully encrypted.\n\n#### 🔑 Recovery Phrase\n\nAfter signup, users receive a **12-word BIP39 mnemonic** (e.g., `abandon ability cable damage enjoy ...`). This phrase:\n\n- **Deterministically derives** the same key pair every time (512-bit seed → 32-byte secret key)\n- Is the **only way** to recover encrypted messages if the password is forgotten\n- Is **never stored** on the server — only the user has it\n- Must be **written down and stored securely** by the user (offline backup recommended)\n- Follows the **BIP39 standard** used by cryptocurrency wallets (proven security model)\n- Can be validated using BIP39 checksum (last word contains checksum bits)\n\n**Security Best Practices:**\n- ✅ Write down the phrase on paper (not digital)\n- ✅ Store in a secure location (safe, safety deposit box)\n- ✅ Never share with anyone (not even support staff)\n- ✅ Never store in cloud services or password managers\n- ❌ Don't take screenshots or photos\n- ❌ Don't email or message the phrase\n\n#### 🔬 Technical Details\n\n**Encryption Process (nacl.box):**\n```\n1. Generate random 24-byte nonce\n2. Compute shared secret: X25519(senderSK, receiverPK)\n3. Derive encryption key: HSalsa20(shared_secret, nonce[0:16])\n4. Encrypt: XSalsa20(plaintext, key, nonce)\n5. Authenticate: Poly1305(ciphertext, key)\n6. Output: ciphertext || mac (authenticated encryption)\n```\n\n**Key Wrapping Process (AES-GCM):**\n```\n1. Generate random 16-byte salt\n2. Derive AES key: PBKDF2-SHA256(password, salt, 100k iterations)\n3. Generate random 12-byte IV\n4. Encrypt: AES-256-GCM(privateKey, key, IV)\n5. Output: ciphertext || auth_tag (authenticated encryption)\n```\n\n**Why These Algorithms?**\n- **X25519**: Fast, secure elliptic curve Diffie-Hellman (ECDH) for key exchange\n- **XSalsa20-Poly1305**: Fast authenticated encryption with strong security guarantees\n- **AES-256-GCM**: Industry-standard authenticated encryption for key wrapping\n- **PBKDF2**: Slow key derivation to resist brute-force attacks on passwords\n- **BIP39**: Proven standard for human-readable key backup (used by billions in crypto)\n\n\u003e 📖 **For detailed technical documentation**, see [E2EE-SECURITY.md](E2EE-SECURITY.md) — includes cryptographic specifications, threat model, security audit, and best practices.\n\n\n---\n\n## 📞 Voice \u0026 Video Calling\n\nRelay supports **peer-to-peer voice and video calls** powered by WebRTC, with full call controls including mute, camera toggle, camera flip (mobile), and call duration tracking.\n\n### Architecture\n\n```\n┌──────────────┐                                    ┌──────────────┐\n│   Caller     │                                    │   Receiver   │\n│              │                                    │              │\n│  getUserMedia│                                    │  getUserMedia│\n│  (mic/cam)   │                                    │  (mic/cam)   │\n│              │      Socket.IO Signaling           │              │\n│  ┌─────────┐ │  ──call:initiate──────────────▶    │ ┌──────────┐ │\n│  │  WebRTC │ │                                    │ │  WebRTC  │ │\n│  │  Peer   │ │  ◀──call:accepted─────────────     │ │  Peer    │ │\n│  │  Conn   │ │  ──call:offer───────────────▶      │ │  Conn    │ │\n│  │         │ │  ◀──call:answer─────────────       │ │          │ │\n│  │         │ │  ◀─call:ice-candidate──▶           │ │          │ │\n│  └────┬────┘ │                                    │ └────┬─────┘ │\n│       │      │                                    │      │       │\n└───────┼──────┘                                    └──────┼───────┘\n        │                                                  │\n        │          Direct P2P Media Connection             │\n        │  ◀═══════════════════════════════════════════▶   │\n        │         Audio/Video (DTLS-SRTP encrypted)        │\n        │                                                  │\n```\n\n### Call Flow\n\n```mermaid\nsequenceDiagram\n    participant Caller\n    participant Server\n    participant Receiver\n\n    Caller-\u003e\u003eCaller: getUserMedia (mic + optional cam)\n    Caller-\u003e\u003eServer: call:initiate (receiverId, callType)\n    Server-\u003e\u003eReceiver: call:incoming (callerName, callerPic, callType)\n    Note over Receiver: Shows IncomingCallModal\n\n    alt Call Accepted\n        Receiver-\u003e\u003eReceiver: getUserMedia (mic + optional cam)\n        Receiver-\u003e\u003eServer: call:accepted (callerId)\n        Server-\u003e\u003eCaller: call:accepted\n        Caller-\u003e\u003eCaller: Create RTCPeerConnection + add tracks\n        Caller-\u003e\u003eServer: call:offer (SDP offer)\n        Server-\u003e\u003eReceiver: call:offer\n        Receiver-\u003e\u003eReceiver: Create RTCPeerConnection + add tracks\n        Receiver-\u003e\u003eReceiver: Set remote description (offer)\n        Receiver-\u003e\u003eServer: call:answer (SDP answer)\n        Server-\u003e\u003eCaller: call:answer\n        Caller-\u003e\u003eCaller: Set remote description (answer)\n        Note over Caller,Receiver: ICE candidates exchanged via call:ice-candidate\n        Note over Caller,Receiver: P2P media connection established (DTLS-SRTP)\n    else Call Rejected\n        Receiver-\u003e\u003eServer: call:rejected\n        Server-\u003e\u003eCaller: call:rejected\n    else No Answer (30s timeout)\n        Caller-\u003e\u003eCaller: Auto-end call\n    end\n```\n\n### Call Features\n\n| Feature | Description |\n|---------|-------------|\n| **Voice Calls** | Full-duplex audio via WebRTC with hidden `\u003caudio\u003e` element |\n| **Video Calls** | Live video with local PiP preview and remote fullscreen |\n| **Mute** | Toggle microphone on/off, synced to remote peer |\n| **Camera Toggle** | Turn camera on/off during video calls |\n| **Camera Flip** | Switch front↔back camera on mobile (device enumeration fallback) |\n| **Call Timer** | Duration counter starts when P2P connection established |\n| **Auto-timeout** | Unanswered calls auto-end after 30 seconds |\n| **Busy Detection** | If receiver is already in a call, caller gets \"User is busy\" |\n| **Offline Detection** | If receiver is offline, caller gets \"User is offline\" |\n| **Disconnection Recovery** | 5-second grace period for temporary network drops |\n| **Controls Auto-hide** | Video call controls fade out after 4 seconds of inactivity |\n\n### ICE Server Configuration\n\nCurrently using Google STUN servers for NAT traversal:\n\n```javascript\niceServers: [\n  { urls: 'stun:stun.l.google.com:19302' },\n  { urls: 'stun:stun1.l.google.com:19302' },\n  { urls: 'stun:stun2.l.google.com:19302' },\n  { urls: 'stun:stun3.l.google.com:19302' },\n]\n```\n\n\u003e **⚠️ Note:** STUN-only configuration. Calls may fail if both users are behind symmetric NATs (corporate networks, some carriers). Adding a TURN server is recommended for production deployments with enterprise users.\n\n### Call Security\n\n- **DTLS-SRTP**: All audio/video media is encrypted in transit by default (WebRTC standard)\n- **Signaling Relay**: The server only relays SDP offers/answers and ICE candidates — never touches actual media\n- **Direct P2P**: Once connected, audio/video flows directly between peers without passing through the server\n\n\n---\n\n## 🔄 Real-Time Communication\n\n### Socket.IO Implementation\n\n#### Connection Flow\n\n```\n1. Client connects with JWT cookie\n2. Server validates JWT from cookie\n3. Server authenticates user from database\n4. Connection established\n5. User added to online users map\n6. Broadcast updated online users list\n```\n\n#### Online User Tracking\n\n```javascript\n// Server maintains a Map\nonlineUsers: Map\u003cuserId, socketId\u003e\n\n// When user connects\nonlineUsers.set(userId, socketId)\nio.emit('getOnlineUsers', Array.from(onlineUsers.keys()))\n\n// When user disconnects\nonlineUsers.delete(userId)\nio.emit('getOnlineUsers', Array.from(onlineUsers.keys()))\n```\n\n#### Typing Indicators\n\n```\nUser A starts typing → Emit 'typing' → Server → Emit 'userTyping' → User B\nUser A stops typing → Emit 'stopTyping' → Server → Emit 'userStopTyping' → User B\n```\n\n#### Message Delivery\n\n```\n1. User A sends message via HTTP POST\n2. Server saves to database\n3. Server responds to User A\n4. Server finds User B's socket ID\n5. Server emits 'newMessage' to User B's socket\n6. User B receives message in real-time\n```\n\n### State Management (Zustand)\n\n#### Store Architecture\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                    Frontend Stores                      │\n├─────────────────────────────────────────────────────────┤\n│  useAuthStore                                           │\n│  - user, isAuthenticated, isLoading                     │\n│  - signup(), login(), logout(), checkAuth()             │\n├─────────────────────────────────────────────────────────┤\n│  useChatStore                                           │\n│  - contacts, messages, selectedContact                  │\n│  - fetchContacts(), fetchMessages(), sendMessage()      │\n│  - lastMessages, unreadCounts                           │\n├─────────────────────────────────────────────────────────┤\n│  useContactStore                                        │\n│  - pendingRequests, sentRequests, isLoadingRequests     │\n│  - sendContactRequest(), acceptRequest()                │\n│  - declineRequest(), cancelRequest(), removeContact()   │\n├─────────────────────────────────────────────────────────┤\n│  useSocketStore                                         │\n│  - socket, onlineUsers, typingUsers                     │\n│  - connectSocket(), disconnectSocket()                  │\n│  - emitTyping(), emitStopTyping()                       │\n│  - WebRTC call event listeners                          │\n├─────────────────────────────────────────────────────────┤\n│  useCallStore                                           │\n│  - callStatus, callType, remoteUser, localStream        │\n│  - isMuted, isVideoOff, callDuration, remoteStream      │\n│  - initiateCall(), acceptCall(), rejectCall(), endCall()│\n│  - toggleMute(), toggleVideo(), flipCamera()            │\n│  - WebRTC peer connection management                    │\n├─────────────────────────────────────────────────────────┤\n│  useThemeStore                                          │\n│  - theme (light/dark mode)                              │\n├─────────────────────────────────────────────────────────┤\n│  useUIStore                                             │\n│  - imagePreview, modals, UI state                       │\n└─────────────────────────────────────────────────────────┘\n```\n\n\n---\n\n## 🚀 Deployment\n\n### Production Build\n\n```bash\n# Build the application\nnpm run build\n\n# This will:\n# 1. Install backend dependencies\n# 2. Install frontend dependencies\n# 3. Build frontend (creates /frontend/dist)\n```\n\n### Deployment Options\n\n#### Option 1: Deploy to Render/Railway/Heroku\n\n1. **Prepare Environment Variables**\n   - Set all required environment variables in platform dashboard\n   - Set `NODE_ENV=production`\n\n2. **Configure Build Command**\n   ```bash\n   npm run build\n   ```\n\n3. **Configure Start Command**\n   ```bash\n   npm start\n   ```\n\n4. **Port Configuration**\n   - The app uses `process.env.PORT` or defaults to 3000\n   - Most platforms automatically set PORT\n\n#### Option 2: Deploy to VPS (DigitalOcean, AWS EC2, etc.)\n\n1. **Install Node.js and MongoDB**\n   ```bash\n   curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\n   sudo apt-get install -y nodejs\n   ```\n\n2. **Clone and Setup**\n   ```bash\n   git clone https://github.com/yourusername/relay.git\n   cd relay\n   npm run build\n   ```\n\n3. **Use PM2 for Process Management**\n   ```bash\n   npm install -g pm2\n   cd backend\n   pm2 start src/server.js --name relay\n   pm2 save\n   pm2 startup\n   ```\n\n4. **Setup Nginx as Reverse Proxy**\n   ```nginx\n   server {\n       listen 80;\n       server_name yourdomain.com;\n\n       location / {\n           proxy_pass http://localhost:3000;\n           proxy_http_version 1.1;\n           proxy_set_header Upgrade $http_upgrade;\n           proxy_set_header Connection 'upgrade';\n           proxy_set_header Host $host;\n           proxy_cache_bypass $http_upgrade;\n       }\n   }\n   ```\n\n5. **Setup SSL with Let's Encrypt**\n   ```bash\n   sudo apt install certbot python3-certbot-nginx\n   sudo certbot --nginx -d yourdomain.com\n   ```\n\n\n#### Option 3: Deploy to Vercel (Frontend) + Render (Backend)\n\n**Frontend (Vercel):**\n```bash\ncd frontend\nvercel --prod\n```\n\n**Backend (Render):**\n- Connect GitHub repository\n- Set root directory to `backend`\n- Configure environment variables\n- Deploy\n\n### Environment Variables Checklist for Production\n\n- [ ] `NODE_ENV=production`\n- [ ] `PORT` (usually auto-set by platform)\n- [ ] `MONGO_URI` (MongoDB Atlas connection string)\n- [ ] `JWT_SECRET` (strong random string)\n- [ ] `RESEND_API_KEY`\n- [ ] `SENDER_EMAIL`\n- [ ] `SENDER_NAME`\n- [ ] `CLIENT_URL` (your frontend domain)\n- [ ] `CLOUDINARY_CLOUD_NAME`\n- [ ] `CLOUDINARY_API_KEY`\n- [ ] `CLOUDINARY_API_SECRET`\n- [ ] `ARCJET_KEY`\n- [ ] `ARCJET_ENV=production`\n\n### Post-Deployment Checklist\n\n- [ ] Test user registration and login\n- [ ] Test message sending (text and images)\n- [ ] Verify WebSocket connection\n- [ ] Test real-time features (online status, typing indicators)\n- [ ] Test voice and video calling between two users\n- [ ] Test camera flip on mobile devices\n- [ ] Check email delivery (welcome emails)\n- [ ] Verify image uploads to Cloudinary\n- [ ] Test on mobile devices\n- [ ] Monitor error logs\n- [ ] Setup monitoring (e.g., Sentry, LogRocket)\n- [ ] Configure database backups\n\n\n---\n\n## 📁 Project Structure\n\n```\nrelay/\n├── backend/\n│   ├── scripts/\n│   │   ├── cleanupUnverifiedUsers.js  # Manual cleanup script\n│   │   └── resetDatabase.js           # Database reset script\n│   ├── src/\n│   │   ├── config/\n│   │   │   └── env.js                 # Environment configuration\n│   │   ├── controllers/\n│   │   │   ├── auth.controller.js     # Authentication logic (with OTP)\n│   │   │   ├── contact.controller.js  # Contact request logic\n│   │   │   └── message.controller.js  # Message handling logic\n│   │   ├── emails/\n│   │   │   ├── emailHandlers.js       # Email sending functions\n│   │   │   └── emailTemplates.js      # Email HTML templates (OTP + Welcome)\n│   │   ├── lib/\n│   │   │   ├── arcjet.js              # Arcjet security config\n│   │   │   ├── cloudinary.js          # Cloudinary config\n│   │   │   ├── db.js                  # MongoDB connection\n│   │   │   ├── generateToken.js       # JWT token generation\n│   │   │   ├── resend.js              # Resend email config\n│   │   │   └── socket.js              # Socket.IO setup\n│   │   ├── middleware/\n│   │   │   ├── arcjet.middleware.js   # Arcjet protection middleware\n│   │   │   └── auth.middleware.js     # JWT authentication middleware\n│   │   ├── models/\n│   │   │   ├── contactRequest.model.js # Contact request schema\n│   │   │   ├── message.model.js       # Message schema\n│   │   │   └── user.model.js          # User schema (with OTP + contacts)\n│   │   ├── routes/\n│   │   │   ├── auth.route.js          # Authentication routes (with OTP)\n│   │   │   ├── contact.route.js       # Contact request routes\n│   │   │   └── message.route.js       # Message routes\n│   │   ├── services/\n│   │   │   └── cleanupService.js      # Automatic cleanup service\n│   │   └── server.js                  # Express server entry point\n│   ├── .env                           # Environment variables\n│   ├── .gitignore\n│   └── package.json\n│\n├── frontend/\n│   ├── public/\n│   │   ├── favicon.svg\n│   │   ├── icons.svg\n│   │   └── relay-icon.svg\n│   ├── src/\n│   │   ├── components/\n│   │   │   ├── call/\n│   │   │   │   ├── CallView.jsx       # Active call UI (video/voice)\n│   │   │   │   ├── CallView.css       # Call view styling\n│   │   │   │   ├── IncomingCallModal.jsx  # Incoming call notification\n│   │   │   │   └── IncomingCallModal.css  # Incoming call styling\n│   │   │   ├── chat/\n│   │   │   │   ├── ChatHeader.jsx     # Chat header component (+ call buttons)\n│   │   │   │   ├── EmptyChat.jsx      # Empty state component\n│   │   │   │   ├── MessageBubble.jsx  # Individual message component\n│   │   │   │   ├── MessageInput.jsx   # Message input component\n│   │   │   │   └── MessageList.jsx    # Messages list component\n│   │   │   ├── shared/\n│   │   │   │   ├── AddContactModal.jsx      # Add contact modal\n│   │   │   │   ├── AddContactModal.css      # Modal styling\n│   │   │   │   ├── Avatar.jsx               # User avatar component\n│   │   │   │   ├── Button.jsx               # Reusable button component\n│   │   │   │   ├── ContactRequestsModal.jsx # Contact requests modal\n│   │   │   │   ├── ContactRequestsModal.css # Modal styling\n│   │   │   │   ├── ForgotPasswordModal.jsx  # Password reset modal\n│   │   │   │   ├── ForgotPasswordModal.css  # Modal styling\n│   │   │   │   ├── ImageLightbox.jsx        # Image preview modal\n│   │   │   │   ├── Input.jsx                # Reusable input component\n│   │   │   │   ├── Logo.jsx                 # App logo component\n│   │   │   │   ├── VerificationModal.jsx    # OTP verification modal\n│   │   │   │   └── VerificationModal.css    # Modal styling\n│   │   │   └── sidebar/\n│   │   │       └── Sidebar.jsx        # Contacts sidebar\n│   │   ├── lib/\n│   │   │   ├── api.js                 # Axios instance \u0026 interceptors\n│   │   │   ├── constants.js           # API endpoints constants\n│   │   │   └── utils.js               # Utility functions\n│   │   ├── pages/\n│   │   │   ├── AuthPage.jsx           # Login/Signup page\n│   │   │   ├── ChatPage.jsx           # Main chat page\n│   │   │   └── ProfilePage.jsx        # User profile page\n│   │   ├── store/\n│   │   │   ├── useAuthStore.js        # Authentication state\n│   │   │   ├── useCallStore.js        # Call state \u0026 WebRTC logic\n│   │   │   ├── useChatStore.js        # Chat state\n│   │   │   ├── useContactStore.js     # Contact request state\n│   │   │   ├── useSocketStore.js      # WebSocket state (+ call signaling)\n│   │   │   ├── useThemeStore.js       # Theme state\n│   │   │   └── useUIStore.js          # UI state\n│   │   ├── App.jsx                    # Root component\n│   │   ├── index.css                  # Global styles\n│   │   └── main.jsx                   # React entry point\n│   ├── .gitignore\n│   ├── eslint.config.js\n│   ├── index.html\n│   ├── package.json\n│   └── vite.config.js\n│\n├── .git/\n├── package.json                       # Root package.json\n└── README.md                          # This file\n```\n\n\n---\n\n## 🎨 Features Deep Dive\n\n### 1. User Authentication \u0026 Email Verification\n\n**Signup Flow:**\n1. User submits registration form (name, email, password)\n2. Arcjet validates request (bot detection, rate limiting)\n3. Server validates input data (format, length, strength)\n4. Check if email already exists in database\n5. Hash password with bcrypt (10 rounds in dev, 12 in production)\n6. Generate cryptographically secure 6-digit OTP using `crypto.randomInt()`\n7. Set OTP expiry (10 minutes from now)\n8. Record `lastOTPSentAt` timestamp for rate limiting\n9. Create user in MongoDB with `isVerified: false`\n10. Send OTP email via Resend (professional template)\n11. Return user data to client (no JWT token yet - user must verify first)\n12. Client shows animated verification modal\n\n**Email Verification Flow:**\n1. User receives OTP email (6-digit code, 10-minute expiry)\n2. User enters code in verification modal (auto-focus, auto-submit)\n3. Client sends verification request to `/api/auth/verify-email`\n4. Server validates OTP format (must be 6 digits)\n5. Server finds user and checks if already verified\n6. Server checks OTP expiry (must be within 10 minutes)\n7. Server compares OTP using `crypto.timingSafeEqual()` (timing attack prevention)\n8. Mark user as verified, clear OTP fields (`otp`, `otpExpiry`, `lastOTPSentAt`)\n9. Generate JWT token (7-day expiry)\n10. Set HTTP-only secure cookie with SameSite=Strict\n11. Send welcome email asynchronously (non-blocking)\n12. Return user data to client with token\n13. Client redirects to chat page and connects WebSocket\n\n**OTP Resend Flow:**\n1. User clicks \"Resend Code\" button\n2. Client sends request to `/api/auth/resend-otp`\n3. Server checks if user is already verified (reject if yes)\n4. Server checks rate limit using `lastOTPSentAt` (must be \u003e 60 seconds)\n5. If rate limited, return 429 with `retryAfter` seconds\n6. Generate new cryptographically secure 6-digit OTP\n7. Update user with new OTP, expiry, and `lastOTPSentAt`\n8. Send new OTP email via Resend\n9. Client shows 60-second countdown timer\n10. User can resend again after cooldown expires\n\n**Login Flow:**\n1. User submits credentials (email, password)\n2. Arcjet validates request (bot detection, rate limiting)\n3. Server validates input format\n4. Find user by email (case-insensitive)\n5. Compare password with bcrypt hash\n6. Check if user is verified (`isVerified: true`)\n7. If not verified:\n   - Return 403 with `requiresVerification: true` flag\n   - Client shows verification modal\n   - User can resend OTP or enter existing OTP\n8. If verified:\n   - Generate JWT token (7-day expiry)\n   - Set HTTP-only secure cookie\n   - Return user data to client\n   - Client connects WebSocket\n\n**Security Features:**\n- ✅ Cryptographically secure OTP generation (`crypto.randomInt()`)\n- ✅ Constant-time OTP comparison (`crypto.timingSafeEqual()` - prevents timing attacks)\n- ✅ Buffer length validation (prevents server crashes)\n- ✅ 10-minute OTP expiration (time-limited validity)\n- ✅ 60-second rate limiting on resend (prevents abuse)\n- ✅ Accurate rate limiting with `lastOTPSentAt` field\n- ✅ OTP fields hidden from API responses (`select: false`)\n- ✅ Email enumeration prevention (generic error messages)\n- ✅ Single-use OTPs (cleared after verification)\n- ✅ XSS prevention in email templates (escapes all special chars including backticks)\n- ✅ Database indexes for performance (`isVerified`, `otpExpiry`)\n- ✅ Automatic cleanup of unverified users (every 6 hours, \u003e 24 hours old)\n\n### 2. Contact Request System \u0026 Privacy\n\n**Architecture:**\n\nThe contact system implements a privacy-first approach where users must explicitly connect before messaging. This prevents spam and gives users full control over their communications.\n\n**Database Design:**\n\n```javascript\n// User Model - contacts array\n{\n  contacts: [ObjectId] // Array of user IDs who are contacts\n}\n\n// ContactRequest Model\n{\n  senderId: ObjectId,    // Who sent the request\n  receiverId: ObjectId,  // Who received it\n  status: 'pending' | 'accepted' | 'declined'\n}\n```\n\n**Request Lifecycle:**\n\n```\n1. User A searches for User B\n   ↓\n2. User A sends contact request\n   ↓\n3. User B receives real-time notification\n   ↓\n4. User B can Accept or Decline\n   ↓\n5a. If Accepted:\n    - Both users added to each other's contacts\n    - Request status updated to 'accepted'\n    - Both users can now message\n    - Real-time notification to User A\n   ↓\n5b. If Declined:\n    - Request status updated to 'declined'\n    - Can be resent later\n```\n\n**Privacy Enforcement:**\n\n```javascript\n// Backend validates on EVERY message operation\n// 1. Get Messages\nif (!isContact) {\n  return 403: \"You can only view messages with your contacts\"\n}\n\n// 2. Send Message\nif (!isContact) {\n  return 403: \"You can only message your contacts\"\n}\n```\n\n**Sidebar Design (Discord-Style):**\n\n```\n┌─────────────────────────────────┐\n│  Relay    [+] [📥2] [◄]         │ ← Header with actions\n├─────────────────────────────────┤\n│ [Messages (3)] [All Contacts]   │ ← Two tabs\n├─────────────────────────────────┤\n│ 🔍 Search contacts...           │ ← Search bar\n├─────────────────────────────────┤\n│ Messages Tab:                   │\n│  • Alice [3]  2:30 PM  ← Unread │\n│  • Bob        1:15 PM           │\n│  • Charlie    Yesterday         │\n├─────────────────────────────────┤\n│ All Contacts Tab:               │\n│  • Alice                        │\n│  • Bob                          │\n│  • Charlie                      │\n│  • Diana      ● Online          │\n│  • Eve        eve@email.com     │\n└─────────────────────────────────┘\n```\n\n**Features:**\n- **Messages Tab**: Shows only contacts with message history, sorted by unread first then most recent\n- **All Contacts Tab**: Shows all contacts alphabetically\n- **Search**: Works across both tabs\n- **Collapsed Mode**: All action buttons remain accessible\n- **Real-Time Badges**: Shows pending request count\n- **Smart Empty States**: Context-aware messages\n\n**Edge Cases Handled:**\n- ✅ Duplicate request prevention\n- ✅ Can't send request to yourself\n- ✅ Can't message non-contacts\n- ✅ Real-time notification when removed as contact\n- ✅ Chat clears when contact removed\n- ✅ Loading states prevent double-clicks\n- ✅ Search resets when switching tabs\n- ✅ Proper ObjectId comparison (`.some()` with `.toString()`)\n- ✅ Error handling in all socket events\n\n### 3. Real-Time Messaging\n\n**Message Sending:**\n1. User types message and/or selects image\n2. Client validates input\n3. If image: Convert to base64\n4. Send POST request to `/api/message/send/:id`\n5. Server validates JWT\n6. If image: Upload to Cloudinary (optimized)\n7. Save message to MongoDB\n8. Find receiver's socket ID\n9. Emit 'newMessage' event to receiver\n10. Update UI for both users\n\n**Read Receipts:**\n1. User opens conversation\n2. Client sends PATCH to `/api/message/read/:id`\n3. Server marks all messages from that user as read\n4. Server emits 'messagesRead' event to sender\n5. Sender's UI updates to show read status\n\n### 4. Image Handling\n\n**Upload Process:**\n1. User selects image\n2. Client validates file type and size\n3. Convert to base64\n4. Send with message\n5. Server validates base64 format\n6. Upload to Cloudinary with transformations:\n   - Max dimensions: 1200x1200\n   - Auto quality optimization\n   - Auto format selection (WebP when supported)\n7. Store Cloudinary URL in database\n8. Return URL to client\n\n**Supported Formats:**\n- JPEG/JPG\n- PNG\n- WebP\n- GIF\n\n**Size Limits:**\n- Maximum: 10MB per image\n\n\n### 5. Online Status \u0026 Presence\n\n**Implementation:**\n```javascript\n// Server maintains online users map\nconst onlineUsers = new Map(); // userId -\u003e socketId\n\n// On connection\nonlineUsers.set(userId, socketId);\nio.emit('getOnlineUsers', Array.from(onlineUsers.keys()));\n\n// On disconnection\nonlineUsers.delete(userId);\nio.emit('getOnlineUsers', Array.from(onlineUsers.keys()));\n```\n\n**Client Usage:**\n```javascript\nconst { onlineUsers } = useSocketStore();\nconst isOnline = onlineUsers.includes(contact._id);\n```\n\n### 6. Typing Indicators\n\n**Flow:**\n```\nUser starts typing\n  ↓\nDebounced event (300ms)\n  ↓\nEmit 'typing' to server\n  ↓\nServer forwards to receiver\n  ↓\nReceiver shows indicator\n  ↓\nUser stops typing (1s timeout)\n  ↓\nEmit 'stopTyping'\n  ↓\nReceiver hides indicator\n```\n\n### 7. Unread Message Counters\n\n**Implementation:**\n1. Fetch all messages for each contact\n2. Count messages where:\n   - `senderId !== currentUserId` (received messages)\n   - `isRead === false` (not yet read)\n3. Store in `unreadCounts` state\n4. Display badge on contact\n5. Clear count when conversation opened\n\n---\n\n## 🧪 Testing\n\n### Manual Testing Checklist\n\n**Authentication:**\n- [ ] Sign up with valid credentials\n- [ ] Receive OTP email after signup\n- [ ] Verify email with correct OTP\n- [ ] Verify email with incorrect OTP (should fail)\n- [ ] Verify email with expired OTP (should fail)\n- [ ] Resend OTP functionality\n- [ ] Rate limiting on OTP resend (60-second cooldown)\n- [ ] Login with unverified account (should show verification modal)\n- [ ] Login with verified account (should succeed)\n- [ ] Sign up with existing email (should fail)\n- [ ] Login with correct credentials\n- [ ] Login with wrong password (should fail)\n- [ ] Logout functionality\n- [ ] Protected routes redirect when not authenticated\n- [ ] JWT token persists across page refreshes\n\n**Contact System:**\n- [ ] Search for users by name or email\n- [ ] Send contact request to another user\n- [ ] Receive contact request notification (real-time)\n- [ ] View pending requests in Inbox modal\n- [ ] Accept contact request\n- [ ] Decline contact request\n- [ ] Cancel sent request\n- [ ] Remove contact from chat header menu\n- [ ] Try to message non-contact (should fail with error)\n- [ ] Try to send duplicate request (should fail)\n- [ ] Try to send request to yourself (should fail)\n- [ ] Contact removed notification (real-time)\n- [ ] Sidebar shows correct tabs (Messages + All Contacts)\n- [ ] Messages tab shows only contacts with history\n- [ ] All Contacts tab shows all contacts alphabetically\n- [ ] Search works in both tabs\n- [ ] Switching tabs clears search\n- [ ] Collapsed sidebar shows all action buttons\n- [ ] Notification badge shows pending request count\n\n**Messaging:**\n- [ ] Send text message to contact\n- [ ] Send image message to contact\n- [ ] Send message with both text and image\n- [ ] Receive messages in real-time\n- [ ] Messages persist after refresh\n- [ ] Message timestamps display correctly\n- [ ] Read receipts update correctly\n- [ ] Unread badge shows correct count\n- [ ] Unread count clears when opening chat\n\n**Real-Time Features:**\n- [ ] Online status updates immediately\n- [ ] Typing indicators appear and disappear\n- [ ] Multiple tabs/devices sync correctly\n- [ ] Reconnection after network loss\n\n**Voice \u0026 Video Calling:**\n- [ ] Initiate voice call to online contact\n- [ ] Initiate video call to online contact\n- [ ] Receive incoming call notification (modal appears)\n- [ ] Accept incoming voice call\n- [ ] Accept incoming video call\n- [ ] Decline incoming call\n- [ ] End call from either side\n- [ ] Call auto-ends after 30s if unanswered\n- [ ] Mute/unmute during call (both sides notified)\n- [ ] Camera toggle during video call\n- [ ] Camera flip (front↔back) on mobile device\n- [ ] Call timer counts correctly\n- [ ] Call to offline user shows \"User is offline\"\n- [ ] Call to busy user shows \"User is busy\"\n- [ ] Temporary network drop recovers within 5s\n- [ ] Call controls auto-hide during connected video call\n- [ ] Local PiP video preview displays correctly\n- [ ] Voice call plays audio via hidden audio element\n\n**UI/UX:**\n- [ ] Responsive design on mobile\n- [ ] Smooth animations\n- [ ] Loading states display correctly\n- [ ] Error messages are user-friendly\n- [ ] Image lightbox works\n- [ ] Theme switching (if implemented)\n\n\n---\n\n## 🐛 Troubleshooting\n\n### Common Issues\n\n#### 1. MongoDB Connection Failed\n\n**Error:** `Error connecting to MongoDB`\n\n**Solutions:**\n- Check if MongoDB is running locally: `sudo systemctl status mongod`\n- Verify `MONGO_URI` in `.env` file\n- Check network connectivity for MongoDB Atlas\n- Whitelist your IP in MongoDB Atlas\n- Verify credentials in connection string\n\n#### 2. Socket Connection Failed\n\n**Error:** `Socket connection error` or `WebSocket connection failed`\n\n**Solutions:**\n- Ensure backend server is running\n- Check CORS configuration\n- Verify JWT cookie is being sent\n- Check browser console for specific errors\n- Ensure port 3000 is not blocked by firewall\n\n#### 3. Images Not Uploading\n\n**Error:** `Failed to upload image`\n\n**Solutions:**\n- Verify Cloudinary credentials in `.env`\n- Check image size (must be \u003c 10MB)\n- Verify image format (JPEG, PNG, WebP, GIF)\n- Check Cloudinary dashboard for quota limits\n- Ensure base64 encoding is correct\n\n#### 4. Emails Not Sending\n\n**Error:** `Failed to send welcome email`\n\n**Solutions:**\n- Verify Resend API key\n- Check sender email is verified in Resend dashboard\n- Verify domain configuration\n- Check Resend dashboard for error logs\n- Ensure email format is valid\n\n#### 5. Arcjet Blocking Requests\n\n**Error:** `Access denied` or `Rate limit exceeded`\n\n**Solutions:**\n- Check Arcjet dashboard for blocked requests\n- Verify Arcjet key is correct\n- Adjust rate limit settings if needed\n- In development, set `ARCJET_ENV=development`\n- Check IP whitelist settings\n\n#### 6. JWT Token Issues\n\n**Error:** `Unauthorized - Invalid token`\n\n**Solutions:**\n- Clear browser cookies\n- Verify `JWT_SECRET` is set\n- Check token expiration (7 days default)\n- Ensure cookie is HTTP-only and secure\n- Verify SameSite settings\n\n#### 7. Email Verification Issues\n\n**Error:** `OTP not received` or `Verification email not delivered`\n\n**Solutions:**\n- Check spam/junk folder\n- Verify Resend API key is correct\n- Check sender email is verified in Resend dashboard\n- Verify domain configuration in Resend\n- Check Resend dashboard for delivery logs\n- Ensure email format is valid\n- Try resending OTP\n\n**Error:** `Invalid OTP` or `OTP expired`\n\n**Solutions:**\n- Ensure OTP is entered correctly (6 digits)\n- Check if OTP has expired (10-minute window)\n- Request a new OTP using \"Resend Code\"\n- Verify server time is correct\n- Check database for OTP and expiry values\n\n**Error:** `Rate limit exceeded` when resending OTP\n\n**Solutions:**\n- Wait 60 seconds before requesting new OTP\n- Check countdown timer in UI\n- Verify rate limiting is working correctly\n- In development, you can adjust cooldown time in controller\n\n#### 8. \"Too Many Requests\" Errors\n\n**Error:** `Too many requests. Please try again later.`\n\n**Solutions:**\n- The app now has automatic retry - wait a few seconds\n- Rate limit is 500 requests per minute (should be sufficient for normal use)\n- If persistent, check Arcjet dashboard for issues\n- Verify Arcjet service is responding (check backend logs)\n- In development, ensure `ARCJET_ENV=development` is set\n- Clear browser cache and cookies\n- Wait 60 seconds for rate limit to reset\n\n**Note:** The application now has improved rate limiting (500 requests per minute) with automatic retry logic for better user experience while maintaining security.\n\n#### 9. Contact System Issues\n\n**Error:** `You can only message your contacts`\n\n**Solutions:**\n- Ensure you've sent a contact request\n- Verify the other user accepted your request\n- Check if contact was removed\n- Refresh the page to sync contacts list\n\n**Error:** `Contact request already pending`\n\n**Solutions:**\n- Check the \"Sent Requests\" section in Inbox modal\n- Wait for the other user to accept or decline\n- You can cancel the request and send a new one\n\n**Error:** `Already in your contacts`\n\n**Solutions:**\n- The user is already a contact\n- Check the \"All Contacts\" tab in sidebar\n- You can message them directly\n\n**Issue:** Contact requests not showing up\n\n**Solutions:**\n- Check if socket is connected (look for green \"Online\" status)\n- Refresh the page to reconnect socket\n- Check backend logs for socket errors\n- Verify JWT cookie is valid\n- Click the Inbox icon to manually refresh requests\n\n**Issue:** Removed contact still appears in sidebar\n\n**Solutions:**\n- Refresh the page\n- Check if the removal was successful (should see success toast)\n- Verify backend logs for errors\n- Check MongoDB to confirm contact was removed from both users\n\n### Debug Mode\n\nEnable detailed logging:\n\n```javascript\n// backend/src/server.js\nif (config.isDevelopment()) {\n  app.use((req, res, next) =\u003e {\n    console.log(`${req.method} ${req.path}`);\n    next();\n  });\n}\n```\n\n\n---\n\n## 🔮 Future Enhancements\n\n### Planned Features\n\n#### ✅ Completed Features\n- [x] **End-to-End Encryption**: Zero-knowledge E2EE with NaCl box (✅ Implemented)\n- [x] **Recovery Phrase**: BIP39 12-word mnemonic for key backup (✅ Implemented)\n- [x] **Email Verification**: Secure OTP-based email verification (✅ Implemented)\n- [x] **Password Reset**: Forgot password with OTP verification and key recovery (✅ Implemented)\n- [x] **Smart Rate Limiting**: Balanced protection with automatic retry (✅ Implemented)\n- [x] **Contact Request System**: Privacy-first messaging with mutual consent (✅ Implemented)\n- [x] **Dark/Light Theme**: System-aware theme switching (✅ Implemented)\n- [x] **Voice \u0026 Video Calling**: P2P calling with WebRTC, camera flip, and Obsidian-themed UI (✅ Implemented)\n\n#### 🚧 In Progress / Planned\n- [ ] **Image Encryption**: Client-side image encryption before upload\n- [ ] **Group Chats**: Create and manage encrypted group conversations\n- [ ] **Voice Messages**: Record and send encrypted audio messages\n- [ ] **TURN Server**: Relay server for calls behind symmetric NATs\n- [ ] **Call E2EE**: Application-level encryption for calls using WebRTC Insertable Streams\n- [ ] **File Sharing**: Send encrypted documents, PDFs, and other files\n- [ ] **Message Reactions**: React to messages with emojis\n- [ ] **Message Editing**: Edit sent messages (with encryption)\n- [ ] **Message Deletion**: Delete messages for everyone\n- [ ] **Search Functionality**: Search encrypted messages (client-side indexing)\n- [ ] **Push Notifications**: Browser and mobile push notifications\n- [ ] **Message Forwarding**: Forward encrypted messages to other contacts\n- [ ] **User Status**: Custom status messages\n- [ ] **Last Seen**: Show when user was last active\n- [ ] **Blocked Users**: Block and unblock functionality\n- [ ] **Multi-language Support**: Internationalization (i18n)\n- [ ] **Admin Dashboard**: User management and analytics\n- [ ] **Message Scheduling**: Schedule encrypted messages for later\n- [ ] **Auto-delete Messages**: Temporary messages feature (self-destructing)\n- [ ] **Disappearing Messages**: Configurable message expiration\n- [ ] **Screenshot Detection**: Warn users when screenshots are taken (mobile)\n- [ ] **Key Verification**: QR code-based public key verification (TOFU)\n\n### Performance Optimizations\n\n- [ ] **Message Pagination**: Implement virtual scrolling for large message histories\n- [ ] **Redis Caching**: Cache user sessions, online status, and frequently accessed data\n- [ ] **Database Optimization**: Optimize queries with aggregation pipelines and proper indexing\n- [ ] **CDN Integration**: Serve static assets via CDN for faster global delivery\n- [ ] **Service Worker**: Add offline support and background sync for messages\n- [ ] **Lazy Loading**: Implement lazy loading for images and message history\n- [ ] **Compression**: Add gzip/brotli compression middleware\n- [ ] **Code Splitting**: Optimize bundle size with dynamic imports and route-based splitting\n- [ ] **Image Optimization**: Implement progressive image loading and WebP format\n- [ ] **WebSocket Optimization**: Implement message batching and compression\n- [ ] **IndexedDB Optimization**: Optimize encrypted key storage and message caching\n- [ ] **Memory Management**: Implement proper cleanup for large message histories\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please follow these guidelines:\n\n### How to Contribute\n\n1. **Fork the repository**\n   ```bash\n   git clone https://github.com/yourusername/relay.git\n   ```\n\n2. **Create a feature branch**\n   ```bash\n   git checkout -b feature/amazing-feature\n   ```\n\n3. **Make your changes**\n   - Write clean, documented code\n   - Follow existing code style\n   - Add comments where necessary\n\n4. **Test your changes**\n   - Ensure all existing features still work\n   - Test new features thoroughly\n\n5. **Commit your changes**\n   ```bash\n   git commit -m \"Add amazing feature\"\n   ```\n\n6. **Push to your fork**\n   ```bash\n   git push origin feature/amazing-feature\n   ```\n\n7. **Open a Pull Request**\n   - Describe your changes\n   - Reference any related issues\n\n### Code Style Guidelines\n\n- Use ES6+ features\n- Follow Airbnb JavaScript Style Guide\n- Use meaningful variable and function names\n- Add JSDoc comments for functions\n- Keep functions small and focused\n- Handle errors appropriately\n\n### Commit Message Convention\n\n```\nfeat: Add new feature\nfix: Fix bug\ndocs: Update documentation\nstyle: Format code\nrefactor: Refactor code\ntest: Add tests\nchore: Update dependencies\n```\n\n\n---\n\n## 📄 License\n\nThis project is licensed under the ISC License.\n\n```\nISC License\n\nCopyright (c) 2026 Rudra Sanandiya\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n```\n\n---\n\n## 👨‍💻 Author\n\n**Rudra Sanandiya** - *The GOAT*\n\n- GitHub: [@rudrasanandiya](https://github.com/rudra1806)\n\n---\n\n## 🙏 Acknowledgments\n\n- **MongoDB** - Database solution\n- **Cloudinary** - Image hosting and optimization\n- **Resend** - Email delivery service\n- **Arcjet** - Security and protection\n- **Socket.IO** - Real-time communication\n- **React Team** - Amazing UI library\n- **Zustand** - Simple state management\n- **Vite** - Lightning-fast build tool\n\n---\n\n## 📞 Support\n\nIf you have any questions or need help, please:\n\n1. Check the [Troubleshooting](#-troubleshooting) section\n2. Search existing [GitHub Issues](https://github.com/rudra1806/relay/issues)\n3. Open a new issue with detailed information\n4. Contact the author\n\n---\n\n## ⭐ Show Your Support\n\nIf you found this project helpful, please give it a ⭐️ on GitHub!\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**Built with ❤️ by Rudra Sanandiya**\n\n[Report Bug](https://github.com/rudra1806/relay/issues) · [Request Feature](https://github.com/rudra1806/relay/issues)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frudra1806%2Frelay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frudra1806%2Frelay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frudra1806%2Frelay/lists"}