JavaScript Real-Time Applications: WebSockets, Server-Sent Events, and Live Updates
Build real-time JavaScript applications with WebSockets, Server-Sent Events, WebRTC, and live data synchronization. Master real-time communication patterns.
Real-time applications provide instant data updates and interactive experiences. This comprehensive guide covers WebSockets, Server-Sent Events, WebRTC, and modern techniques for building responsive, live applications.
WebSocket Communication
WebSocket Client Implementation
// Advanced WebSocket Client with Reconnection and State Management
class WebSocketClient {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnectInterval: 1000,
maxReconnectInterval: 30000,
reconnectDecay: 1.5,
timeoutInterval: 2000,
maxReconnectAttempts: null,
binaryType: 'blob',
protocols: [],
...options,
};
this.ws = null;
this.reconnectAttempts = 0;
this.readyState = WebSocket.CONNECTING;
this.url = url;
this.protocols = this.options.protocols;
this.listeners = {
open: [],
close: [],
message: [],
error: [],
reconnect: [],
maxReconnectAttemptsReached: [],
};
this.messageQueue = [];
this.isIntentionallyClosed = false;
this.timerID = null;
this.connect();
}
connect() {
try {
this.ws = new WebSocket(this.url, this.protocols);
this.ws.binaryType = this.options.binaryType;
this.ws.onopen = (event) => {
this.onOpen(event);
};
this.ws.onclose = (event) => {
this.onClose(event);
};
this.ws.onmessage = (event) => {
this.onMessage(event);
};
this.ws.onerror = (event) => {
this.onError(event);
};
} catch (error) {
this.onError(error);
}
}
onOpen(event) {
this.readyState = WebSocket.OPEN;
this.reconnectAttempts = 0;
// Send queued messages
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.send(message);
}
this.emit('open', event);
}
onClose(event) {
this.readyState = WebSocket.CLOSED;
if (!this.isIntentionallyClosed) {
this.emit('close', event);
this.attemptReconnect();
}
}
onMessage(event) {
let data = event.data;
// Try to parse JSON
try {
data = JSON.parse(event.data);
} catch (e) {
// Keep original data if not JSON
}
this.emit('message', {
data,
originalEvent: event,
timestamp: Date.now(),
});
}
onError(event) {
this.emit('error', event);
}
attemptReconnect() {
if (
this.options.maxReconnectAttempts &&
this.reconnectAttempts >= this.options.maxReconnectAttempts
) {
this.emit('maxReconnectAttemptsReached');
return;
}
const timeout =
this.options.reconnectInterval *
Math.pow(this.options.reconnectDecay, this.reconnectAttempts);
const maxTimeout = Math.min(timeout, this.options.maxReconnectInterval);
this.timerID = setTimeout(() => {
this.reconnectAttempts++;
this.emit('reconnect', {
attempt: this.reconnectAttempts,
maxAttempts: this.options.maxReconnectAttempts,
});
this.connect();
}, maxTimeout);
}
send(data) {
if (this.readyState === WebSocket.OPEN) {
const message = typeof data === 'object' ? JSON.stringify(data) : data;
this.ws.send(message);
} else {
// Queue message for later
this.messageQueue.push(data);
}
}
close(code = 1000, reason = '') {
this.isIntentionallyClosed = true;
if (this.timerID) {
clearTimeout(this.timerID);
this.timerID = null;
}
if (this.ws) {
this.ws.close(code, reason);
}
}
// Event listener management
addEventListener(type, listener) {
if (this.listeners[type]) {
this.listeners[type].push(listener);
}
}
removeEventListener(type, listener) {
if (this.listeners[type]) {
const index = this.listeners[type].indexOf(listener);
if (index > -1) {
this.listeners[type].splice(index, 1);
}
}
}
emit(type, data) {
if (this.listeners[type]) {
this.listeners[type].forEach((listener) => {
try {
listener(data);
} catch (error) {
console.error('Error in event listener:', error);
}
});
}
}
// Convenience methods
on(type, listener) {
this.addEventListener(type, listener);
return this;
}
off(type, listener) {
this.removeEventListener(type, listener);
return this;
}
// Connection state
get state() {
return this.readyState;
}
get isConnected() {
return this.readyState === WebSocket.OPEN;
}
get isConnecting() {
return this.readyState === WebSocket.CONNECTING;
}
get isClosed() {
return this.readyState === WebSocket.CLOSED;
}
}
// WebSocket Server Implementation (Node.js style)
class WebSocketServer {
constructor(options = {}) {
this.options = {
port: 8080,
host: 'localhost',
path: '/',
maxClients: 100,
heartbeatInterval: 30000,
...options,
};
this.clients = new Set();
this.rooms = new Map();
this.middleware = [];
this.routes = new Map();
this.messageHandlers = new Map();
this.setupDefaultHandlers();
}
setupDefaultHandlers() {
this.on('connection', (client) => {
console.log(`Client connected: ${client.id}`);
client.on('close', () => {
console.log(`Client disconnected: ${client.id}`);
this.clients.delete(client);
this.leaveAllRooms(client);
});
client.on('error', (error) => {
console.error(`Client error ${client.id}:`, error);
});
});
// Built-in message handlers
this.handleMessage('ping', (client, data) => {
client.send({ type: 'pong', timestamp: Date.now() });
});
this.handleMessage('join-room', (client, data) => {
this.joinRoom(client, data.room);
});
this.handleMessage('leave-room', (client, data) => {
this.leaveRoom(client, data.room);
});
this.handleMessage('room-message', (client, data) => {
this.broadcastToRoom(data.room, data.message, client);
});
}
// Middleware system
use(middleware) {
this.middleware.push(middleware);
}
// Message handler registration
handleMessage(type, handler) {
this.messageHandlers.set(type, handler);
}
on(event, handler) {
if (!this.eventHandlers) {
this.eventHandlers = new Map();
}
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, []);
}
this.eventHandlers.get(event).push(handler);
}
emit(event, ...args) {
if (this.eventHandlers && this.eventHandlers.has(event)) {
this.eventHandlers.get(event).forEach((handler) => {
try {
handler(...args);
} catch (error) {
console.error('Error in event handler:', error);
}
});
}
}
// Client management
addClient(ws, request) {
const client = {
id: this.generateClientId(),
ws,
request,
rooms: new Set(),
metadata: {},
connectedAt: Date.now(),
lastPing: Date.now(),
};
// Add event forwarding
client.send = (data) => {
if (ws.readyState === WebSocket.OPEN) {
const message = typeof data === 'object' ? JSON.stringify(data) : data;
ws.send(message);
}
};
client.on = (event, handler) => {
ws.addEventListener(event, handler);
};
client.close = (code, reason) => {
ws.close(code, reason);
};
// Apply middleware
for (const middleware of this.middleware) {
try {
middleware(client, request);
} catch (error) {
console.error('Middleware error:', error);
}
}
this.clients.add(client);
// Setup message handling
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
this.handleClientMessage(client, message);
} catch (error) {
console.error('Invalid message format:', error);
}
});
ws.on('close', () => {
this.emit('disconnection', client);
});
ws.on('error', (error) => {
this.emit('error', client, error);
});
this.emit('connection', client);
return client;
}
handleClientMessage(client, message) {
client.lastPing = Date.now();
const { type, data } = message;
if (this.messageHandlers.has(type)) {
try {
this.messageHandlers.get(type)(client, data, message);
} catch (error) {
console.error(`Error handling message type ${type}:`, error);
client.send({
type: 'error',
message: 'Internal server error',
originalType: type,
});
}
} else {
console.warn(`Unknown message type: ${type}`);
client.send({
type: 'error',
message: `Unknown message type: ${type}`,
originalType: type,
});
}
}
// Room management
joinRoom(client, roomName) {
if (!this.rooms.has(roomName)) {
this.rooms.set(roomName, new Set());
}
this.rooms.get(roomName).add(client);
client.rooms.add(roomName);
client.send({
type: 'room-joined',
room: roomName,
memberCount: this.rooms.get(roomName).size,
});
// Notify other room members
this.broadcastToRoom(
roomName,
{
type: 'member-joined',
clientId: client.id,
memberCount: this.rooms.get(roomName).size,
},
client
);
}
leaveRoom(client, roomName) {
if (this.rooms.has(roomName)) {
this.rooms.get(roomName).delete(client);
client.rooms.delete(roomName);
const room = this.rooms.get(roomName);
if (room.size === 0) {
this.rooms.delete(roomName);
} else {
// Notify remaining members
this.broadcastToRoom(roomName, {
type: 'member-left',
clientId: client.id,
memberCount: room.size,
});
}
client.send({
type: 'room-left',
room: roomName,
});
}
}
leaveAllRooms(client) {
client.rooms.forEach((roomName) => {
this.leaveRoom(client, roomName);
});
}
// Broadcasting
broadcast(message, excludeClient = null) {
this.clients.forEach((client) => {
if (client !== excludeClient && client.ws.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
broadcastToRoom(roomName, message, excludeClient = null) {
if (this.rooms.has(roomName)) {
this.rooms.get(roomName).forEach((client) => {
if (
client !== excludeClient &&
client.ws.readyState === WebSocket.OPEN
) {
client.send(message);
}
});
}
}
// Heartbeat system
startHeartbeat() {
setInterval(() => {
const now = Date.now();
this.clients.forEach((client) => {
const timeSinceLastPing = now - client.lastPing;
if (timeSinceLastPing > this.options.heartbeatInterval * 2) {
// Client appears dead
console.log(`Removing dead client: ${client.id}`);
client.close(1001, 'Client timeout');
this.clients.delete(client);
} else if (timeSinceLastPing > this.options.heartbeatInterval) {
// Send ping
client.send({ type: 'ping', timestamp: now });
}
});
}, this.options.heartbeatInterval);
}
// Utilities
generateClientId() {
return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
getStats() {
return {
clientCount: this.clients.size,
roomCount: this.rooms.size,
rooms: Array.from(this.rooms.entries()).map(([name, clients]) => ({
name,
memberCount: clients.size,
})),
};
}
getClient(clientId) {
return Array.from(this.clients).find((client) => client.id === clientId);
}
getRoomMembers(roomName) {
return this.rooms.has(roomName) ? Array.from(this.rooms.get(roomName)) : [];
}
}
// Real-time Chat Application Example
class RealtimeChat {
constructor(containerId, wsUrl) {
this.container = document.getElementById(containerId);
this.ws = new WebSocketClient(wsUrl);
this.currentRoom = null;
this.messages = [];
this.users = new Map();
this.setupUI();
this.setupWebSocket();
}
setupUI() {
this.container.innerHTML = `
<div class="chat-container">
<div class="chat-sidebar">
<div class="user-info">
<input type="text" id="username" placeholder="Enter username" />
<button id="connect-btn">Connect</button>
</div>
<div class="rooms">
<h3>Rooms</h3>
<div id="room-list"></div>
<input type="text" id="room-input" placeholder="Room name" />
<button id="join-room-btn">Join Room</button>
</div>
<div class="users">
<h3>Online Users</h3>
<div id="user-list"></div>
</div>
</div>
<div class="chat-main">
<div class="chat-header">
<h2 id="current-room">Select a room</h2>
<button id="leave-room-btn" style="display: none;">Leave Room</button>
</div>
<div id="message-list" class="message-list"></div>
<div class="message-input">
<input type="text" id="message-input" placeholder="Type a message..." />
<button id="send-btn">Send</button>
</div>
</div>
</div>
`;
this.setupEventListeners();
}
setupEventListeners() {
document.getElementById('connect-btn').onclick = () => this.connect();
document.getElementById('join-room-btn').onclick = () => this.joinRoom();
document.getElementById('leave-room-btn').onclick = () => this.leaveRoom();
document.getElementById('send-btn').onclick = () => this.sendMessage();
document.getElementById('message-input').onkeypress = (e) => {
if (e.key === 'Enter') this.sendMessage();
};
document.getElementById('room-input').onkeypress = (e) => {
if (e.key === 'Enter') this.joinRoom();
};
}
setupWebSocket() {
this.ws.on('open', () => {
this.addSystemMessage('Connected to server');
});
this.ws.on('close', () => {
this.addSystemMessage('Disconnected from server');
});
this.ws.on('error', (error) => {
this.addSystemMessage('Connection error: ' + error.message);
});
this.ws.on('reconnect', (data) => {
this.addSystemMessage(`Reconnecting... (attempt ${data.attempt})`);
});
this.ws.on('message', (event) => {
this.handleMessage(event.data);
});
}
handleMessage(message) {
switch (message.type) {
case 'room-joined':
this.onRoomJoined(message);
break;
case 'room-left':
this.onRoomLeft(message);
break;
case 'member-joined':
this.onMemberJoined(message);
break;
case 'member-left':
this.onMemberLeft(message);
break;
case 'chat-message':
this.onChatMessage(message);
break;
case 'user-list':
this.onUserList(message);
break;
case 'pong':
// Handle server pong
break;
default:
console.log('Unknown message type:', message.type);
}
}
connect() {
const username = document.getElementById('username').value.trim();
if (!username) {
alert('Please enter a username');
return;
}
this.username = username;
this.ws.send({
type: 'set-username',
username: username,
});
document.getElementById('connect-btn').disabled = true;
}
joinRoom() {
const roomName = document.getElementById('room-input').value.trim();
if (!roomName) return;
if (this.currentRoom) {
this.leaveRoom();
}
this.ws.send({
type: 'join-room',
room: roomName,
});
document.getElementById('room-input').value = '';
}
leaveRoom() {
if (!this.currentRoom) return;
this.ws.send({
type: 'leave-room',
room: this.currentRoom,
});
}
sendMessage() {
const input = document.getElementById('message-input');
const message = input.value.trim();
if (!message || !this.currentRoom) return;
this.ws.send({
type: 'room-message',
room: this.currentRoom,
message: {
text: message,
username: this.username,
timestamp: Date.now(),
},
});
input.value = '';
}
onRoomJoined(data) {
this.currentRoom = data.room;
document.getElementById('current-room').textContent = data.room;
document.getElementById('leave-room-btn').style.display = 'block';
this.addSystemMessage(
`Joined room: ${data.room} (${data.memberCount} members)`
);
this.clearMessages();
}
onRoomLeft(data) {
this.currentRoom = null;
document.getElementById('current-room').textContent = 'Select a room';
document.getElementById('leave-room-btn').style.display = 'none';
this.addSystemMessage(`Left room: ${data.room}`);
this.clearMessages();
}
onMemberJoined(data) {
this.addSystemMessage(
`${data.clientId} joined the room (${data.memberCount} members)`
);
}
onMemberLeft(data) {
this.addSystemMessage(
`${data.clientId} left the room (${data.memberCount} members)`
);
}
onChatMessage(data) {
this.addChatMessage(data.message);
}
onUserList(data) {
const userList = document.getElementById('user-list');
userList.innerHTML = data.users
.map((user) => `<div class="user">${user}</div>`)
.join('');
}
addChatMessage(message) {
const messageList = document.getElementById('message-list');
const messageElement = document.createElement('div');
messageElement.className = 'message';
const time = new Date(message.timestamp).toLocaleTimeString();
messageElement.innerHTML = `
<div class="message-header">
<span class="username">${message.username}</span>
<span class="timestamp">${time}</span>
</div>
<div class="message-text">${this.escapeHtml(message.text)}</div>
`;
messageList.appendChild(messageElement);
messageList.scrollTop = messageList.scrollHeight;
}
addSystemMessage(text) {
const messageList = document.getElementById('message-list');
const messageElement = document.createElement('div');
messageElement.className = 'system-message';
messageElement.textContent = text;
messageList.appendChild(messageElement);
messageList.scrollTop = messageList.scrollHeight;
}
clearMessages() {
document.getElementById('message-list').innerHTML = '';
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Usage
const chat = new RealtimeChat('chat-app', 'ws://localhost:8080');
Server-Sent Events (SSE)
SSE Implementation for Live Updates
// Server-Sent Events Client
class SSEClient {
constructor(url, options = {}) {
this.url = url;
this.options = {
withCredentials: false,
reconnectTime: 1000,
maxReconnectTime: 30000,
reconnectDecay: 1.5,
maxReconnectAttempts: null,
headers: {},
...options,
};
this.eventSource = null;
this.reconnectAttempts = 0;
this.listeners = new Map();
this.reconnectTimeoutId = null;
this.isIntentionallyClosed = false;
this.connect();
}
connect() {
if (this.eventSource) {
this.eventSource.close();
}
const url = new URL(this.url);
// Add headers as query parameters (since EventSource doesn't support custom headers)
Object.entries(this.options.headers).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
this.eventSource = new EventSource(url.toString(), {
withCredentials: this.options.withCredentials,
});
this.eventSource.onopen = (event) => {
this.reconnectAttempts = 0;
this.emit('open', event);
};
this.eventSource.onerror = (event) => {
this.emit('error', event);
if (!this.isIntentionallyClosed) {
this.attemptReconnect();
}
};
this.eventSource.onmessage = (event) => {
this.handleMessage(event);
};
// Setup custom event listeners
this.listeners.forEach((handlers, eventType) => {
this.eventSource.addEventListener(eventType, (event) => {
handlers.forEach((handler) => {
try {
handler(event);
} catch (error) {
console.error('Error in SSE event handler:', error);
}
});
});
});
}
handleMessage(event) {
let data = event.data;
try {
data = JSON.parse(event.data);
} catch (e) {
// Keep original data if not JSON
}
this.emit('message', {
data,
id: event.lastEventId,
type: event.type,
originalEvent: event,
});
}
attemptReconnect() {
if (
this.options.maxReconnectAttempts &&
this.reconnectAttempts >= this.options.maxReconnectAttempts
) {
this.emit('maxReconnectAttemptsReached');
return;
}
const timeout = Math.min(
this.options.reconnectTime *
Math.pow(this.options.reconnectDecay, this.reconnectAttempts),
this.options.maxReconnectTime
);
this.reconnectTimeoutId = setTimeout(() => {
this.reconnectAttempts++;
this.emit('reconnect', {
attempt: this.reconnectAttempts,
maxAttempts: this.options.maxReconnectAttempts,
});
this.connect();
}, timeout);
}
addEventListener(type, handler) {
if (!this.listeners.has(type)) {
this.listeners.set(type, []);
}
this.listeners.get(type).push(handler);
// Add to existing EventSource if connected
if (this.eventSource) {
this.eventSource.addEventListener(type, handler);
}
}
removeEventListener(type, handler) {
if (this.listeners.has(type)) {
const handlers = this.listeners.get(type);
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
if (this.eventSource) {
this.eventSource.removeEventListener(type, handler);
}
}
}
emit(type, data) {
if (this.listeners.has(type)) {
this.listeners.get(type).forEach((handler) => {
try {
handler(data);
} catch (error) {
console.error('Error in event listener:', error);
}
});
}
}
close() {
this.isIntentionallyClosed = true;
if (this.reconnectTimeoutId) {
clearTimeout(this.reconnectTimeoutId);
this.reconnectTimeoutId = null;
}
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
// Convenience methods
on(type, handler) {
this.addEventListener(type, handler);
return this;
}
off(type, handler) {
this.removeEventListener(type, handler);
return this;
}
get readyState() {
return this.eventSource ? this.eventSource.readyState : EventSource.CLOSED;
}
get url() {
return this.eventSource ? this.eventSource.url : this.url;
}
}
// SSE Server Implementation (Node.js style)
class SSEServer {
constructor() {
this.clients = new Set();
this.channels = new Map();
this.middleware = [];
}
// Middleware support
use(middleware) {
this.middleware.push(middleware);
}
// Handle SSE connection
handleConnection(req, res) {
// Set SSE headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control',
});
const client = {
id: this.generateClientId(),
req,
res,
channels: new Set(),
lastEventId: 0,
connectedAt: Date.now(),
isAlive: true,
};
// Apply middleware
for (const middleware of this.middleware) {
try {
middleware(client, req);
} catch (error) {
console.error('SSE middleware error:', error);
}
}
this.clients.add(client);
// Send initial connection event
this.sendToClient(client, {
type: 'connected',
data: { clientId: client.id },
});
// Handle client disconnect
req.on('close', () => {
this.handleDisconnect(client);
});
req.on('error', (error) => {
console.error('SSE client error:', error);
this.handleDisconnect(client);
});
// Setup heartbeat
this.setupHeartbeat(client);
return client;
}
handleDisconnect(client) {
client.isAlive = false;
this.clients.delete(client);
// Remove from all channels
client.channels.forEach((channelName) => {
this.leaveChannel(client, channelName);
});
console.log(`SSE client disconnected: ${client.id}`);
}
setupHeartbeat(client) {
const heartbeatInterval = setInterval(() => {
if (!client.isAlive) {
clearInterval(heartbeatInterval);
return;
}
try {
this.sendToClient(client, {
type: 'heartbeat',
data: { timestamp: Date.now() },
});
} catch (error) {
console.error('Heartbeat error:', error);
clearInterval(heartbeatInterval);
this.handleDisconnect(client);
}
}, 30000);
}
// Send event to specific client
sendToClient(client, event) {
if (!client.isAlive || client.res.destroyed) {
return false;
}
try {
const eventId = ++client.lastEventId;
const data =
typeof event.data === 'object'
? JSON.stringify(event.data)
: event.data;
let message = '';
if (event.id) {
message += `id: ${event.id}\n`;
} else {
message += `id: ${eventId}\n`;
}
if (event.type) {
message += `event: ${event.type}\n`;
}
if (event.retry) {
message += `retry: ${event.retry}\n`;
}
message += `data: ${data}\n\n`;
client.res.write(message);
return true;
} catch (error) {
console.error('Error sending to client:', error);
this.handleDisconnect(client);
return false;
}
}
// Broadcast to all clients
broadcast(event) {
let successCount = 0;
this.clients.forEach((client) => {
if (this.sendToClient(client, event)) {
successCount++;
}
});
return successCount;
}
// Channel management
createChannel(name) {
if (!this.channels.has(name)) {
this.channels.set(name, {
name,
clients: new Set(),
messageHistory: [],
maxHistory: 100,
});
}
return this.channels.get(name);
}
joinChannel(client, channelName) {
const channel = this.createChannel(channelName);
channel.clients.add(client);
client.channels.add(channelName);
// Send recent message history
channel.messageHistory.forEach((message) => {
this.sendToClient(client, message);
});
// Notify about joining
this.sendToClient(client, {
type: 'channel-joined',
data: {
channel: channelName,
memberCount: channel.clients.size,
},
});
return channel;
}
leaveChannel(client, channelName) {
const channel = this.channels.get(channelName);
if (channel) {
channel.clients.delete(client);
client.channels.delete(channelName);
if (channel.clients.size === 0) {
this.channels.delete(channelName);
}
}
}
// Send to channel
sendToChannel(channelName, event) {
const channel = this.channels.get(channelName);
if (!channel) {
return 0;
}
// Add to history
channel.messageHistory.push(event);
if (channel.messageHistory.length > channel.maxHistory) {
channel.messageHistory.shift();
}
let successCount = 0;
channel.clients.forEach((client) => {
if (this.sendToClient(client, event)) {
successCount++;
}
});
return successCount;
}
// Utilities
generateClientId() {
return `sse-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
getStats() {
return {
clientCount: this.clients.size,
channelCount: this.channels.size,
channels: Array.from(this.channels.entries()).map(([name, channel]) => ({
name,
clientCount: channel.clients.size,
messageCount: channel.messageHistory.length,
})),
};
}
getClient(clientId) {
return Array.from(this.clients).find((client) => client.id === clientId);
}
}
// Live Dashboard Example using SSE
class LiveDashboard {
constructor(containerId, sseUrl) {
this.container = document.getElementById(containerId);
this.sse = new SSEClient(sseUrl);
this.metrics = new Map();
this.charts = new Map();
this.setupUI();
this.setupSSE();
}
setupUI() {
this.container.innerHTML = `
<div class="dashboard">
<div class="dashboard-header">
<h1>Live Dashboard</h1>
<div class="connection-status" id="connection-status">Connecting...</div>
</div>
<div class="metrics-grid" id="metrics-grid"></div>
<div class="charts-container" id="charts-container"></div>
<div class="events-feed" id="events-feed">
<h3>Live Events</h3>
<div id="events-list"></div>
</div>
</div>
`;
}
setupSSE() {
this.sse.on('open', () => {
this.updateConnectionStatus('Connected', 'connected');
});
this.sse.on('error', () => {
this.updateConnectionStatus('Connection Error', 'error');
});
this.sse.on('reconnect', (data) => {
this.updateConnectionStatus(
`Reconnecting (${data.attempt})...`,
'reconnecting'
);
});
// Handle specific events
this.sse.on('metrics', (event) => {
this.updateMetrics(event.data);
});
this.sse.on('alert', (event) => {
this.addAlert(event.data);
});
this.sse.on('system-event', (event) => {
this.addEvent(event.data);
});
this.sse.on('user-activity', (event) => {
this.updateUserActivity(event.data);
});
}
updateConnectionStatus(text, status) {
const statusElement = document.getElementById('connection-status');
statusElement.textContent = text;
statusElement.className = `connection-status ${status}`;
}
updateMetrics(metrics) {
const metricsGrid = document.getElementById('metrics-grid');
Object.entries(metrics).forEach(([key, value]) => {
let metricElement = document.getElementById(`metric-${key}`);
if (!metricElement) {
metricElement = document.createElement('div');
metricElement.id = `metric-${key}`;
metricElement.className = 'metric-card';
metricsGrid.appendChild(metricElement);
}
metricElement.innerHTML = `
<div class="metric-label">${this.formatMetricName(key)}</div>
<div class="metric-value">${this.formatMetricValue(value)}</div>
<div class="metric-trend ${this.getTrend(key, value)}"></div>
`;
this.metrics.set(key, value);
});
}
addAlert(alert) {
// Create floating alert
const alertElement = document.createElement('div');
alertElement.className = `alert alert-${alert.level}`;
alertElement.innerHTML = `
<div class="alert-title">${alert.title}</div>
<div class="alert-message">${alert.message}</div>
<button onclick="this.parentElement.remove()">×</button>
`;
document.body.appendChild(alertElement);
// Auto-remove after delay
setTimeout(() => {
if (alertElement.parentElement) {
alertElement.remove();
}
}, alert.duration || 5000);
this.addEvent({
type: 'alert',
level: alert.level,
message: `${alert.title}: ${alert.message}`,
timestamp: Date.now(),
});
}
addEvent(event) {
const eventsList = document.getElementById('events-list');
const eventElement = document.createElement('div');
eventElement.className = `event event-${event.type}`;
const time = new Date(event.timestamp).toLocaleTimeString();
eventElement.innerHTML = `
<div class="event-time">${time}</div>
<div class="event-message">${event.message}</div>
`;
eventsList.insertBefore(eventElement, eventsList.firstChild);
// Keep only last 50 events
while (eventsList.children.length > 50) {
eventsList.removeChild(eventsList.lastChild);
}
}
updateUserActivity(activity) {
this.addEvent({
type: 'user-activity',
message: `${activity.action} by ${activity.user}`,
timestamp: activity.timestamp,
});
}
formatMetricName(name) {
return name
.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase())
.replace(/_/g, ' ');
}
formatMetricValue(value) {
if (typeof value === 'number') {
if (value > 1000000) {
return (value / 1000000).toFixed(1) + 'M';
} else if (value > 1000) {
return (value / 1000).toFixed(1) + 'K';
}
return value.toLocaleString();
}
return value;
}
getTrend(key, value) {
const previous = this.metrics.get(key);
if (previous === undefined) return '';
if (value > previous) return 'up';
if (value < previous) return 'down';
return 'stable';
}
}
// Usage
const dashboard = new LiveDashboard(
'dashboard-container',
'/api/dashboard/events'
);
WebRTC for Peer-to-Peer Communication
WebRTC Implementation
// WebRTC Peer Connection Manager
class WebRTCPeer {
constructor(config = {}) {
this.config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
],
...config,
};
this.peerConnection = null;
this.localStream = null;
this.remoteStream = null;
this.dataChannel = null;
this.isInitiator = false;
this.listeners = new Map();
this.candidateQueue = [];
this.connectionState = 'new';
this.setupPeerConnection();
}
setupPeerConnection() {
this.peerConnection = new RTCPeerConnection(this.config);
// ICE candidate handling
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.emit('icecandidate', event.candidate);
}
};
// Connection state changes
this.peerConnection.onconnectionstatechange = () => {
this.connectionState = this.peerConnection.connectionState;
this.emit('connectionstatechange', this.connectionState);
if (this.connectionState === 'connected') {
this.emit('connected');
} else if (this.connectionState === 'disconnected') {
this.emit('disconnected');
} else if (this.connectionState === 'failed') {
this.emit('failed');
}
};
// ICE connection state changes
this.peerConnection.oniceconnectionstatechange = () => {
this.emit(
'iceconnectionstatechange',
this.peerConnection.iceConnectionState
);
};
// Remote stream handling
this.peerConnection.ontrack = (event) => {
this.remoteStream = event.streams[0];
this.emit('remotestream', this.remoteStream);
};
// Data channel handling
this.peerConnection.ondatachannel = (event) => {
const channel = event.channel;
this.setupDataChannel(channel);
};
}
// Create offer (caller)
async createOffer(options = {}) {
this.isInitiator = true;
try {
const offer = await this.peerConnection.createOffer(options);
await this.peerConnection.setLocalDescription(offer);
this.emit('offer', offer);
return offer;
} catch (error) {
this.emit('error', error);
throw error;
}
}
// Create answer (callee)
async createAnswer(offer, options = {}) {
try {
await this.peerConnection.setRemoteDescription(offer);
// Process queued candidates
for (const candidate of this.candidateQueue) {
await this.addIceCandidate(candidate);
}
this.candidateQueue = [];
const answer = await this.peerConnection.createAnswer(options);
await this.peerConnection.setLocalDescription(answer);
this.emit('answer', answer);
return answer;
} catch (error) {
this.emit('error', error);
throw error;
}
}
// Handle answer (caller)
async handleAnswer(answer) {
try {
await this.peerConnection.setRemoteDescription(answer);
// Process queued candidates
for (const candidate of this.candidateQueue) {
await this.addIceCandidate(candidate);
}
this.candidateQueue = [];
} catch (error) {
this.emit('error', error);
throw error;
}
}
// Add ICE candidate
async addIceCandidate(candidate) {
try {
if (this.peerConnection.remoteDescription) {
await this.peerConnection.addIceCandidate(candidate);
} else {
this.candidateQueue.push(candidate);
}
} catch (error) {
console.error('Error adding ICE candidate:', error);
}
}
// Media handling
async getUserMedia(constraints = { video: true, audio: true }) {
try {
this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
// Add tracks to peer connection
this.localStream.getTracks().forEach((track) => {
this.peerConnection.addTrack(track, this.localStream);
});
this.emit('localstream', this.localStream);
return this.localStream;
} catch (error) {
this.emit('error', error);
throw error;
}
}
async getDisplayMedia(constraints = { video: true }) {
try {
this.localStream =
await navigator.mediaDevices.getDisplayMedia(constraints);
// Replace video track
const videoTrack = this.localStream.getVideoTracks()[0];
const sender = this.peerConnection
.getSenders()
.find((s) => s.track && s.track.kind === 'video');
if (sender) {
await sender.replaceTrack(videoTrack);
} else {
this.peerConnection.addTrack(videoTrack, this.localStream);
}
this.emit('localstream', this.localStream);
return this.localStream;
} catch (error) {
this.emit('error', error);
throw error;
}
}
// Data channel
createDataChannel(label, options = {}) {
const defaultOptions = {
ordered: true,
maxRetransmits: 3,
...options,
};
this.dataChannel = this.peerConnection.createDataChannel(
label,
defaultOptions
);
this.setupDataChannel(this.dataChannel);
return this.dataChannel;
}
setupDataChannel(channel) {
channel.onopen = () => {
this.emit('datachannel-open', channel);
};
channel.onclose = () => {
this.emit('datachannel-close', channel);
};
channel.onmessage = (event) => {
this.emit('datachannel-message', event.data);
};
channel.onerror = (error) => {
this.emit('datachannel-error', error);
};
if (!this.dataChannel) {
this.dataChannel = channel;
}
}
sendData(data) {
if (this.dataChannel && this.dataChannel.readyState === 'open') {
const message = typeof data === 'object' ? JSON.stringify(data) : data;
this.dataChannel.send(message);
return true;
}
return false;
}
// Statistics
async getStats() {
try {
const stats = await this.peerConnection.getStats();
const parsedStats = {};
stats.forEach((report) => {
parsedStats[report.id] = report;
});
return parsedStats;
} catch (error) {
console.error('Error getting stats:', error);
return null;
}
}
// Event handling
on(event, handler) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(handler);
}
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach((handler) => {
try {
handler(data);
} catch (error) {
console.error('Error in event handler:', error);
}
});
}
}
// Cleanup
close() {
if (this.localStream) {
this.localStream.getTracks().forEach((track) => track.stop());
}
if (this.dataChannel) {
this.dataChannel.close();
}
if (this.peerConnection) {
this.peerConnection.close();
}
this.emit('closed');
}
// State getters
get state() {
return this.connectionState;
}
get signalingState() {
return this.peerConnection ? this.peerConnection.signalingState : 'closed';
}
get iceConnectionState() {
return this.peerConnection
? this.peerConnection.iceConnectionState
: 'closed';
}
}
// Video Chat Application
class VideoChat {
constructor(containerId, signalingUrl) {
this.container = document.getElementById(containerId);
this.signalingSocket = new WebSocketClient(signalingUrl);
this.peer = null;
this.roomId = null;
this.isInitiator = false;
this.setupUI();
this.setupSignaling();
}
setupUI() {
this.container.innerHTML = `
<div class="video-chat">
<div class="video-container">
<video id="localVideo" autoplay muted playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
</div>
<div class="controls">
<input type="text" id="roomInput" placeholder="Room ID" />
<button id="joinBtn">Join Room</button>
<button id="leaveBtn" disabled>Leave Room</button>
<button id="muteBtn">Mute</button>
<button id="videoBtn">Video Off</button>
<button id="screenBtn">Share Screen</button>
</div>
<div class="chat">
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Type a message..." />
<button id="sendBtn">Send</button>
</div>
<div class="status" id="status">Not connected</div>
</div>
`;
this.setupEventListeners();
}
setupEventListeners() {
document.getElementById('joinBtn').onclick = () => this.joinRoom();
document.getElementById('leaveBtn').onclick = () => this.leaveRoom();
document.getElementById('muteBtn').onclick = () => this.toggleMute();
document.getElementById('videoBtn').onclick = () => this.toggleVideo();
document.getElementById('screenBtn').onclick = () => this.shareScreen();
document.getElementById('sendBtn').onclick = () => this.sendMessage();
document.getElementById('messageInput').onkeypress = (e) => {
if (e.key === 'Enter') this.sendMessage();
};
}
setupSignaling() {
this.signalingSocket.on('open', () => {
this.updateStatus('Connected to signaling server');
});
this.signalingSocket.on('message', (event) => {
this.handleSignalingMessage(event.data);
});
this.signalingSocket.on('error', () => {
this.updateStatus('Signaling error');
});
}
async joinRoom() {
const roomInput = document.getElementById('roomInput');
this.roomId = roomInput.value.trim();
if (!this.roomId) {
alert('Please enter a room ID');
return;
}
try {
// Get user media
await this.getUserMedia();
// Join room via signaling
this.signalingSocket.send({
type: 'join-room',
room: this.roomId,
});
document.getElementById('joinBtn').disabled = true;
document.getElementById('leaveBtn').disabled = false;
} catch (error) {
console.error('Error joining room:', error);
alert('Error accessing camera/microphone');
}
}
leaveRoom() {
if (this.peer) {
this.peer.close();
this.peer = null;
}
this.signalingSocket.send({
type: 'leave-room',
room: this.roomId,
});
document.getElementById('remoteVideo').srcObject = null;
document.getElementById('joinBtn').disabled = false;
document.getElementById('leaveBtn').disabled = true;
this.updateStatus('Left room');
}
async getUserMedia() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
document.getElementById('localVideo').srcObject = stream;
return stream;
} catch (error) {
console.error('Error getting user media:', error);
throw error;
}
}
async handleSignalingMessage(message) {
switch (message.type) {
case 'room-joined':
this.updateStatus(`Joined room: ${message.room}`);
this.isInitiator = message.isInitiator;
if (this.isInitiator) {
await this.createPeerConnection();
await this.createOffer();
}
break;
case 'user-joined':
this.updateStatus('Another user joined');
if (!this.peer) {
await this.createPeerConnection();
}
break;
case 'offer':
await this.handleOffer(message.offer);
break;
case 'answer':
await this.handleAnswer(message.answer);
break;
case 'ice-candidate':
await this.handleIceCandidate(message.candidate);
break;
case 'user-left':
this.updateStatus('Other user left');
document.getElementById('remoteVideo').srcObject = null;
break;
}
}
async createPeerConnection() {
this.peer = new WebRTCPeer();
// Add local stream
const localStream = document.getElementById('localVideo').srcObject;
if (localStream) {
localStream.getTracks().forEach((track) => {
this.peer.peerConnection.addTrack(track, localStream);
});
}
// Setup event handlers
this.peer.on('icecandidate', (candidate) => {
this.signalingSocket.send({
type: 'ice-candidate',
room: this.roomId,
candidate,
});
});
this.peer.on('remotestream', (stream) => {
document.getElementById('remoteVideo').srcObject = stream;
this.updateStatus('Connected to peer');
});
this.peer.on('connected', () => {
this.updateStatus('Peer connection established');
});
this.peer.on('disconnected', () => {
this.updateStatus('Peer disconnected');
});
// Create data channel for chat
this.peer.createDataChannel('chat');
this.peer.on('datachannel-message', (data) => {
this.addChatMessage(JSON.parse(data), false);
});
}
async createOffer() {
try {
const offer = await this.peer.createOffer();
this.signalingSocket.send({
type: 'offer',
room: this.roomId,
offer,
});
} catch (error) {
console.error('Error creating offer:', error);
}
}
async handleOffer(offer) {
if (!this.peer) {
await this.createPeerConnection();
}
try {
const answer = await this.peer.createAnswer(offer);
this.signalingSocket.send({
type: 'answer',
room: this.roomId,
answer,
});
} catch (error) {
console.error('Error handling offer:', error);
}
}
async handleAnswer(answer) {
try {
await this.peer.handleAnswer(answer);
} catch (error) {
console.error('Error handling answer:', error);
}
}
async handleIceCandidate(candidate) {
try {
await this.peer.addIceCandidate(candidate);
} catch (error) {
console.error('Error adding ICE candidate:', error);
}
}
toggleMute() {
const localVideo = document.getElementById('localVideo');
const audioTracks = localVideo.srcObject.getAudioTracks();
if (audioTracks.length > 0) {
const enabled = audioTracks[0].enabled;
audioTracks[0].enabled = !enabled;
const btn = document.getElementById('muteBtn');
btn.textContent = enabled ? 'Unmute' : 'Mute';
}
}
toggleVideo() {
const localVideo = document.getElementById('localVideo');
const videoTracks = localVideo.srcObject.getVideoTracks();
if (videoTracks.length > 0) {
const enabled = videoTracks[0].enabled;
videoTracks[0].enabled = !enabled;
const btn = document.getElementById('videoBtn');
btn.textContent = enabled ? 'Video On' : 'Video Off';
}
}
async shareScreen() {
try {
await this.peer.getDisplayMedia();
this.updateStatus('Screen sharing started');
} catch (error) {
console.error('Error sharing screen:', error);
}
}
sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (message && this.peer) {
const chatMessage = {
text: message,
timestamp: Date.now(),
};
this.peer.sendData(chatMessage);
this.addChatMessage(chatMessage, true);
input.value = '';
}
}
addChatMessage(message, isLocal) {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.className = `message ${isLocal ? 'local' : 'remote'}`;
const time = new Date(message.timestamp).toLocaleTimeString();
messageElement.innerHTML = `
<div class="message-text">${message.text}</div>
<div class="message-time">${time}</div>
`;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
updateStatus(text) {
document.getElementById('status').textContent = text;
}
}
// Usage
const videoChat = new VideoChat('video-chat-container', 'ws://localhost:8080');
Conclusion
Real-time applications require careful consideration of communication patterns, connection management, and user experience. WebSockets provide full-duplex communication for interactive applications, Server-Sent Events offer simple server-to-client streaming, and WebRTC enables direct peer-to-peer communication. Choose the right technology based on your specific requirements: WebSockets for bidirectional real-time communication, SSE for live updates and notifications, and WebRTC for direct media communication. Remember to implement proper error handling, reconnection logic, and graceful degradation for robust real-time applications.