{"id":27926038,"url":"https://github.com/hamidfarmani/whatsapp-websocket-frontend-clone","last_synced_at":"2026-04-29T14:02:20.659Z","repository":{"id":291477570,"uuid":"977741673","full_name":"hamidfarmani/whatsapp-websocket-frontend-clone","owner":"hamidfarmani","description":"simple real-time chat application, mimicking some basic functionalities of WhatsApp, built using Next.js for the frontend and a Express server with Socket.IO for the backend communication","archived":false,"fork":false,"pushed_at":"2025-05-04T21:41:10.000Z","size":150,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-07T00:51:40.585Z","etag":null,"topics":["socket-io","websocket"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/hamidfarmani.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}},"created_at":"2025-05-04T21:40:01.000Z","updated_at":"2025-05-04T21:42:59.000Z","dependencies_parsed_at":"2025-05-04T22:43:45.920Z","dependency_job_id":null,"html_url":"https://github.com/hamidfarmani/whatsapp-websocket-frontend-clone","commit_stats":null,"previous_names":["hamidfarmani/whatsapp-websocket-frontend-clone"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hamidfarmani/whatsapp-websocket-frontend-clone","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamidfarmani%2Fwhatsapp-websocket-frontend-clone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamidfarmani%2Fwhatsapp-websocket-frontend-clone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamidfarmani%2Fwhatsapp-websocket-frontend-clone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamidfarmani%2Fwhatsapp-websocket-frontend-clone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hamidfarmani","download_url":"https://codeload.github.com/hamidfarmani/whatsapp-websocket-frontend-clone/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hamidfarmani%2Fwhatsapp-websocket-frontend-clone/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262570598,"owners_count":23330432,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["socket-io","websocket"],"created_at":"2025-05-07T00:51:07.918Z","updated_at":"2026-04-29T14:02:15.614Z","avatar_url":"https://github.com/hamidfarmani.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WhatsApp Clone Frontend with Socket.IO\n\nThis project is a simple real-time chat application, mimicking some basic functionalities of WhatsApp, built using Next.js for the frontend and a Node.js/Express server with Socket.IO for the backend communication.\n\n## Features\n\n- **Group Chat:** Users can join existing chat groups or create new ones by specifying a group name.\n- **Direct Messaging (DM):** Users can click on a member in the group list to initiate a private chat.\n- **Real-time Messaging:** Messages are sent and received in real-time within the active group or DM using WebSockets (via Socket.IO).\n- **User Identification:** Users must provide a username before joining a chat. Username uniqueness is checked on the server.\n- **User Presence:** Displays a list of users currently present in the _joined group_.\n- **Join/Leave Notifications:** System messages notify users within a group when someone joins or leaves.\n- **Typing Indicators:** Shows when other users in the current group or DM chat are typing.\n- **Emoji Support:** Users can add emojis to their messages using a built-in emoji picker.\n- **Chat History Persistence (Client-side):** Remembers the user's username and recently joined groups using browser `localStorage` for quick rejoining. (DM history is not persisted).\n- **Modern UI:** Styled using Shadcn/ui components and Tailwind CSS.\n- **Component-Based:** Frontend code refactored into smaller, reusable React components.\n\n## Technology Stack\n\n- **Frontend:**\n  - Next.js (v14+ with App Router)\n  - React\n  - TypeScript\n  - Socket.IO Client\n  - Shadcn/ui\n  - Tailwind CSS\n  - emoji-picker-react\n- **Backend:**\n  - Node.js\n  - Express\n  - Socket.IO Server\n  - CORS\n- **Package Manager:** pnpm\n\n## How it Works\n\n1.  **Connection:** The Next.js frontend establishes a single, stable WebSocket connection to the backend Socket.IO server on initial load, managed to handle React Strict Mode.\n2.  **Joining:**\n    - The user enters their desired username (saved to `localStorage`).\n    - The user can either click a button to rejoin a recent group (loaded from `localStorage`) or type a new group name and click \"Create \u0026 Join Group\".\n    - The frontend emits a `join` event to the server with the username and target group name.\n    - The server checks for username conflicts. If the username is taken, a `joinError` is emitted back.\n    - If OK, the server adds the user's socket to the specified group (room), stores user/group/socket associations, maps the username to the socket ID, and broadcasts an updated user list (`updateUserList` event) and a system message (`message` event with `type: 'system'`) to the group notifying others of the join.\n    - The frontend sets the `joinedGroup` state and sets the `activeChat` to the group, transitioning to the chat view. The group is added to the recent groups list in `localStorage`.\n3.  **Direct Messaging (DM):**\n    - Clicking a user in the sidebar (who isn't the current user) sets the `activeChat` state to `{ type: 'dm', id: targetUsername }`.\n    - The UI switches context, displaying the DM chat history (if any) and updating the header.\n4.  **Messaging (Group \u0026 DM):**\n    - When a user types a message and clicks send, the frontend checks the `activeChat` state.\n    - If it's a group chat, it emits a `sendMessage` event with the `group` ID and the message.\n    - If it's a DM, it emits a `sendMessage` event with the `recipientUsername` and the message.\n    - **Group Message:** The server receives the `sendMessage` with a `group` ID, looks up the group, and broadcasts the message (`message` event) to all _other_ clients in that group's room.\n    - **Direct Message:** The server receives the `sendMessage` with a `recipientUsername`, looks up the recipient's socket ID using the `usernameToSocketId` map. If found, it emits the message (`message` event, marked as `isPrivate: true`) _directly_ to that specific socket ID. It also emits the same message back to the _sender's_ socket ID so it appears in their chat history.\n    - Clients receive the `message` event. If `isPrivate` is true, the message is added to the corresponding DM state; otherwise, it's added to the group message state. Messages sent by the user are marked with `user: 'You'` for styling.\n5.  **Typing Indicators:**\n    - When the user types in the input, the frontend emits `startTyping` with either the `group` ID or `recipientUsername` based on `activeChat`.\n    - After a timeout (or when a message is sent/chat focus changes), `stopTyping` is emitted with the same context.\n    - **Group:** The server receives `startTyping` / `stopTyping` with a `group` ID, updates the user's typing status, and broadcasts `userTyping` / `userStoppedTyping` to _other_ users in the group room.\n    - **DM:** The server receives `startTyping` / `stopTyping` with a `recipientUsername`, looks up the recipient's socket ID, and emits `userTyping` / `userStoppedTyping` (marked as `isPrivate: true`) _directly_ to that recipient.\n    - The frontend listens for `userTyping` / `userStoppedTyping` and updates the relevant typing state (`typingUsers` for group, `dmTypingUsers` for DMs) to display the indicator in the header for the currently active chat.\n6.  **Leaving Group:**\n    - When a user clicks the \"Leave Group\" button (which leaves the _primary joined group_), the frontend emits a `leave` event with the username and `joinedGroup` name.\n    - The server removes the user/group association, ensures any typing indicators are cleared, broadcasts a system message (`message` event) notifying others of the departure, and broadcasts an updated user list (`updateUserList` event) to the remaining users in the group.\n    - The server makes the user's socket leave the Socket.IO room.\n    - The leaving user's frontend UI resets to the join screen.\n7.  **Disconnecting:**\n    - If a user disconnects (e.g., closes the browser tab), the server detects the `disconnect` event.\n    - It cleans up the user's state: removes them from the username mapping, clears any active typing indicators, and performs the `leave` logic for all groups the user was associated with (notifying others, updating user lists).\n\n### Architecture Diagram (Mermaid - Simplified Group + DM + Typing)\n\n```mermaid\nsequenceDiagram\n    participant Client A\n    participant Client B\n    participant Server\n\n    Client A-\u003e\u003eServer: Connect\n    Server--\u003e\u003eClient A: Connected\n    Client B-\u003e\u003eServer: Connect\n    Server--\u003e\u003eClient B: Connected\n\n    Client A-\u003e\u003eServer: emit('join', { username: 'Alice', group: 'Room1' })\n    Server-\u003e\u003eServer: Map Alice -\u003e socketA, Add Alice to Room1\n    Server--\u003e\u003eClient B: emit('message', system join msg)\n    Server--\u003e\u003eClient A: emit('updateUserList', ['Alice'])\n    Server--\u003e\u003eClient B: emit('updateUserList', ['Alice', 'Bob'])\n\n    Client B-\u003e\u003eServer: emit('join', { username: 'Bob', group: 'Room1' })\n    Server-\u003e\u003eServer: Map Bob -\u003e socketB, Add Bob to Room1\n    Server--\u003e\u003eClient A: emit('message', system join msg)\n    Server--\u003e\u003eClient A: emit('updateUserList', ['Alice', 'Bob'])\n    Server--\u003e\u003eClient B: emit('updateUserList', ['Alice', 'Bob'])\n\n    Client A-\u003e\u003eClient A: Click on Bob (Open DM)\n    Client A-\u003e\u003eServer: emit('startTyping', { recipientUsername: 'Bob' })\n    Server-\u003e\u003eServer: Find Bob -\u003e socketB\n    Server--\u003e\u003eClient B: emit('userTyping', { username: 'Alice', isPrivate: true })\n    Client B-\u003e\u003eClient B: Show typing indicator for Alice\n\n    Client A-\u003e\u003eServer: emit('sendMessage', { recipientUsername: 'Bob', message: {...} })\n    Server-\u003e\u003eServer: Find Bob -\u003e socketB\n    Server--\u003e\u003eClient B: emit('message', {..., isPrivate: true})\n    Server--\u003e\u003eClient A: emit('message', {..., isPrivate: true, user: 'You'}) // Echo back to sender\n    Client B-\u003e\u003eClient B: Add DM from Alice\n    Client A-\u003e\u003eClient A: Add own DM to Bob\n\n    Client B-\u003e\u003eClient B: Switch back to Group view\n    Client B-\u003e\u003eServer: emit('startTyping', { group: 'Room1' })\n    Server-\u003e\u003eServer: Mark Bob as typing in Room1\n    Server--\u003e\u003eClient A: emit('userTyping', { username: 'Bob', group: 'Room1' })\n    Client A-\u003e\u003eClient A: Show typing indicator for Bob (in Group view)\n\n    Client B-\u003e\u003eServer: emit('sendMessage', { group: 'Room1', message: {...} })\n    Server-\u003e\u003eServer: Bob stopped typing in Room1\n    Server--\u003e\u003eClient A: emit('userStoppedTyping', { username: 'Bob', group: 'Room1' })\n    Server--\u003e\u003eClient A: emit('message', { user: 'Bob', text: '...' })\n    Client A-\u003e\u003eClient A: Add group message from Bob, clear typing indicator\n\n```\n\n## Project Structure\n\n```\n/whatsapp-websocket-frontend-clone\n|-- /public                 # Static assets for Next.js\n|-- /server                 # Backend Node.js server\n|   |-- node_modules/\n|   |-- server.js           # Express + Socket.IO logic\n|   |-- package.json        # Backend dependencies\n|   |-- pnpm-lock.yaml\n|-- /src                    # Frontend Next.js source\n|   |-- /app                # App Router pages\n|   |   |-- globals.css     # Tailwind global styles\n|   |   |-- layout.tsx      # Root layout\n|   |   |-- page.tsx        # Main page component (state management, layout)\n|   |-- /components\n|   |   |-- /chat           # Chat specific components\n|   |   |   |-- ChatHeader.tsx\n|   |   |   |-- ChatSidebar.tsx\n|   |   |   |-- Icons.tsx\n|   |   |   |-- JoinChatForm.tsx\n|   |   |   |-- MessageBubble.tsx\n|   |   |   |-- MessageInput.tsx\n|   |   |   |-- MessageList.tsx\n|   |   |-- /ui             # Shadcn UI components (auto-generated)\n|   |-- /lib                # Utility functions\n|   |   |-- utils.ts        # Shadcn utilities (e.g., cn)\n|-- .env.local              # (Optional) Environment variables\n|-- .eslintrc.json\n|-- .gitignore\n|-- components.json         # Shadcn configuration\n|-- next-env.d.ts\n|-- next.config.mjs         # Next.js configuration\n|-- package.json            # Frontend dependencies\n|-- pnpm-lock.yaml\n|-- postcss.config.mjs\n|-- README.md               # This file\n|-- tailwind.config.ts      # Tailwind configuration\n|-- tsconfig.json\n```\n\n## Getting Started\n\n### Prerequisites\n\n- Node.js (v18 or later recommended)\n- pnpm (or npm/yarn, but commands below use pnpm)\n\n### Installation\n\n1.  **Clone the repository:**\n    ```bash\n    git clone https://github.com/hamidfarmani/whatsapp-websocket-frontend-clone.git\n    cd whatsapp-websocket-frontend-clone\n    ```\n2.  **Install Frontend Dependencies:**\n    ```bash\n    pnpm install\n    ```\n3.  **Install Backend Dependencies:**\n    ```bash\n    cd server\n    pnpm install\n    cd ..\n    ```\n\n### Running the Application\n\n1.  **Start the Backend Server:**\n\n    - Open a terminal window.\n    - Navigate to the server directory:\n      ```bash\n      cd server\n      ```\n    - Run the server:\n      ```bash\n      node server.js\n      ```\n    - Keep this terminal running. You should see output like `Server listening on *:3001`.\n\n2.  **Start the Frontend Development Server:**\n\n    - Open a _separate_ terminal window.\n    - Make sure you are in the project root directory (`whatsapp-websocket-frontend-clone`).\n    - Run the Next.js development server:\n      ```bash\n      pnpm dev\n      ```\n\n3.  **Access the Application:**\n    - Open your web browser and navigate to `http://localhost:3000` (or the port specified in the `pnpm dev` output).\n    - Open a second browser tab/window to the same address to simulate multiple users.\n\n## Potential Improvements\n\n- Read receipts.\n- User profile pictures (beyond initials).\n- Persistent message history (database integration for groups and DMs).\n- Notifications for new DMs when not actively viewing the DM chat.\n- More robust state management (e.g., Zustand, Redux Toolkit).\n- Error handling for failed message sends, server issues.\n- Deployment configuration.\n\n## Core Socket.IO Concepts\n\nThis project relies heavily on Socket.IO for real-time communication between the React frontend and the Node.js backend. Here's a breakdown of the key concepts used:\n\n### `io` vs. `socket`\n\n- **`io` (Server Instance):** Represents the main Socket.IO server instance on the backend (`server/server.js`). It manages _all_ client connections and can broadcast messages globally or to specific rooms. Created via `new Server(httpServer, ...)`.\n- **`socket` (Client Connection):** Represents a _single, specific_ client connection.\n  - **Server-side:** Inside `io.on('connection', (socket) =\u003e { ... })`, `socket` is the object for the newly connected client. Used to listen to events _from_ this client and send messages _back_ to this client.\n  - **Client-side:** In the frontend (`src/app/page.tsx`), the `socket` instance (e.g., `socketInstance`) represents _that client's own connection_ to the server. Used to send events _to_ the server and listen for events _from_ the server.\n\n### Event Handling\n\n- **`io.on('connection', callback)` (Server-side):**\n\n  - The primary listener on the server. Triggered when a _new client connects_.\n  - The `callback` function receives the `socket` object for that specific client, allowing you to set up individual event listeners for them.\n  - _Example:_ `server/server.js` uses this to know when a user connects and then sets up listeners like `socket.on('join', ...)` for that user.\n\n- **`socket.on('eventName', callback)` (Client \u0026 Server-side):**\n\n  - Listens for a specific, _custom_ event named `eventName`.\n  - **Client-side:** The client must _actively register_ these listeners to tell its `socket` instance which events from the server it cares about (e.g., `'message'`, `'updateUserList'`).\n  - **Server-side:** Listens for events emitted _from a specific client_.\n  - When the other side `emit`s that event, the corresponding `callback` function on the listening side executes.\n  - **Crucially, any `data` sent by the server via `emit('eventName', data)` becomes the argument passed to the client's `callback` function.**\n  - _Server Example:_ `socket.on('sendMessage', ...)` listens for a client sending a message.\n  - _Client Example:_ `socket.on('message', handleNewMessage)` listens for the server broadcasting a new message. When the server emits `message` with message data, the `handleNewMessage` function on the client runs, receiving the message data as its argument, and updates the React state to display it. Similarly, `socket.on('updateUserList', handleUserListUpdate)` listens for user list updates.\n\n- **`emit('eventName', data)` (Client \u0026 Server-side):**\n  - Sends an event named `eventName`, optionally with `data`.\n  - **`socket.emit(...)`:** Sends _only_ to the other side of that specific connection (server to one client, or client to server).\n    - _Example:_ Server sending a `'joinError'` only to the client who failed to join. Client sending a `'startTyping'` event to the server.\n  - **`io.emit(...)` (Server-side):** Sends to _all connected clients_. (Broadcast).\n    - _Example:_ Announcing server maintenance to everyone.\n\n### Rooms (Groups)\n\nRooms allow the server to manage and target specific groups of clients.\n\n- **`socket.join('roomName')` (Server-side):**\n\n  - Makes the specific client (`socket`) join the logical room named `roomName`.\n  - _Example:_ A user joining a specific chat group like 'General' (`socket.join('General')`).\n\n- **`socket.leave('roomName')` (Server-side):**\n\n  - Makes the specific client (`socket`) leave the room.\n  - _Example:_ A user leaving the 'General' chat group.\n\n- **`socket.to('roomName').emit(...)` (Server-side):**\n\n  - Sends an event to _all clients in `roomName`_ **except** the sender (`socket`).\n  - _Example:_ Sending a chat message to everyone else in the 'General' room after receiving it from one user.\n\n- **`io.to('roomName').emit(...)` (Server-side):**\n  - Sends an event to _all clients in `roomName`_, **including** the sender (if they are in the room).\n  - _Example:_ Sending an updated user list for the 'General' room to everyone in that room, including the user whose joining/leaving triggered the update.\n\n## Socket.IO Admin Dashboard\n\nYou can monitor the WebSocket server activity using the Socket.IO Admin UI.\n\n1.  Ensure the server is running.\n2.  Open your web browser and navigate to [https://admin.socket.io/](https://admin.socket.io/).\n3.  Enter the server address: `http://localhost:3001` (or your server's address if different).\n4.  Since authentication is disabled (`auth: false` in `server.js`), you don't need a username or password.\n\nFrom the dashboard, you can view connected clients (sockets), inspect emitted events, view server statistics, and much more.\n\n### STOMP WebSocket Client (`/stomp` page)\n\nIn addition to the primary Socket.IO implementation, this project includes a separate chat client example using the STOMP (Simple Text Oriented Messaging Protocol) protocol over WebSockets. This approach is common in enterprise environments, often used with message brokers like RabbitMQ or ActiveMQ, and frameworks like Spring Boot.\n\n- **Technology:** Uses `@stomp/stompjs` and `sockjs-client` on the frontend. `SockJS` provides a WebSocket-like object with fallbacks for older browsers, while `stompjs` handles the STOMP protocol specifics on top of the connection. This contrasts with the Socket.IO client (`socket.io-client`), which uses its own protocol and features like automatic reconnection and multiplexing.\n- **Functionality \u0026 Key Differences:**\n  - **Connection:** Connects to a specific URL (`http://localhost:8080/ws` by default) expected to be a STOMP endpoint, usually provided by a message broker or a backend framework integration (like Spring Boot with WebSocket support). Socket.IO connects directly to the `server.js` endpoint (`http://localhost:3001`).\n  - **Communication Model:** STOMP is message-oriented and typically relies on a central message broker. Clients `SUBSCRIBE` to named destinations (topics or queues, e.g., `/topic/public`) to receive messages and `SEND` messages to application-specific destinations (e.g., `/app/chat.sendMessage`). The broker routes messages based on these destinations. Socket.IO uses a more direct event-based model where clients emit named events (`sendMessage`, `join`) to the server, and the server broadcasts or emits events back to clients or specific rooms.\n  - **Broker vs. Direct Server:** The STOMP client assumes an intermediary broker managing subscriptions and message routing. The Socket.IO client communicates directly with the custom logic in the Node.js `server.js` file.\n  - **Message Structure:** STOMP messages often contain headers and a body (typically JSON in this example). The `stompjs` library parses these. Socket.IO messages are typically simple data payloads (like JavaScript objects) associated with an event name.\n  - **Operations:** Instead of Socket.IO's `emit` and `on`, STOMP uses commands like `CONNECT`, `SUBSCRIBE`, `SEND`, `DISCONNECT`. The `@stomp/stompjs` client provides methods like `client.subscribe()`, `client.publish()`, and `client.activate()`.\n  - **Example Flow:**\n    1. `client.activate()`: Connects to the broker via SockJS/WebSocket.\n    2. `client.subscribe('/topic/public', callback)`: Tells the broker to send messages from the `/topic/public` destination to the client's `callback` function.\n    3. `client.publish({ destination: '/app/chat.addUser', body: ... })`: Sends a message to the `/app/chat.addUser` destination for the backend application to process (e.g., register the user).\n    4. `client.publish({ destination: '/app/chat.sendMessage', body: ... })`: Sends a chat message for the backend to process and likely broadcast to `/topic/public`.\n- **Usage:** Navigate to `/stomp` in the application. **Requires a compatible backend STOMP broker** (like a Spring Boot application with WebSocket and STOMP dependencies configured) running and accessible at the specified address (`http://localhost:8080/ws`).\n- **Note:** This STOMP client is **functionally separate** from the main Socket.IO chat. It demonstrates a different WebSocket communication pattern and **will not work** with the included Node.js/Socket.IO backend (`server/server.js`). It needs its own dedicated STOMP-compatible backend.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhamidfarmani%2Fwhatsapp-websocket-frontend-clone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhamidfarmani%2Fwhatsapp-websocket-frontend-clone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhamidfarmani%2Fwhatsapp-websocket-frontend-clone/lists"}