Exemples WebSocket

Exemples de communication en temps réel utilisant le protocole WebSocket incluant chat, notifications et streaming de données

💻 Client WebSocket JavaScript javascript

🟢 simple ⭐⭐

Implémentation complète de client WebSocket avec reconnexion, heartbeat et gestion des messages

⏱️ 20 min 🏷️ 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 };
}

💻 Serveur WebSocket Node.js javascript

🟡 intermediate ⭐⭐⭐⭐

Implémentation complète de serveur WebSocket avec Express, Socket.IO et gestion des salles

⏱️ 30 min 🏷️ 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 };

💻 Types et Interfaces WebSocket TypeScript typescript

🟡 intermediate ⭐⭐⭐

Définitions de types et interfaces complètes TypeScript pour applications WebSocket

⏱️ 25 min 🏷️ 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 types

💻 Composant Chat WebSocket React typescript

🟡 intermediate ⭐⭐⭐⭐

Application chat React complète avec intégration WebSocket et messagerie en temps réel

⏱️ 35 min 🏷️ 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<NodeJS.Timeout | 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>
    );
};