Skip to content

Instantly share code, notes, and snippets.

@MuhammadSawalhy
Created September 24, 2024 19:51
Show Gist options
  • Save MuhammadSawalhy/cc8f5dc0c83059ff9bfa4ed196ceca19 to your computer and use it in GitHub Desktop.
Save MuhammadSawalhy/cc8f5dc0c83059ff9bfa4ed196ceca19 to your computer and use it in GitHub Desktop.

Revisions

  1. MuhammadSawalhy created this gist Sep 24, 2024.
    97 changes: 97 additions & 0 deletions chat.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,97 @@
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat App</title>
    </head>

    <body>
    <h1>Group Chat</h1>
    <div id="chat-box" style="height: 300px; overflow-y: auto; border: 1px solid black; padding: 10px;"></div>
    <div style="margin-top: 30px;">
    <p id="username"></p>
    <form style="margin-top:10px" id="msg-form">
    <input type="text" id="message-input" placeholder="Type your message">
    <button>Send</button>
    </form>
    </div>
    <script>
    // Function to generate a random readable name
    const generateRandomName = () => {
    const adjectives = ["Brave", "Clever", "Witty", "Jolly", "Swift", "Bold", "Lucky"];
    const animals = ["Lion", "Eagle", "Fox", "Bear", "Shark", "Wolf", "Hawk"];
    const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
    const animal = animals[Math.floor(Math.random() * animals.length)];
    return `${adjective} ${animal}`;
    };

    // Get or generate a readable username
    let username = localStorage.getItem("username");
    if (!username) {
    username = generateRandomName();
    localStorage.setItem("username", username);
    }

    // Display the username
    const usernameElement = document.getElementById("username");
    usernameElement.textContent = `Hello, ${username}!`;

    // EventSource for SSE
    const eventSource = new EventSource("/stream");

    // Handle incoming messages
    eventSource.onmessage = function (event) {
    const message = JSON.parse(event.data);
    displayMessage(message);
    };

    // Display messages in the chat box
    function displayMessage(message) {
    // if the message is from the current user, align right
    if (message.username === username) {
    const chatBox = document.getElementById("chat-box");
    const messageElem = document.createElement("p");
    const time = new Date(message.timestamp).toLocaleTimeString();
    messageElem.textContent = `[${time}] ${message.username}: ${message.message}`;
    messageElem.style.textAlign = "right";
    chatBox.appendChild(messageElem);
    chatBox.scrollTop = chatBox.scrollHeight;
    return;
    }
    const chatBox = document.getElementById("chat-box");
    const messageElem = document.createElement("p");
    const time = new Date(message.timestamp).toLocaleTimeString();
    messageElem.textContent = `[${time}] ${message.username}: ${message.message}`;
    chatBox.appendChild(messageElem);
    chatBox.scrollTop = chatBox.scrollHeight;
    }

    // Send message to the server
    document.getElementById("msg-form").addEventListener("submit", (e) => {
    e.preventDefault();
    const messageInput = document.getElementById("message-input");
    const message = messageInput.value;

    if (!message.trim()) return;

    // POST the message to the server
    fetch("/message", {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    },
    body: JSON.stringify({ username, message }),
    })
    .then(response => response.json())
    .then(data => {
    if (data.status === "Message sent.") {
    messageInput.value = ""; // Clear input after sending
    }
    });
    });
    </script>
    </body>

    </html>
    83 changes: 83 additions & 0 deletions chat.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    import express from "express";
    import type { Request, Response } from "express";
    import { v4 as uuidv4 } from "uuid";

    const app = express();
    const PORT = 3000;

    app.use(express.json());

    type Message = {
    id: string;
    username: string;
    message: string;
    timestamp: number;
    };

    // In-memory message store
    const messages: Message[] = [];

    // List of connected clients
    let clients: Response[] = [];

    // SSE connection route
    app.get("/stream", (req, res) => {
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("Connection", "keep-alive");

    // use Last-Event-ID to send the lost messages only
    const lastId = req.headers['Last-Event-ID'];
    if (lastId) {
    const startIndex = messages.findIndex(msg => msg.id === lastId);
    for (const msg of messages.slice(startIndex + 1)) sendSSE(res, msg);
    } else {
    // Send all previous messages to the new client
    for (const msg of messages) sendSSE(res, msg);
    }

    clients.push(res);

    // Remove client when the connection closes
    req.on("close", () => {
    clients = clients.filter((client) => client !== res);
    });
    });

    // POST new message
    app.post("/message", (req: Request, res: Response) => {
    const { username, message } = req.body;

    // Validate request
    if (!username || !message) {
    return res.status(400).json({ error: "Username and message are required." });
    }

    const newMessage: Message = {
    id: uuidv4(), // Generate unique ID for each message
    username,
    message,
    timestamp: Date.now(),
    };

    messages.push(newMessage); // Store the message in-memory

    // Broadcast message to all connected clients
    for (const clientRes of clients) sendSSE(clientRes, newMessage);

    return res.status(201).json({ status: "Message sent." });
    });

    // Helper function to send SSE
    function sendSSE(res: Response, message: Message) {
    res.write(`data: ${JSON.stringify(message)}\n\n`);
    }

    app.get("/", (req: Request, res: Response) => {
    res.sendFile("./chat.html", { root: __dirname });
    });

    // Start server
    app.listen(PORT, () => {
    console.log(`Server is running at http://localhost:${PORT}`);
    });