Waves is a modern, real-time chat application that offers global, network-based, and custom private chat rooms. Built with React, Node.js, and Socket.IO, it features a beautiful, responsive UI and seamless real-time communication.
- Global Room: Connect with users worldwide in a public chat
- Network Room: Auto-assigned rooms based on IP subnet for local connections
- Custom Rooms: Private rooms with unique 6-character codes for secure sharing
- Anonymous login with auto-generated usernames and colors
- Custom account creation with persistent identities
- Room-specific color palettes for visual distinction
- Peer-to-Peer messaging via WebRTC with automatic server fallback
- Real-time communication with message deduplication
- Message reactions and timestamps
- Mobile-optimized interface with touch-friendly controls
- Native sharing with clipboard fallback
- Auto-focus input fields and rate limiting
- Node.js (v14 or higher)
- MongoDB
- npm or yarn
- Clone the repository
git clone https://github.com/wavey-waves/waves.git
cd waves- Install dependencies for both frontend and backend
# Install backend dependencies
cd backend
npm install
# Install frontend dependencies
cd ../frontend
npm install- Create environment files
Backend (.env):
PORT=3000
MONGODB_URI=your_mongodb_uri
JWT_SECRET=your_jwt_secret
NODE_ENV=development- Start the development servers
Backend:
cd backend
npm run devFrontend:
cd frontend
npm run dev- React (Vite)
- Socket.IO Client
- Axios
- TailwindCSS
- React Router
- react-toastify
- Node.js
- Express
- Socket.IO
- MongoDB with Mongoose
- JWT Authentication
Waves is a real-time chat application featuring Peer-to-Peer (P2P) messaging with WebRTC Data Channels, automatic server fallback, and three room types: Global, Network-based, and Custom private rooms.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ WAVES CHAT PLATFORM │
│ Real-time P2P Messaging System │
└─────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ USER JOURNEY │
└─────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────┴───────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ ANONYMOUS USER │ │ REGISTERED USER │
│ (localStorage) │ │ (JWT Cookies) │
└─────────────────────┘ └─────────────────────┘
│ │
└───────────────┬───────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ROOM SELECTION │
└─────────────────────────────────────────────────────────────────────────────────┘
│
┌────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────┐ ┌────────────────┐ ┌──────────────────────┐
│ GLOBAL ROOM │ │ NETWORK │ │ CUSTOM ROOM │
│ (/chat/global) │ │ ROOM │ │ (/chat/custom/XXXXXX)|
│ │ │ (/chat/network)| │ │
└─────────────────────┘ └────────────────┘ └──────────────────────┘
│ │ │
└─────────────────────┼──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION FLOW │
└─────────────────────────────────────────────────────────────────────────────────┘
1. User visits / (Home)
2. Clicks room type (Global/Network/Custom)
3. JoinRoom component loads
4. Check localStorage.getItem('anonymousUser')
├── If exists & valid (not expired):
│ └── Parse JSON: {name, color, expiry}
│ └── expiry = Date.now() + 7 days
└── If missing/invalid/expired:
└── Generate new random name & color
├── Name: unique-names-generator (adjective-color-animal)
├── Color: Room-specific color palette
└── Store in localStorage as JSON
5. User clicks "Join as Anonymous"
6. POST /api/auth/login with {userName: randomName}
├── If user exists: Return user data
└── If user doesn't exist: Auto-create via signup
└── POST /api/auth/signup with {userName, color, isAnonymous: true}
7. Server generates JWT token (7 days expiry)
8. Cookie: jwt=token; httpOnly=true; secure=production; path="/"
9. Return: {_id, userName, color, isAnonymous: true}
1. User visits / (Home)
2. Clicks room type → JoinRoom component
3. Selects "Create Account" tab
4. Enters username, password, selects color
5. POST /api/auth/signup with {userName, password, color, isAnonymous: false}
├── Validate: username unique, password ≥6 chars
├── Hash password: bcrypt.genSalt(10) + bcrypt.hash()
├── Store: {userName, hashedPassword, color, isAnonymous: false}
└── Generate JWT token (7 days)
6. Cookie: jwt=token (same settings as anonymous)
7. Return: {_id, userName, color, isAnonymous: false}
- JWT Cookie: 7 days expiry, httpOnly, secure in production
- Anonymous localStorage: 7 days expiry, auto-regenerates
- Color Assignment: Room-specific palettes (15 colors each)
├── Global: Purple/Violet/Blue theme
├── Network: Emerald/Cyan/Teal theme
├── Custom: Rose/Pink theme
- Accessible to all authenticated users
- Room name: "global-room"
- No special assignment logic
- All users join same Socket.IO room: "global-room"
1. User authenticates successfully
2. POST /api/rooms/assign (protected route)
3. Extract client IP using request-ip package
4. Create subnet: IP.split('.').slice(0,3).join('.')
5. Generate roomName: `network-${subnet}`
6. Find/create room in MongoDB
├── If exists: Add user to members array
└── If new: Create room with user as first member
7. Return: {roomId, roomName, memberCount, members[]}
8. Socket.IO join: socket.join(roomName)
Creation Flow:
1. User clicks "Custom Room" on home
2. CustomRoom component loads
3. User clicks "Create Room"
4. POST /api/rooms/create
5. Generate unique 6-char code:
├── Loop until unique: Math.random().toString(36).substring(2,8).toUpperCase()
├── Check MongoDB: Room.findOne({code})
6. Create room: {roomName: `custom-${code}`, code, isCustomRoom: true}
7. Return: {roomId, roomName, code, memberCount: 0}
Joining Flow:
1. User enters URL: /chat/custom/ABC123
2. ChatRoute component loads
3. POST /api/rooms/join with {code: "ABC123"}
4. Find room by code (case-insensitive)
5. Return room info (without member details for privacy)
6. Show JoinRoom component for authentication
7. After auth: Join Socket.IO room by roomName
┌─────────────────────────────────────────────────────────────────┐
│ MESSAGE SENDING FLOW │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────┐
│ User Types Message │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ Input Validation │
│ - Not empty │
│ - ≤1000 chars │
│ - Rate limit (1s) │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ P2P Attempt First │
│ (WebRTC DataChannel)│
└───────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────┐ ┌─────────────┐
│ P2P Success │ │P2P │ │ P2P Fails │
│ Direct Send │ │Fails│ │ Server │
└─────────────────┘ └─────┘ │ Relay │
└─────────────┘
│
▼
┌────────────────────────────────────┐
│ Server Processing │
│ POST /api/messages/send/:roomName|
└────────────────────────────────────┘
Connection Establishment:
1. User joins Socket.IO room
2. Server emits "existing-room-users" with other users
3. For each existing user:
├── Create RTCPeerConnection with ICE servers
├── Create DataChannel named "chat"
├── Create offer: pc.createOffer()
├── Set local description
├── Send offer via Socket.IO: "webrtc-offer"
4. Receiving user:
├── Create RTCPeerConnection
├── Set remote description (offer)
├── Create answer: pc.createAnswer()
├── Send answer via Socket.IO: "webrtc-answer"
5. ICE candidate exchange:
├── Both sides: pc.onicecandidate → emit "webrtc-ice-candidate"
├── Add received candidates to peer connections
6. DataChannel setup:
├── dc.onopen: Connection ready for P2P messaging
├── dc.onmessage: Receive P2P messages
├── dc.onclose: Cleanup connection
Fallback Logic:
- If P2P fails after 10 seconds: Close connection, use server
- Network changes: Automatically attempt P2P reconnection
- Firewall/NAT issues: Seamless server relay fallback
Message Schema:
{
senderId: ObjectId (ref: Users),
room: String (default: "global-room"),
text: String,
reactions: [{
userId: ObjectId,
emoji: String,
createdAt: Date
}],
expiresAt: Date (31 days for messages, 40 days for rooms)
}
Cleanup System:
- MongoDB TTL indexes auto-delete expired messages/rooms
- Manual cleanup: DELETE /api/messages/cleanup
├── Keep only latest 1000 messages per room
├── Delete older messages in batches
- Reaction limits: One reaction per user per message
Room-Based Color Palettes:
Global Room Colors (15 colors):
┌─────────────────────────────────────────────────┐
│ #8b5cf6 #a855f7 #6366f1 #3b82f6 #0ea5e9 #60a5fa │
│ #d946ef #ec4899 #f43f5e #f97316 #f59e0b #fbbf24 │
│ #eab308 #84cc16 #22c55e │
└─────────────────────────────────────────────────┘
Theme: Purple → Violet → Blue → Indigo
Network Room Colors (15 colors):
┌─────────────────────────────────────────────────┐
│ #10b981 #14b8a6 #06b6d4 #34d399 #22c55e #84cc16 │
│ #0ea5e9 #60a5fa #3b82f6 #6366f1 #8b5cf6 #a855f7 │
│ #d946ef #ec4899 #f43f5e │
└─────────────────────────────────────────────────┘
Theme: Emerald → Teal → Cyan → Green
Custom Room Colors (15 colors):
┌─────────────────────────────────────────────────┐
│ #f43f5e #ec4899 #d946ef #e11d48 #f97316 #fbbf24 │
│ #f59e0b #84cc16 #22c55e #10b981 #06b6d4 #3b82f6 │
│ #6366f1 #8b5cf6 #a855f7 │
└─────────────────────────────────────────────────┘
Theme: Rose → Pink → Fuchsia
Mobile Optimizations:
- Viewport height: CSS custom property --vh
- Info button: Shows user details & room code on mobile
- Header layout: Logo acts as back button on mobile
- Touch-friendly: Larger buttons, swipe gestures
- Auto-focus: Input field focuses automatically on load
Desktop Features:
- Hover effects: Scale transforms, glow effects
- Gradient animations: CSS keyframe animations
- Modal dialogs: Documentation, room creation
- Native sharing: Web Share API with clipboard fallback
Server Events (io.on):
├── "connection": New client connects
├── "join": Client joins room
├── "leave": Client leaves room
├── "disconnect": Client disconnects
├── "webrtc-offer": WebRTC offer received
├── "webrtc-answer": WebRTC answer received
├── "webrtc-ice-candidate": ICE candidate received
Client Events (socket.emit):
├── "join": Join room
├── "leave": Leave room
├── "webrtc-offer": Send WebRTC offer
├── "webrtc-answer": Send WebRTC answer
├── "webrtc-ice-candidate": Send ICE candidate
Broadcast Events (io.to(room).emit):
├── "userJoined": New user joined room
├── "userLeft": User left/disconnected
├── "chatMessage": New message received
├── "message-reacted": Message reaction updated
├── "existing-room-users": Send existing users to new joiner
Processed Message IDs:
- Set<string> processedMessageIds (in-memory)
- Tracks both _id and tempId for each message
- Prevents duplicate rendering from P2P + Server paths
- Cleanup: Automatic garbage collection on component unmount
Info Button (Mobile Only):
- Position: Fixed top-right corner
- Content: Username, Room type/code, Member count
- Trigger: Click to show modal overlay
- Close: Click outside or X button
Header Layout:
- Desktop: Logo + Room info + Share + Info
- Mobile: Logo (back) + Room name + Info button
- Responsive breakpoints: Tailwind CSS classes
Input System:
- Auto-focus: textarea.focus() on component mount
- Auto-resize: Dynamic height based on content
- Character limit: 1000 chars with warning at 900
- Rate limiting: 1000ms throttle between sends
if (navigator.share) {
// Web Share API (Mobile browsers)
navigator.share({
title: \`Join \${roomName}\`,
text: \`Join my chat room: \${roomCode}\`,
url: window.location.href
});
} else {
// Clipboard fallback (Desktop/some mobile)
navigator.clipboard.writeText(window.location.href);
toast.success("Room link copied to clipboard!");
}
/ → Home (room selection)
/chat/global → Global room
/chat/network → Network room (IP-based)
/chat/custom/:code → Custom room (code-based)
jwt: JWT token (7 days expiry)
├── httpOnly: true (prevents XSS access)
├── secure: true (HTTPS only in production)
├── sameSite: "lax" (CSRF protection)
├── path: "/" (available site-wide)
anonymousUser: JSON string
├── {name, color, expiry}
├── expiry: Date.now() + 7 days
├── Auto-regenerates when expired
Legacy keys (auto-migrated):
├── anonymousUsername: string
├── userColor: string
Users:
├── _id, userName, password?, color, isAnonymous
├── expiresAt (TTL: 7 days anonymous, 1 year registered)
Messages:
├── senderId, room, text, reactions[], expiresAt
├── TTL: 31 days
Rooms:
├── roomName, code?, members[], createdByIp, isCustomRoom
├── TTL: 40 days
Backend (.env):
├── MONGODB_URI: MongoDB connection string
├── PORT: Server port (default: 3000)
├── JWT_SECRET: JWT signing key
├── NODE_ENV: development/production
Frontend (.env.*):
├── VITE_BACKEND_URL: API endpoint
├── Development: http://localhost:8000
├── Production: https://waves-c53a.onrender.com
Root package.json scripts:
├── build: Install deps + build frontend + install backend
├── start: Start backend (serves built frontend)
Frontend (Vite):
├── dev: Development server with HMR
├── build: Production build to dist/
├── preview: Preview production build
Backend (Node.js):
├── dev: nodemon with auto-restart
├── start: Production server
WebRTC P2P Fallback:
├── Timeout: 10 seconds for P2P connection
├── Auto-fallback: Server relay if P2P fails
├── Reconnection: Automatic P2P reattempts
Network Issues:
├── Socket.IO auto-reconnection
├── Message queuing during disconnects
├── Offline detection and user feedback
Authentication Errors:
├── Token expiry: Redirect to join room
├── Invalid token: Clear cookies, re-authenticate
├── Network errors: Retry with exponential backoff
Automatic Cleanup:
├── Keep latest 1000 messages per room
├── Delete older messages in batches
├── MongoDB TTL indexes for auto-expiry
Memory Management:
├── Processed message ID deduplication
├── Peer connection cleanup on disconnect
├── Component unmount cleanup
Rate Limiting:
├── 1000ms throttle between message sends
├── Character limits (1000 max)
├── Input validation before sending
1. Landing Page
├── Animated "Waves" title with glow effects
├── Three room type cards with hover animations
├── Documentation button (top-right)
└── Gradient background with subtle patterns
2. Room Selection
├── Global: Purple theme, instant access
├── Network: Green theme, IP-based auto-assignment
├── Custom: Pink theme, code generation/sharing
3. Authentication
├── Anonymous: Auto-generated name/color (localStorage)
├── Registered: Manual username/password creation
├── Color selection from room-specific palettes
4. Chat Interface
├── Real-time P2P messaging with server fallback
├── Mobile-responsive header with info modal
├── Auto-focus input field
├── Message reactions (one per user)
├── Share functionality with native API
5. Message Features
├── P2P priority with seamless server fallback
├── User color coding for message distinction
├── Timestamp display
├── Auto-scroll to latest messages
├── Character counter with warnings
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request