Elysia Tools
Navigation
Real-time Communication
WebSocket Samples
Real-time communication examples using WebSocket protocol including chat, notifications, and live data streaming
Samples
Entries inside this sample collection
JavaScript WebSocket Client
Complete WebSocket client implementation with reconnection, heartbeat, and message handling
Difficulty
4/10
Estimated time
20 min
Tags
websocket, javascript, real-time, client
Prerequisites
JavaScript basics, WebSocket protocol
// WebSocket Client Implementation with Reconnection and Heartbeat
class WebSocketClient {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnectInterval: 3000,
maxReconnectAttempts: 5,
heartbeatInterval: 30000,
heartbeatTimeout: 5000,
debug: false,
...options
};
this.ws = null;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.heartbeatTimeoutTimer = null;
this.messageQueue = [];
this.eventListeners = new Map();
this.isManualClose = false;
this.connect();
}
connect() {
try {
this.log('Connecting to WebSocket...');
this.ws = new WebSocket(this.url);
this.ws.onopen = (event) => {
this.log('WebSocket connected');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.flushMessageQueue();
this.emit('open', event);
};
this.ws.onmessage = (event) => {
this.log('Message received:', event.data);
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
} catch (error) {
this.log('Error parsing message:', error);
this.emit('error', new Error('Invalid JSON message'));
}
// Handle heartbeat response
if (event.data === 'pong') {
this.handleHeartbeatResponse();
}
this.emit('message', event);
};
this.ws.onerror = (event) => {
this.log('WebSocket error:', event);
this.emit('error', event);
};
this.ws.onclose = (event) => {
this.log('WebSocket closed:', event.code, event.reason);
this.stopHeartbeat();
this.emit('close', event);
if (!this.isManualClose && this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.scheduleReconnect();
}
};
} catch (error) {
this.log('Connection error:', error);
this.emit('error', error);
this.scheduleReconnect();
}
}
disconnect() {
this.isManualClose = true;
this.stopHeartbeat();
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close(1000, 'Manual disconnect');
}
}
send(data) {
const message = typeof data === 'string' ? data : JSON.stringify(data);
if (this.isConnected()) {
this.ws.send(message);
this.log('Message sent:', message);
} else {
this.log('WebSocket not connected, queuing message');
this.messageQueue.push(message);
}
}
isConnected() {
return this.ws && this.ws.readyState === WebSocket.OPEN;
}
on(event, callback) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, []);
}
this.eventListeners.get(event).push(callback);
}
off(event, callback) {
if (this.eventListeners.has(event)) {
const listeners = this.eventListeners.get(event);
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
emit(event, ...args) {
if (this.eventListeners.has(event)) {
this.eventListeners.get(event).forEach(callback => {
try {
callback(...args);
} catch (error) {
this.log('Error in event listener:', error);
}
});
}
}
// Heartbeat mechanism
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.isConnected()) {
this.ws.send('ping');
this.heartbeatTimeoutTimer = setTimeout(() => {
this.log('Heartbeat timeout, closing connection');
this.ws.close(1000, 'Heartbeat timeout');
}, this.options.heartbeatTimeout);
}
}, this.options.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
if (this.heartbeatTimeoutTimer) {
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = null;
}
}
handleHeartbeatResponse() {
if (this.heartbeatTimeoutTimer) {
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = null;
}
}
// Reconnection logic
scheduleReconnect() {
this.reconnectAttempts++;
const delay = Math.min(
this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1),
30000
);
this.log(`Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
setTimeout(() => {
this.connect();
}, delay);
}
// Message queue for offline messages
flushMessageQueue() {
while (this.messageQueue.length > 0 && this.isConnected()) {
const message = this.messageQueue.shift();
this.ws.send(message);
}
}
// Message handling with type routing
handleMessage(message) {
if (message.type && this.eventListeners.has(message.type)) {
this.emit(message.type, message.data);
}
}
log(...args) {
if (this.options.debug) {
console.log('[WebSocketClient]', ...args);
}
}
// Get connection state
getState() {
if (!this.ws) return 'DISCONNECTED';
switch (this.ws.readyState) {
case WebSocket.CONNECTING: return 'CONNECTING';
case WebSocket.OPEN: return 'CONNECTED';
case WebSocket.CLOSING: return 'CLOSING';
case WebSocket.CLOSED: return 'DISCONNECTED';
default: return 'UNKNOWN';
}
}
// Get statistics
getStats() {
return {
state: this.getState(),
reconnectAttempts: this.reconnectAttempts,
queuedMessages: this.messageQueue.length,
url: this.url
};
}
}
// Usage Examples
// Example 1: Basic WebSocket client
const basicClient = new WebSocketClient('wss://echo.websocket.org', {
debug: true,
reconnectInterval: 2000,
maxReconnectAttempts: 10
});
basicClient.on('open', () => {
console.log('Connected to echo server');
basicClient.send({
type: 'message',
data: 'Hello, WebSocket!'
});
});
basicClient.on('message', (event) => {
console.log('Received:', event.data);
});
basicClient.on('error', (error) => {
console.error('WebSocket error:', error);
});
// Example 2: Chat client
class ChatClient extends WebSocketClient {
constructor(url, username) {
super(url, { debug: true });
this.username = username;
this.on('open', () => {
this.send({
type: 'join',
data: { username: this.username }
});
});
this.on('chat_message', (data) => {
console.log(`${data.username}: ${data.message}`);
});
this.on('user_joined', (data) => {
console.log(`${data.username} joined the chat`);
});
this.on('user_left', (data) => {
console.log(`${data.username} left the chat`);
});
}
sendMessage(message) {
this.send({
type: 'chat_message',
data: {
username: this.username,
message: message,
timestamp: new Date().toISOString()
}
});
}
}
// Example 3: Real-time notifications client
class NotificationClient extends WebSocketClient {
constructor(url, userId) {
super(url, { debug: true });
this.userId = userId;
this.notifications = [];
this.on('open', () => {
this.send({
type: 'authenticate',
data: { userId: this.userId }
});
});
this.on('notification', (data) => {
this.notifications.push(data);
this.showNotification(data);
});
}
showNotification(notification) {
if (Notification.permission === 'granted') {
new Notification(notification.title, {
body: notification.body,
icon: notification.icon,
tag: notification.id
});
}
console.log('Notification:', notification);
}
getUnreadCount() {
return this.notifications.filter(n => !n.read).length;
}
markAsRead(notificationId) {
const notification = this.notifications.find(n => n.id === notificationId);
if (notification) {
notification.read = true;
}
}
}
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
// Export classes for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = { WebSocketClient, ChatClient, NotificationClient };
}Node.js WebSocket Server
Complete WebSocket server implementation with Express, Socket.IO, and room management
Difficulty
7/10
Estimated time
30 min
Tags
websocket, nodejs, server, real-time
Prerequisites
Node.js, Express, Socket.IO, JWT
// WebSocket Server Implementation with Socket.IO and Express
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const app = express();
const server = http.createServer(app);
// Configure Socket.IO with CORS and options
const io = socketIo(server, {
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
methods: ['GET', 'POST'],
credentials: true
},
pingTimeout: 60000,
pingInterval: 25000
});
// Rate limiting for HTTP endpoints
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
app.use(express.json());
// In-memory storage (replace with database in production)
const users = new Map();
const rooms = new Map();
const connectedSockets = new Map();
// JWT Secret
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Middleware for Socket.IO authentication
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.replace('Bearer ', '');
if (!token) {
return next(new Error('Authentication token required'));
}
const decoded = jwt.verify(token, JWT_SECRET);
socket.userId = decoded.userId;
socket.username = decoded.username;
next();
} catch (error) {
next(new Error('Invalid authentication token'));
}
});
// Socket.IO connection handling
io.on('connection', (socket) => {
console.log(`User connected: ${socket.username} (${socket.userId})`);
// Store socket connection
connectedSockets.set(socket.userId, socket);
// Join user to their personal room
const personalRoom = `user_${socket.userId}`;
socket.join(personalRoom);
// Initialize user data
users.set(socket.userId, {
id: socket.userId,
username: socket.username,
socketId: socket.id,
connectedAt: new Date(),
rooms: [personalRoom],
status: 'online'
});
// Notify others about user coming online
socket.broadcast.emit('user_status_change', {
userId: socket.userId,
username: socket.username,
status: 'online'
});
// Handle joining rooms
socket.on('join_room', (data) => {
const { roomId, password } = data;
if (!rooms.has(roomId)) {
return socket.emit('error', { message: 'Room not found' });
}
const room = rooms.get(roomId);
// Check password protection
if (room.password && room.password !== password) {
return socket.emit('error', { message: 'Invalid room password' });
}
// Join room
socket.join(roomId);
users.get(socket.userId).rooms.push(roomId);
// Update room participants
if (!room.participants.includes(socket.userId)) {
room.participants.push(socket.userId);
}
socket.emit('joined_room', { roomId, roomName: room.name });
// Notify other room participants
socket.to(roomId).emit('user_joined_room', {
userId: socket.userId,
username: socket.username,
roomId
});
// Send room info to the user
socket.emit('room_info', {
roomId,
name: room.name,
participants: room.participants.map(pid => users.get(pid)?.username).filter(Boolean)
});
});
// Handle leaving rooms
socket.on('leave_room', (data) => {
const { roomId } = data;
socket.leave(roomId);
const userData = users.get(socket.userId);
if (userData) {
userData.rooms = userData.rooms.filter(room => room !== roomId);
}
const room = rooms.get(roomId);
if (room) {
room.participants = room.participants.filter(pid => pid !== socket.userId);
// Notify other room participants
socket.to(roomId).emit('user_left_room', {
userId: socket.userId,
username: socket.username,
roomId
});
// Delete room if empty and not persistent
if (room.participants.length === 0 && !room.persistent) {
rooms.delete(roomId);
}
}
socket.emit('left_room', { roomId });
});
// Handle chat messages
socket.on('chat_message', (data) => {
const { roomId, message, type = 'text' } = data;
const messageData = {
id: generateId(),
userId: socket.userId,
username: socket.username,
message: sanitizeMessage(message),
type,
timestamp: new Date().toISOString(),
roomId
};
if (roomId) {
// Send to specific room
socket.to(roomId).emit('chat_message', messageData);
} else {
// Send to all connected users
socket.broadcast.emit('chat_message', messageData);
}
// Send confirmation to sender
socket.emit('message_sent', messageData);
// Store message in room history
if (roomId && rooms.has(roomId)) {
const room = rooms.get(roomId);
if (!room.messages) room.messages = [];
room.messages.push(messageData);
// Limit message history
if (room.messages.length > 1000) {
room.messages = room.messages.slice(-1000);
}
}
});
// Handle private messages
socket.on('private_message', (data) => {
const { targetUserId, message } = data;
if (!users.has(targetUserId)) {
return socket.emit('error', { message: 'User not found' });
}
const messageData = {
id: generateId(),
userId: socket.userId,
username: socket.username,
message: sanitizeMessage(message),
timestamp: new Date().toISOString(),
private: true
};
// Send to target user
const targetSocket = connectedSockets.get(targetUserId);
if (targetSocket) {
targetSocket.emit('private_message', messageData);
}
// Send confirmation to sender
socket.emit('message_sent', messageData);
});
// Handle typing indicators
socket.on('typing_start', (data) => {
const { roomId } = data;
if (roomId) {
socket.to(roomId).emit('user_typing', {
userId: socket.userId,
username: socket.username,
typing: true
});
}
});
socket.on('typing_stop', (data) => {
const { roomId } = data;
if (roomId) {
socket.to(roomId).emit('user_typing', {
userId: socket.userId,
username: socket.username,
typing: false
});
}
});
// Handle room creation
socket.on('create_room', (data) => {
const { name, password, persistent = false } = data;
const roomId = generateId();
rooms.set(roomId, {
id: roomId,
name: sanitizeInput(name),
password: password || null,
persistent,
createdBy: socket.userId,
createdAt: new Date(),
participants: [socket.userId],
messages: []
});
socket.emit('room_created', { roomId, name });
// Auto-join creator to the room
socket.emit('join_room', { roomId });
});
// Handle get room list
socket.on('get_rooms', () => {
const roomList = Array.from(rooms.values())
.filter(room => !room.password || room.participants.includes(socket.userId))
.map(room => ({
id: room.id,
name: room.name,
participantCount: room.participants.length,
hasPassword: !!room.password,
persistent: room.persistent
}));
socket.emit('rooms_list', roomList);
});
// Handle get online users
socket.on('get_online_users', () => {
const onlineUsers = Array.from(users.values())
.filter(user => user.id !== socket.userId)
.map(user => ({
id: user.id,
username: user.username,
status: user.status
}));
socket.emit('online_users', onlineUsers);
});
// Handle disconnection
socket.on('disconnect', (reason) => {
console.log(`User disconnected: ${socket.username} (${reason})`);
// Clean up
connectedSockets.delete(socket.userId);
const userData = users.get(socket.userId);
if (userData) {
// Leave all rooms
userData.rooms.forEach(roomId => {
if (roomId !== `user_${socket.userId}`) {
const room = rooms.get(roomId);
if (room) {
room.participants = room.participants.filter(pid => pid !== socket.userId);
// Notify room participants
socket.to(roomId).emit('user_left_room', {
userId: socket.userId,
username: socket.username,
roomId
});
// Delete empty non-persistent rooms
if (room.participants.length === 0 && !room.persistent) {
rooms.delete(roomId);
}
}
}
});
users.delete(socket.userId);
}
// Notify others about user going offline
socket.broadcast.emit('user_status_change', {
userId: socket.userId,
username: socket.username,
status: 'offline'
});
});
});
// HTTP Routes
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
connectedUsers: users.size,
activeRooms: rooms.size
});
});
app.get('/api/rooms', (req, res) => {
const roomList = Array.from(rooms.values()).map(room => ({
id: room.id,
name: room.name,
participantCount: room.participants.length,
hasPassword: !!room.password,
persistent: room.persistent,
createdAt: room.createdAt
}));
res.json(roomList);
});
app.get('/api/users/online', (req, res) => {
const onlineUsers = Array.from(users.values()).map(user => ({
id: user.id,
username: user.username,
status: user.status,
connectedAt: user.connectedAt
}));
res.json(onlineUsers);
});
// Utility functions
function generateId() {
return Math.random().toString(36).substr(2, 9);
}
function sanitizeInput(input) {
return input.replace(/<[^>]*>/g, '').trim();
}
function sanitizeMessage(message) {
// Basic sanitization - in production, use a proper HTML sanitizer
return sanitizeInput(message);
}
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
// Start server
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
console.log(`WebSocket server running on port ${PORT}`);
});
module.exports = { app, server, io };TypeScript WebSocket Types and Interfaces
Complete TypeScript type definitions and interfaces for WebSocket applications
Difficulty
5/10
Estimated time
25 min
Tags
typescript, websocket, types, definitions
Prerequisites
TypeScript, WebSocket concepts
// WebSocket Type Definitions and Interfaces for TypeScript
// Base WebSocket message types
export interface BaseMessage {
id: string;
timestamp: string;
type: string;
}
// Chat message types
export interface ChatMessage extends BaseMessage {
type: 'chat_message';
data: {
userId: string;
username: string;
message: string;
roomId?: string;
private?: boolean;
messageType: 'text' | 'image' | 'file' | 'emoji';
metadata?: {
fileName?: string;
fileSize?: number;
fileUrl?: string;
imageUrl?: string;
};
};
}
export interface PrivateMessage extends BaseMessage {
type: 'private_message';
data: {
userId: string;
username: string;
targetUserId: string;
targetUsername: string;
message: string;
messageType: 'text' | 'image' | 'file' | 'emoji';
};
}
// System notification types
export interface SystemNotification extends BaseMessage {
type: 'system_notification';
data: {
title: string;
message: string;
level: 'info' | 'warning' | 'error' | 'success';
icon?: string;
actionUrl?: string;
actionText?: string;
};
}
// User management types
export interface UserStatusChange extends BaseMessage {
type: 'user_status_change';
data: {
userId: string;
username: string;
status: 'online' | 'offline' | 'away' | 'busy';
lastSeen?: string;
};
}
export interface UserJoinedRoom extends BaseMessage {
type: 'user_joined_room';
data: {
userId: string;
username: string;
roomId: string;
roomName: string;
};
}
export interface UserLeftRoom extends BaseMessage {
type: 'user_left_room';
data: {
userId: string;
username: string;
roomId: string;
roomName: string;
};
}
// Room management types
export interface RoomInfo {
id: string;
name: string;
description?: string;
participantCount: number;
maxParticipants?: number;
hasPassword: boolean;
persistent: boolean;
isPrivate: boolean;
createdBy: string;
createdAt: string;
lastActivity: string;
tags: string[];
}
export interface RoomInfoResponse extends BaseMessage {
type: 'room_info';
data: RoomInfo & {
participants: Array<{
userId: string;
username: string;
role: 'owner' | 'admin' | 'moderator' | 'member';
joinedAt: string;
}>;
permissions: {
canSendMessages: boolean;
canSendFiles: boolean;
canInviteUsers: boolean;
canManageRoom: boolean;
};
};
}
export interface RoomListResponse extends BaseMessage {
type: 'rooms_list';
data: {
rooms: RoomInfo[];
totalCount: number;
hasMore: boolean;
};
}
// Typing indicators
export interface UserTyping extends BaseMessage {
type: 'user_typing';
data: {
userId: string;
username: string;
roomId?: string;
typing: boolean;
};
}
// File sharing types
export interface FileShareMessage extends BaseMessage {
type: 'file_share';
data: {
userId: string;
username: string;
fileName: string;
fileSize: number;
fileUrl: string;
mimeType: string;
roomId?: string;
private?: boolean;
thumbnailUrl?: string;
downloadUrl: string;
previewUrl?: string;
};
}
// Voice/Video call types
export interface CallInvitation extends BaseMessage {
type: 'call_invitation';
data: {
callId: string;
callerId: string;
callerUsername: string;
targetUserId: string;
targetUsername: string;
callType: 'audio' | 'video';
roomId?: string;
};
}
export interface CallResponse extends BaseMessage {
type: 'call_response';
data: {
callId: string;
response: 'accept' | 'decline' | 'busy';
userId: string;
username: string;
};
}
export interface CallEnd extends BaseMessage {
type: 'call_end';
data: {
callId: string;
endedBy: string;
endedByUsername: string;
duration: number; // in seconds
reason: 'ended' | 'rejected' | 'failed' | 'timeout';
};
}
// Authentication and authorization types
export interface AuthRequest {
type: 'authenticate';
data: {
token: string;
userId?: string;
deviceId?: string;
};
}
export interface AuthResponse extends BaseMessage {
type: 'auth_response';
data: {
success: boolean;
userId: string;
username: string;
permissions: string[];
sessionExpiresAt: string;
};
}
// Error handling types
export interface ErrorMessage extends BaseMessage {
type: 'error';
data: {
code: string;
message: string;
details?: any;
retryable: boolean;
};
}
// Presence and activity types
export interface UserActivity extends BaseMessage {
type: 'user_activity';
data: {
userId: string;
username: string;
activity: {
type: 'listening' | 'watching' | 'playing' | 'working';
details: string;
expiresAt?: string;
};
};
}
// Reaction types
export interface MessageReaction extends BaseMessage {
type: 'message_reaction';
data: {
messageId: string;
userId: string;
username: string;
reaction: string; // emoji
action: 'add' | 'remove';
};
}
// Moderation types
export interface MessageModeration extends BaseMessage {
type: 'message_moderation';
data: {
messageId: string;
moderatedBy: string;
moderatedByUsername: string;
action: 'delete' | 'edit' | 'warn';
reason?: string;
originalContent?: string;
};
}
// Union type for all possible messages
export type WebSocketMessage =
| ChatMessage
| PrivateMessage
| SystemNotification
| UserStatusChange
| UserJoinedRoom
| UserLeftRoom
| RoomInfoResponse
| RoomListResponse
| UserTyping
| FileShareMessage
| CallInvitation
| CallResponse
| CallEnd
| AuthResponse
| ErrorMessage
| UserActivity
| MessageReaction
| MessageModeration;
// Client configuration
export interface WebSocketClientConfig {
url: string;
protocols?: string[];
reconnectInterval?: number;
maxReconnectAttempts?: number;
heartbeatInterval?: number;
heartbeatTimeout?: number;
debug?: boolean;
authentication?: {
token?: string;
type?: 'bearer' | 'custom';
};
}
// Room creation data
export interface CreateRoomData {
name: string;
description?: string;
password?: string;
maxParticipants?: number;
isPrivate?: boolean;
persistent?: boolean;
tags?: string[];
}
// Room join data
export interface JoinRoomData {
roomId: string;
password?: string;
}
// Chat message data
export interface SendMessageData {
message: string;
roomId?: string;
targetUserId?: string;
messageType?: 'text' | 'image' | 'file' | 'emoji';
metadata?: {
fileName?: string;
fileSize?: number;
fileUrl?: string;
imageUrl?: string;
};
}
// File upload data
export interface FileUploadData {
file: File | Blob;
fileName?: string;
roomId?: string;
targetUserId?: string;
description?: string;
}
// Statistics and monitoring types
export interface WebSocketStats {
connectionState: 'connecting' | 'connected' | 'disconnecting' | 'disconnected';
reconnectAttempts: number;
messagesReceived: number;
messagesSent: number;
bytesReceived: number;
bytesSent: number;
latency: number; // in milliseconds
connectedAt?: string;
lastMessageAt?: string;
}
export interface RoomStats {
roomId: string;
participantCount: number;
messageCount: number;
fileCount: number;
lastActivity: string;
createdAt: string;
}
// Event handler types
export type WebSocketEventHandler<T = WebSocketMessage> = (data: T) => void;
export type WebSocketErrorEventHandler = (error: Error) => void;
export type WebSocketCloseEventHandler = (event: CloseEvent) => void;
export type WebSocketOpenEventHandler = (event: Event) => void;
// Advanced client interface
export interface IWebSocketClient {
connect(): Promise<void>;
disconnect(): void;
send<T extends WebSocketMessage>(message: T): void;
on<T extends WebSocketMessage['type']>(
eventType: T,
handler: (data: Extract<WebSocketMessage, { type: T }>) => void
): void;
off<T extends WebSocketMessage['type']>(
eventType: T,
handler: (data: Extract<WebSocketMessage, { type: T }>) => void
): void;
once<T extends WebSocketMessage['type']>(
eventType: T,
handler: (data: Extract<WebSocketMessage, { type: T }>) => void
): void;
getStats(): WebSocketStats;
getState(): string;
isConnected(): boolean;
}
// Room management interface
export interface IRoomManager {
createRoom(data: CreateRoomData): Promise<string>;
joinRoom(data: JoinRoomData): Promise<void>;
leaveRoom(roomId: string): Promise<void>;
getRoomList(): Promise<RoomInfo[]>;
getRoomInfo(roomId: string): Promise<RoomInfoResponse['data']>;
inviteToRoom(roomId: string, userIds: string[]): Promise<void>;
kickFromRoom(roomId: string, userIds: string[]): Promise<void>;
}
// Message persistence interface
export interface IMessagePersistence {
saveMessage(message: ChatMessage): Promise<void>;
getMessageHistory(roomId?: string, limit?: number, offset?: number): Promise<ChatMessage[]>;
searchMessages(query: string, roomId?: string): Promise<ChatMessage[]>;
deleteMessage(messageId: string): Promise<void>;
editMessage(messageId: string, newContent: string): Promise<void>;
}
// User presence interface
export interface IUserPresence {
updateStatus(status: 'online' | 'offline' | 'away' | 'busy'): void;
setActivity(activity: UserActivity['data']['activity']): void;
getOnlineUsers(): Promise<Array<{ id: string; username: string; status: string }>>;
subscribeToUserStatus(userId: string): void;
unsubscribeFromUserStatus(userId: string): void;
}
// File sharing interface
export interface IFileSharing {
uploadFile(data: FileUploadData): Promise<string>; // Returns file URL
downloadFile(fileId: string): Promise<Blob>;
deleteFile(fileId: string): Promise<void>;
getFileMetadata(fileId: string): Promise<FileShareMessage['data']>;
}
// Type guards for message type discrimination
export function isChatMessage(message: WebSocketMessage): message is ChatMessage {
return message.type === 'chat_message';
}
export function isPrivateMessage(message: WebSocketMessage): message is PrivateMessage {
return message.type === 'private_message';
}
export function isSystemNotification(message: WebSocketMessage): message is SystemNotification {
return message.type === 'system_notification';
}
export function isUserStatusChange(message: WebSocketMessage): message is UserStatusChange {
return message.type === 'user_status_change';
}
export function isErrorMessage(message: WebSocketMessage): message is ErrorMessage {
return message.type === 'error';
}
export function isCallInvitation(message: WebSocketMessage): message is CallInvitation {
return message.type === 'call_invitation';
}
// Utility functions
export function createChatMessage(
data: ChatMessage['data']
): ChatMessage {
return {
id: generateId(),
timestamp: new Date().toISOString(),
type: 'chat_message',
data
};
}
export function createPrivateMessage(
data: PrivateMessage['data']
): PrivateMessage {
return {
id: generateId(),
timestamp: new Date().toISOString(),
type: 'private_message',
data
};
}
export function createSystemNotification(
title: string,
message: string,
level: SystemNotification['data']['level'] = 'info'
): SystemNotification {
return {
id: generateId(),
timestamp: new Date().toISOString(),
type: 'system_notification',
data: { title, message, level }
};
}
function generateId(): string {
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
}
// Export all types for easy importing
export * from './socket-io-types'; // If using Socket.IO specific typesReact WebSocket Chat Component
Complete React chat application with WebSocket integration and real-time messaging
Difficulty
8/10
Estimated time
35 min
Tags
react, websocket, chat, typescript, ui
Prerequisites
React, TypeScript, WebSocket
// React WebSocket Chat Application
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { WebSocketClient } from './websocket-client';
import type { ChatMessage, UserStatusChange, UserTyping } from './websocket-types';
interface ChatRoom {
id: string;
name: string;
participantCount: number;
hasPassword: boolean;
}
interface User {
id: string;
username: string;
status: 'online' | 'offline' | 'away' | 'busy';
lastSeen?: string;
}
interface ChatState {
messages: ChatMessage[];
users: User[];
rooms: ChatRoom[];
currentRoom: string | null;
isConnected: boolean;
typingUsers: Set<string>;
}
// Chat component
export const ChatApp: React.FC<{ wsUrl: string; authToken: string; username: string }> = ({
wsUrl,
authToken,
username
}) => {
const [state, setState] = useState<ChatState>({
messages: [],
users: [],
rooms: [],
currentRoom: null,
isConnected: false,
typingUsers: new Set()
});
const [inputMessage, setInputMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [showRoomList, setShowRoomList] = useState(false);
const wsClientRef = useRef<WebSocketClient | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const typingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Initialize WebSocket connection
useEffect(() => {
if (!wsUrl || !authToken) return;
const client = new WebSocketClient(wsUrl, {
debug: process.env.NODE_ENV === 'development',
authentication: {
token: authToken,
type: 'bearer'
}
});
wsClientRef.current = client;
// Event handlers
const handleOpen = () => {
setState(prev => ({ ...prev, isConnected: true }));
};
const handleClose = () => {
setState(prev => ({ ...prev, isConnected: false }));
};
const handleError = (error: Error) => {
console.error('WebSocket error:', error);
// Could add toast notification here
};
const handleChatMessage = (message: ChatMessage) => {
setState(prev => ({
...prev,
messages: [...prev.messages, message]
}));
scrollToBottom();
};
const handleUserStatusChange = (data: UserStatusChange['data']) => {
setState(prev => ({
...prev,
users: prev.users.map(user =>
user.id === data.userId
? { ...user, status: data.status, lastSeen: data.lastSeen }
: user
)
}));
};
const handleUserTyping = (data: UserTyping['data']) => {
setState(prev => {
const newTypingUsers = new Set(prev.typingUsers);
if (data.typing) {
newTypingUsers.add(data.username);
} else {
newTypingUsers.delete(data.username);
}
return { ...prev, typingUsers: newTypingUsers };
});
};
const handleRoomsList = (rooms: ChatRoom[]) => {
setState(prev => ({ ...prev, rooms }));
};
const handleOnlineUsers = (users: User[]) => {
setState(prev => ({ ...prev, users }));
};
// Register event listeners
client.on('open', handleOpen);
client.on('close', handleClose);
client.on('error', handleError);
client.on('chat_message', handleChatMessage);
client.on('user_status_change', (msg: any) => handleUserStatusChange(msg.data));
client.on('user_typing', (msg: any) => handleUserTyping(msg.data));
client.on('rooms_list', (msg: any) => handleRoomsList(msg.data.rooms));
client.on('online_users', (msg: any) => handleOnlineUsers(msg.data));
// Request initial data
client.on('open', () => {
client.send({ type: 'get_rooms' });
client.send({ type: 'get_online_users' });
});
return () => {
client.off('open', handleOpen);
client.off('close', handleClose);
client.off('error', handleError);
client.off('chat_message', handleChatMessage);
client.off('user_status_change', (msg: any) => handleUserStatusChange(msg.data));
client.off('user_typing', (msg: any) => handleUserTyping(msg.data));
client.off('rooms_list', (msg: any) => handleRoomsList(msg.data.rooms));
client.off('online_users', (msg: any) => handleOnlineUsers(msg.data));
client.disconnect();
};
}, [wsUrl, authToken]);
// Auto-scroll to bottom on new messages
const scrollToBottom = useCallback(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
// Send message
const sendMessage = useCallback(() => {
if (!inputMessage.trim() || !wsClientRef.current) return;
const messageData = {
type: 'chat_message',
data: {
userId: '', // Will be filled by server
username,
message: inputMessage.trim(),
roomId: state.currentRoom || undefined,
messageType: 'text' as const
}
};
wsClientRef.current.send(messageData);
setInputMessage('');
stopTyping();
}, [inputMessage, username, state.currentRoom]);
// Handle typing indicators
const startTyping = useCallback(() => {
if (isTyping || !wsClientRef.current) return;
setIsTyping(true);
wsClientRef.current.send({
type: 'typing_start',
data: { roomId: state.currentRoom }
});
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}
typingTimeoutRef.current = setTimeout(() => {
stopTyping();
}, 3000);
}, [isTyping, state.currentRoom]);
const stopTyping = useCallback(() => {
if (!isTyping || !wsClientRef.current) return;
setIsTyping(false);
wsClientRef.current.send({
type: 'typing_stop',
data: { roomId: state.currentRoom }
});
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
typingTimeoutRef.current = null;
}
}, [isTyping, state.currentRoom]);
// Join room
const joinRoom = useCallback((roomId: string) => {
if (!wsClientRef.current) return;
wsClientRef.current.send({
type: 'join_room',
data: { roomId }
});
setState(prev => ({ ...prev, currentRoom: roomId }));
setShowRoomList(false);
}, []);
// Leave room
const leaveRoom = useCallback(() => {
if (!wsClientRef.current || !state.currentRoom) return;
wsClientRef.current.send({
type: 'leave_room',
data: { roomId: state.currentRoom }
});
setState(prev => ({ ...prev, currentRoom: null }));
}, [state.currentRoom]);
// Handle input change
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setInputMessage(e.target.value);
if (e.target.value.trim()) {
startTyping();
} else {
stopTyping();
}
}, [startTyping, stopTyping]);
// Handle key press
const handleKeyPress = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}, [sendMessage]);
// Get current room info
const currentRoomInfo = state.rooms.find(room => room.id === state.currentRoom);
return (
<div className="chat-app">
{/* Header */}
<div className="chat-header">
<div className="connection-status">
<span className={`status-indicator ${state.isConnected ? 'connected' : 'disconnected'}`} />
{state.isConnected ? 'Connected' : 'Disconnected'}
</div>
<div className="current-room">
{currentRoomInfo ? currentRoomInfo.name : 'General Chat'}
{state.currentRoom && (
<button onClick={leaveRoom} className="leave-room-btn">
Leave Room
</button>
)}
</div>
<div className="user-info">
{username}
</div>
</div>
<div className="chat-body">
{/* Sidebar */}
<div className="chat-sidebar">
<div className="sidebar-section">
<h3>Rooms</h3>
<button onClick={() => setShowRoomList(!showRoomList)}>
{showRoomList ? 'Hide' : 'Show'} Rooms
</button>
{showRoomList && (
<div className="room-list">
<div
className={`room-item ${!state.currentRoom ? 'active' : ''}`}
onClick={() => setState(prev => ({ ...prev, currentRoom: null }))}
>
General Chat
</div>
{state.rooms.map(room => (
<div
key={room.id}
className={`room-item ${state.currentRoom === room.id ? 'active' : ''}`}
onClick={() => joinRoom(room.id)}
>
<span>{room.name}</span>
<span className="participant-count">
{room.participantCount}
</span>
</div>
))}
</div>
)}
</div>
<div className="sidebar-section">
<h3>Online Users</h3>
<div className="user-list">
{state.users.map(user => (
<div key={user.id} className="user-item">
<span className={`user-status ${user.status}`} />
<span>{user.username}</span>
</div>
))}
</div>
</div>
</div>
{/* Main chat area */}
<div className="chat-main">
{/* Messages */}
<div className="messages-container">
{state.messages.map((message, index) => (
<div key={message.id || index} className="message">
<div className="message-header">
<span className="message-username">
{message.data.username}
</span>
<span className="message-timestamp">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="message-content">
{message.data.message}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
{/* Typing indicator */}
{state.typingUsers.size > 0 && (
<div className="typing-indicator">
{Array.from(state.typingUsers).join(', ')} {state.typingUsers.size === 1 ? 'is' : 'are'} typing...
</div>
)}
{/* Message input */}
<div className="message-input-container">
<input
type="text"
className="message-input"
placeholder="Type a message..."
value={inputMessage}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
disabled={!state.isConnected}
/>
<button
className="send-button"
onClick={sendMessage}
disabled={!inputMessage.trim() || !state.isConnected}
>
Send
</button>
</div>
</div>
</div>
<style jsx>{`
.chat-app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 1200px;
margin: 0 auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
}
.connection-status {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
}
.status-indicator.connected {
background: #4caf50;
}
.status-indicator.disconnected {
background: #f44336;
}
.chat-body {
display: flex;
flex: 1;
overflow: hidden;
}
.chat-sidebar {
width: 250px;
background: #fafafa;
border-right: 1px solid #ddd;
overflow-y: auto;
}
.sidebar-section {
padding: 1rem;
border-bottom: 1px solid #eee;
}
.room-list, .user-list {
margin-top: 0.5rem;
}
.room-item, .user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
cursor: pointer;
border-radius: 4px;
margin-bottom: 0.25rem;
}
.room-item:hover, .user-item:hover {
background: #e3f2fd;
}
.room-item.active {
background: #2196f3;
color: white;
}
.user-status {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0.5rem;
}
.user-status.online { background: #4caf50; }
.user-status.offline { background: #9e9e9e; }
.user-status.away { background: #ff9800; }
.user-status.busy { background: #f44336; }
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
padding: 1rem;
overflow-y: auto;
background: white;
}
.message {
margin-bottom: 1rem;
padding: 0.5rem;
border-radius: 8px;
background: #f9f9f9;
}
.message-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.message-username {
font-weight: bold;
color: #2196f3;
}
.message-timestamp {
color: #666;
}
.message-content {
word-wrap: break-word;
}
.typing-indicator {
padding: 0.5rem 1rem;
color: #666;
font-style: italic;
font-size: 0.875rem;
}
.message-input-container {
display: flex;
padding: 1rem;
border-top: 1px solid #ddd;
background: #fafafa;
}
.message-input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.send-button {
margin-left: 0.5rem;
padding: 0.5rem 1rem;
background: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.send-button:disabled {
background: #ccc;
cursor: not-allowed;
}
.leave-room-btn {
margin-left: 0.5rem;
padding: 0.25rem 0.5rem;
background: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
}
`}</style>
</div>
);
};Tools
Tools frequently paired with this sample
Related