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.
Server Sent Events Demo
<!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>
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}`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment