# Build WebSocket Server

> Automatically generate production-ready WebSocket servers with Socket.IO or native WebSocket implementations featuring room management, authentication, broadcasting, presence tracking, and resilient connection handling…

- Skill: `tools-only/build-websocket-server-2` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/build-websocket-server-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/build-websocket-server-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-29
- Page: https://skillmd.com/skills/tools-only/build-websocket-server-2

---


# Build WebSocket Server

Automatically generate production-ready WebSocket servers with Socket.IO or native WebSocket implementations featuring room management, authentication, broadcasting, presence tracking, and resilient connection handling for real-time applications.

## When to Use This Command

Use `/build-websocket-server` when you need to:
- Build real-time chat applications or messaging systems
- Implement live collaboration features (Google Docs-style)
- Create real-time dashboards with live data updates
- Build multiplayer game servers
- Stream live data feeds (stock prices, sports scores)
- Implement real-time notifications and alerts

DON'T use this when:
- Simple request-response patterns suffice (use REST)
- Unidirectional server-to-client updates only (consider SSE)
- Very high throughput binary streaming (consider WebRTC)
- Message ordering is critical (consider message queues)

## Design Decisions

This command implements **Socket.IO** as the primary approach because:
- Automatic fallback to long-polling for compatibility
- Built-in room and namespace management
- Automatic reconnection with exponential backoff
- Binary data support with automatic serialization
- Event acknowledgments and timeouts
- Extensive middleware ecosystem

**Alternative considered: Native WebSocket (ws)**
- Lower overhead and better performance
- No automatic fallbacks
- Manual implementation of features
- Recommended for simple, high-performance needs

**Alternative considered: Server-Sent Events (SSE)**
- Simpler for unidirectional communication
- Works over HTTP/2
- No bidirectional support
- Recommended for news feeds, notifications

## Prerequisites

Before running this command:
1. Choose Socket.IO vs native WebSocket
2. Design event/message protocol
3. Plan authentication strategy
4. Define room/channel structure
5. Determine scaling approach (Redis adapter for multi-server)

## Implementation Process

### Step 1: Initialize WebSocket Server
Set up Socket.IO or ws server with proper configuration.

### Step 2: Implement Authentication
Add middleware for connection authentication and authorization.

### Step 3: Define Event Handlers
Create handlers for all client events and server broadcasts.

### Step 4: Add Room Management
Implement room joining, leaving, and broadcasting logic.

### Step 5: Configure Resilience
Set up reconnection, heartbeat, and error handling.

## Output Format

The command generates:
- `websocket/server.js` - Main WebSocket server setup
- `websocket/handlers/` - Event handler modules
- `websocket/middleware/` - Auth and validation middleware
- `websocket/rooms/` - Room management logic
- `websocket/presence/` - User presence tracking
- `websocket/client.js` - Client SDK/library
- `config/websocket.json` - Server configuration
- `tests/websocket/` - Integration tests

## Code Examples

### Example 1: Full-Featured Chat Server with Socket.IO

```javascript
// websocket/server.js - Socket.IO server with all features
const { Server } = require('socket.io');
const { createAdapter } = require('@socket.io/redis-adapter');
const { instrument } = require('@socket.io/admin-ui');
const jwt = require('jsonwebtoken');
const Redis = require('ioredis');
const crypto = require('crypto');

class WebSocketServer {
    constructor(httpServer, config = {}) {
        this.config = {
            cors: {
                origin: config.corsOrigin || ['http://localhost:3000'],
                credentials: true
            },
            pingTimeout: config.pingTimeout || 60000,
            pingInterval: config.pingInterval || 25000,
            transports: config.transports || ['websocket', 'polling'],
            maxHttpBufferSize: config.maxHttpBufferSize || 1e6, // 1MB
            ...config
        };

        this.io = new Server(httpServer, this.config);
        this.redis = new Redis(config.redisUrl);
        this.pubClient = this.redis;
        this.subClient = this.redis.duplicate();

        this.rooms = new Map();
        this.users = new Map();
        this.presence = new Map();

        this.setupRedisAdapter();
        this.setupMiddleware();
        this.setupEventHandlers();
        this.setupAdminUI();
        this.startPresenceTracking();
    }

    setupRedisAdapter() {
        // Enable horizontal scaling with Redis
        this.io.adapter(createAdapter(this.pubClient, this.subClient));
    }

    setupMiddleware() {
        // Authentication middleware
        this.io.use(async (socket, next) => {
            try {
                const token = socket.handshake.auth.token;
                if (!token) {
                    return next(new Error('Authentication required'));
                }

                const decoded = jwt.verify(token, process.env.JWT_SECRET);
                const user = await this.validateUser(decoded.userId);

                if (!user) {
                    return next(new Error('Invalid user'));
                }

                // Attach user data to socket
                socket.userId = user.id;
                socket.user = user;
                socket.sessionId = crypto.randomBytes(8).toString('hex');

                // Rate limiting per user
                const rateLimitKey = `ratelimit:${user.id}`;
                const requests = await this.redis.incr(rateLimitKey);

                if (requests === 1) {
                    await this.redis.expire(rateLimitKey, 60); // 1 minute window
                }

                if (requests > 100) { // 100 messages per minute
                    return next(new Error('Rate limit exceeded'));
                }

                next();
            } catch (error) {
                next(new Error('Authentication failed: ' + error.message));
            }
        });

        // Connection logging middleware
        this.io.use((socket, next) => {
            console.log(`New connection from ${socket.handshake.address}`);
            socket.on('error', (error) => {
                console.error(`Socket error for ${socket.userId}:`, error);
            });
            next();
        });
    }

    setupEventHandlers() {
        this.io.on('connection', (socket) => {
            this.handleConnection(socket);

            // Room management
            socket.on('join-room', (roomId, callback) =>
                this.handleJoinRoom(socket, roomId, callback)
            );

            socket.on('leave-room', (roomId, callback) =>
                this.handleLeaveRoom(socket, roomId, callback)
            );

            // Messaging
            socket.on('message', (data, callback) =>
                this.handleMessage(socket, data, callback)
            );

            socket.on('typing', (data) =>
                this.handleTyping(socket, data)
            );

            socket.on('stop-typing', (data) =>
                this.handleStopTyping(socket, data)
            );

            // Presence
            socket.on('update-status', (status) =>
                this.handleStatusUpdate(socket, status)
            );

            // Private messaging
            socket.on('private-message', (data, callback) =>
                this.handlePrivateMessage(socket, data, callback)
            );

            // File sharing
            socket.on('upload-start', (data, callback) =>
                this.handleUploadStart(socket, data, callback)
            );

            socket.on('upload-chunk', (data, callback) =>
                this.handleUploadChunk(socket, data, callback)
            );

            // Voice/Video calls
            socket.on('call-user', (data, callback) =>
                this.handleCallUser(socket, data, callback)
            );

            socket.on('call-answer', (data) =>
                this.handleCallAnswer(socket, data)
            );

            socket.on('ice-candidate', (data) =>
                this.handleIceCandidate(socket, data)
            );

            // Disconnection
            socket.on('disconnect', (reason) =>
                this.handleDisconnection(socket, reason)
            );
        });
    }

    async handleConnection(socket) {
        console.log(`User ${socket.userId} connected (${socket.sessionId})`);

        // Track user connection
        if (!this.users.has(socket.userId)) {
            this.users.set(socket.userId, new Set());
        }
        this.users.get(socket.userId).add(socket.id);

        // Update presence
        await this.updatePresence(socket.userId, 'online');

        // Send connection success with user data
        socket.emit('connected', {
            sessionId: socket.sessionId,
            userId: socket.userId,
            serverTime: Date.now()
        });

        // Rejoin previous rooms from session
        const previousRooms = await this.getUserRooms(socket.userId);
        for (const roomId of previousRooms) {
            socket.join(roomId);
            socket.to(roomId).emit('user-joined', {
                userId: socket.userId,
                user: socket.user,
                roomId
            });
        }

        // Send pending messages
        const pendingMessages = await this.getPendingMessages(socket.userId);
        if (pendingMessages.length > 0) {
            socket.emit('pending-messages', pendingMessages);
        }
    }

    async handleJoinRoom(socket, roomId, callback) {
        try {
            // Validate room access
            const hasAccess = await this.validateRoomAccess(socket.userId, roomId);
            if (!hasAccess) {
                return callback({ error: 'Access denied' });
            }

            // Join the room
            socket.join(roomId);

            // Track room membership
            if (!this.rooms.has(roomId)) {
                this.rooms.set(roomId, new Set());
            }
            this.rooms.get(roomId).add(socket.userId);

            // Get room info
            const roomInfo = await this.getRoomInfo(roomId);
            const members = await this.getRoomMembers(roomId);

            // Notify others in room
            socket.to(roomId).emit('user-joined', {
                userId: socket.userId,
                user: socket.user,
                roomId
            });

            // Send room state to joiner
            callback({
                success: true,
                room: roomInfo,
                members: members,
                recentMessages: await this.getRecentMessages(roomId)
            });

            // Update user's room list
            await this.addUserRoom(socket.userId, roomId);

        } catch (error) {
            console.error('Join room error:', error);
            callback({ error: error.message });
        }
    }

    async handleMessage(socket, data, callback) {
        try {
            // Validate message
            if (!data.roomId || !data.content) {
                return callback({ error: 'Invalid message format' });
            }

            // Check room membership
            if (!socket.rooms.has(data.roomId)) {
                return callback({ error: 'Not in room' });
            }

            // Create message object
            const message = {
                id: crypto.randomBytes(16).toString('hex'),
                roomId: data.roomId,
                userId: socket.userId,
                user: socket.user,
                content: data.content,
                type: data.type || 'text',
                timestamp: Date.now(),
                edited: false,
                deleted: false
            };

            // Store message
            await this.storeMessage(message);

            // Broadcast to room
            this.io.to(data.roomId).emit('new-message', message);

            // Send acknowledgment
            callback({
                success: true,
                messageId: message.id,
                timestamp: message.timestamp
            });

            // Update room activity
            await this.updateRoomActivity(data.roomId);

            // Send push notifications to offline users
            await this.sendPushNotifications(data.roomId, message, socket.userId);

        } catch (error) {
            console.error('Message error:', error);
            callback({ error: error.message });
        }
    }

    async handleTyping(socket, data) {
        if (data.roomId && socket.rooms.has(data.roomId)) {
            socket.to(data.roomId).emit('user-typing', {
                userId: socket.userId,
                user: socket.user,
                roomId: data.roomId
            });

            // Auto-stop typing after 3 seconds
            setTimeout(() => {
                socket.to(data.roomId).emit('user-stopped-typing', {
                    userId: socket.userId,
                    roomId: data.roomId
                });
            }, 3000);
        }
    }

    async handlePrivateMessage(socket, data, callback) {
        try {
            const targetUserId = data.targetUserId;
            const targetSockets = this.users.get(targetUserId);

            const message = {
                id: crypto.randomBytes(16).toString('hex'),
                fromUserId: socket.userId,
                fromUser: socket.user,
                toUserId: targetUserId,
                content: data.content,
                timestamp: Date.now(),
                read: false
            };

            // Store private message
            await this.storePrivateMessage(message);

            if (targetSockets && targetSockets.size > 0) {
                // User is online, deliver to all their connections
                for (const socketId of targetSockets) {
                    this.io.to(socketId).emit('private-message', message);
                }
                message.delivered = true;
            } else {
                // User is offline, queue for later
                await this.queueMessage(targetUserId, message);
                message.delivered = false;
            }

            callback({
                success: true,
                messageId: message.id,
                delivered: message.delivered
            });

        } catch (error) {
            console.error('Private message error:', error);
            callback({ error: error.message });
        }
    }

    async handleDisconnection(socket, reason) {
        console.log(`User ${socket.userId} disconnected: ${reason}`);

        // Remove socket from user's connections
        const userSockets = this.users.get(socket.userId);
        if (userSockets) {
            userSockets.delete(socket.id);

            // If no more connections, mark as offline
            if (userSockets.size === 0) {
                this.users.delete(socket.userId);
                await this.updatePresence(socket.userId, 'offline');

                // Notify rooms user was in
                for (const roomId of socket.rooms) {
                    if (roomId !== socket.id) { // Skip default room
                        socket.to(roomId).emit('user-left', {
                            userId: socket.userId,
                            roomId
                        });
                    }
                }
            }
        }

        // Clean up room memberships
        for (const [roomId, members] of this.rooms.entries()) {
            members.delete(socket.userId);
            if (members.size === 0) {
                this.rooms.delete(roomId);
            }
        }
    }

    // Presence tracking
    startPresenceTracking() {
        setInterval(async () => {
            for (const [userId, status] of this.presence.entries()) {
                // Check if user has active connections
                if (!this.users.has(userId)) {
                    this.presence.set(userId, 'offline');
                    await this.broadcastPresenceUpdate(userId, 'offline');
                }
            }
        }, 30000); // Check every 30 seconds
    }

    async updatePresence(userId, status) {
        this.presence.set(userId, status);
        await this.redis.setex(`presence:${userId}`, 120, status);
        await this.broadcastPresenceUpdate(userId, status);
    }

    async broadcastPresenceUpdate(userId, status) {
        // Get all rooms user is in
        const userRooms = await this.getUserRooms(userId);

        for (const roomId of userRooms) {
            this.io.to(roomId).emit('presence-update', {
                userId,
                status,
                timestamp: Date.now()
            });
        }
    }

    // Helper methods
    async validateUser(userId) {
        // Implement user validation
        return { id: userId, name: `User ${userId}` };
    }

    async validateRoomAccess(userId, roomId) {
        // Implement room access validation
        return true;
    }

    async getRoomInfo(roomId) {
        // Implement room info retrieval
        return { id: roomId, name: `Room ${roomId}` };
    }

    async getRoomMembers(roomId) {
        // Implement member list retrieval
        return Array.from(this.rooms.get(roomId) || []);
    }

    async getRecentMessages(roomId, limit = 50) {
        // Implement message history retrieval
        return [];
    }

    async storeMessage(message) {
        // Implement message storage
        await this.redis.lpush(
            `messages:${message.roomId}`,
            JSON.stringify(message)
        );
        await this.redis.ltrim(`messages:${message.roomId}`, 0, 999);
    }

    async getUserRooms(userId) {
        // Implement user room list retrieval
        return [];
    }

    async getPendingMessages(userId) {
        // Implement pending message retrieval
        return [];
    }

    setupAdminUI() {
        // Enable admin UI for monitoring
        instrument(this.io, {
            auth: {
                type: 'basic',
                username: process.env.SOCKETIO_ADMIN_USER || 'admin',
                password: process.env.SOCKETIO_ADMIN_PASSWORD || 'admin'
            },
            readonly: false
        });
    }

    // Public methods for external use
    async broadcast(event, data) {
        this.io.emit(event, data);
    }

    async broadcastToRoom(roomId, event, data) {
        this.io.to(roomId).emit(event, data);
    }

    async sendToUser(userId, event, data) {
        const userSockets = this.users.get(userId);
        if (userSockets) {
            for (const socketId of userSockets) {
                this.io.to(socketId).emit(event, data);
            }
        }
    }

    getOnlineUsers() {
        return Array.from(this.users.keys());
    }

    getRoomMembers(roomId) {
        return Array.from(this.rooms.get(roomId) || []);
    }
}

// Initialize server
const http = require('http');
const express = require('express');

const app = express();
const server = http.createServer(app);

const wsServer = new WebSocketServer(server, {
    corsOrigin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'],
    redisUrl: process.env.REDIS_URL || 'redis://localhost:6379'
});

// REST endpoints for WebSocket management
app.get('/ws/stats', (req, res) => {
    res.json({
        onlineUsers: wsServer.getOnlineUsers().length,
        rooms: wsServer.rooms.size,
        connections: wsServer.io.engine.clientsCount
    });
});

app.post('/ws/broadcast', express.json(), async (req, res) => {
    const { event, data } = req.body;
    await wsServer.broadcast(event, data);
    res.json({ success: true });
});

server.listen(3000, () => {
    console.log('WebSocket server running on port 3000');
});

module.exports = { WebSocketServer };
```

### Example 2: High-Performance Native WebSocket Implementation

```javascript
// websocket/native-server.js - Pure WebSocket with advanced features
const WebSocket = require('ws');
const http = require('http');
const crypto = require('crypto');
const EventEmitter = require('events');

class NativeWebSocketServer extends EventEmitter {
    constructor(server, options = {}) {
        super();

        this.wss = new WebSocket.Server({
            server,
            perMessageDeflate: options.compression !== false,
            maxPayload: options.maxPayload || 10 * 1024 * 1024, // 10MB
            clientTracking: true,
            ...options
        });

        this.clients = new Map();
        this.rooms = new Map();
        this.messageHandlers = new Map();
        this.heartbeatInterval = options.heartbeatInterval || 30000;

        this.setupEventHandlers();
        this.startHeartbeat();
    }

    setupEventHandlers() {
        this.wss.on('connection', (ws, req) => {
            this.handleConnection(ws, req);
        });

        this.wss.on('error', (error) => {
            console.error('WebSocket server error:', error);
            this.emit('error', error);
        });
    }

    handleConnection(ws, req) {
        // Generate client ID
        const clientId = crypto.randomBytes(16).toString('hex');

        // Parse authentication from URL or headers
        const token = this.extractToken(req);
        const user = this.authenticateToken(token);

        if (!user) {
            ws.close(1008, 'Unauthorized');
            return;
        }

        // Setup client
        const client = {
            id: clientId,
            ws: ws,
            user: user,
            rooms: new Set(),
            isAlive: true,
            joinedAt: Date.now(),
            lastActivity: Date.now(),
            messageCount: 0
        };

        this.clients.set(clientId, client);

        // Send connection acknowledgment
        this.send(client, {
            type: 'connected',
            clientId: clientId,
            timestamp: Date.now()
        });

        // Setup client event handlers
        ws.on('message', (data) => this.handleMessage(client, data));
        ws.on('pong', () => this.handlePong(client));
        ws.on('close', (code, reason) => this.handleClose(client, code, reason));
        ws.on('error', (error) => this.handleError(client, error));

        this.emit('connection', client);
    }

    handleMessage(client, data) {
        try {
            // Update activity
            client.lastActivity = Date.now();
            client.messageCount++;

            // Parse message
            let message;
            if (typeof data === 'string') {
                message = JSON.parse(data);
            } else {
                // Handle binary data
                message = this.parseBinaryMessage(data);
            }

            // Rate limiting
            if (client.messageCount > 100) {
                const timeDiff = Date.now() - client.joinedAt;
                if (timeDiff < 60000) { // Less than 1 minute
                    this.send(client, {
                        type: 'error',
                        error: 'Rate limit exceeded'
                    });
                    client.ws.close(1008, 'Rate limit exceeded');
                    return;
                }
                client.messageCount = 0;
                client.joinedAt = Date.now();
            }

            // Route message to handler
            const handler = this.messageHandlers.get(message.type);
            if (handler) {
                handler(client, message);
            } else {
                this.handleDefaultMessage(client, message);
            }

            this.emit('message', client, message);

        } catch (error) {
            console.error('Message handling error:', error);
            this.send(client, {
                type: 'error',
                error: 'Invalid message format'
            });
        }
    }

    handleDefaultMessage(client, message) {
        switch (message.type) {
            case 'join':
                this.joinRoom(client, message.room);
                break;
            case 'leave':
                this.leaveRoom(client, message.room);
                break;
            case 'broadcast':
                this.broadcastToRoom(message.room, message.data, client);
                break;
            case 'ping':
                this.send(client, { type: 'pong', timestamp: Date.now() });
                break;
            default:
                this.emit('custom-message', client, message);
        }
    }

    joinRoom(client, roomId) {
        if (!roomId) return;

        // Add client to room
        if (!this.rooms.has(roomId)) {
            this.rooms.set(roomId, new Set());
        }
        this.rooms.get(roomId).add(client.id);
        client.rooms.add(roomId);

        // Notify room members
        this.broadcastToRoom(roomId, {
            type: 'user-joined',
            userId: client.user.id,
            roomId: roomId,
            timestamp: Date.now()
        }, client);

        // Send confirmation
        this.send(client, {
            type: 'joined',
            roomId: roomId,
            members: this.getRoomMembers(roomId)
        });

        this.emit('room-joined', client, roomId);
    }

    leaveRoom(client, roomId) {
        if (!roomId || !client.rooms.has(roomId)) return;

        // Remove client from room
        const room = this.rooms.get(roomId);
        if (room) {
            room.delete(client.id);
            if (room.size === 0) {
                this.rooms.delete(roomId);
            }
        }
        client.rooms.delete(roomId);

        // Notify room members
        this.broadcastToRoom(roomId, {
            type: 'user-left',
            userId: client.user.id,
            roomId: roomId,
            timestamp: Date.now()
        });

        // Send confirmation
        this.send(client, {
            type: 'left',
            roomId: roomId
        });

        this.emit('room-left', client, roomId);
    }

    broadcastToRoom(roomId, data, excludeClient = null) {
        const room = this.rooms.get(roomId);
        if (!room) return;

        const message = typeof data === 'object' ? JSON.stringify(data) : data;

        for (const clientId of room) {
            if (excludeClient && clientId === excludeClient.id) continue;

            const client = this.clients.get(clientId);
            if (client && client.ws.readyState === WebSocket.OPEN) {
                client.ws.send(message);
            }
        }
    }

    broadcast(data, excludeClient = null) {
        const message = typeof data === 'object' ? JSON.stringify(data) : data;

        this.clients.forEach((client) => {
            if (excludeClient && client.id === excludeClient.id) return;

            if (client.ws.readyState === WebSocket.OPEN) {
                client.ws.send(message);
            }
        });
    }

    send(client, data) {
        if (client.ws.readyState === WebSocket.OPEN) {
            const message = typeof data === 'object' ? JSON.stringify(data) : data;
            client.ws.send(message);
        }
    }

    handlePong(client) {
        client.isAlive = true;
    }

    handleClose(client, code, reason) {
        console.log(`Client ${client.id} disconnected: ${code} - ${reason}`);

        // Leave all rooms
        for (const roomId of client.rooms) {
            this.leaveRoom(client, roomId);
        }

        // Remove client
        this.clients.delete(client.id);

        this.emit('disconnection', client, code, reason);
    }

    handleError(client, error) {
        console.error(`Client ${client.id} error:`, error);
        this.emit('client-error', client, error);
    }

    startHeartbeat() {
        setInterval(() => {
            this.clients.forEach((client) => {
                if (!client.isAlive) {
                    console.log(`Terminating inactive client ${client.id}`);
                    client.ws.terminate();
                    this.clients.delete(client.id);
                    return;
                }

                client.isAlive = false;
                client.ws.ping();
            });
        }, this.heartbeatInterval);
    }

    // Utility methods
    extractToken(req) {
        // Extract from query string or authorization header
        const url = new URL(req.url, `http://${req.headers.host}`);
        return url.searchParams.get('token') ||
               req.headers.authorization?.replace('Bearer ', '');
    }

    authenticateToken(token) {
        // Implement token validation
        if (!token) return null;
        return { id: 'user123', name: 'Test User' };
    }

    getRoomMembers(roomId) {
        const room = this.rooms.get(roomId);
        if (!room) return [];

        return Array.from(room).map(clientId => {
            const client = this.clients.get(clientId);
            return client ? client.user : null;
        }).filter(Boolean);
    }

    // Public API
    registerHandler(type, handler) {
        this.messageHandlers.set(type, handler);
    }

    getClient(clientId) {
        return this.clients.get(clientId);
    }

    getClients() {
        return Array.from(this.clients.values());
    }

    getRooms() {
        return Array.from(this.rooms.keys());
    }

    close() {
        clearInterval(this.heartbeatTimer);
        this.wss.close();
    }
}

// Usage
const server = http.createServer();
const wsServer = new NativeWebSocketServer(server, {
    compression: true,
    heartbeatInterval: 30000
});

// Register custom message handlers
wsServer.registerHandler('chat', (client, message) => {
    wsServer.broadcastToRoom(message.room, {
        type: 'chat',
        from: client.user.name,
        content: message.content,
        timestamp: Date.now()
    });
});

wsServer.registerHandler('file-upload', (client, message) => {
    // Handle file upload
    console.log(`File upload from ${client.user.name}:`, message.filename);
});

// Event listeners
wsServer.on('connection', (client) => {
    console.log(`New connection: ${client.user.name}`);
});

wsServer.on('room-joined', (client, roomId) => {
    console.log(`${client.user.name} joined room ${roomId}`);
});

server.listen(3000, () => {
    console.log('Native WebSocket server running on port 3000');
});
```

### Example 3: Client SDK and Testing

```javascript
// websocket/client.js - Browser/Node.js client
class WebSocketClient {
    constructor(url, options = {}) {
        this.url = url;
        this.options = options;
        this.ws = null;
        this.messageHandlers = new Map();
        this.messageQueue = [];
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
        this.reconnectDelay = options.reconnectDelay || 1000;
        this.isConnected = false;
    }

    connect() {
        return new Promise((resolve, reject) => {
            try {
                this.ws = new WebSocket(this.url);

                this.ws.onopen = () => {
                    console.log('WebSocket connected');
                    this.isConnected = true;
                    this.reconnectAttempts = 0;

                    // Send queued messages
                    while (this.messageQueue.length > 0) {
                        const message = this.messageQueue.shift();
                        this.send(message);
                    }

                    resolve();
                };

                this.ws.onmessage = (event) => {
                    this.handleMessage(event.data);
                };

                this.ws.onerror = (error) => {
                    console.error('WebSocket error:', error);
                    reject(error);
                };

                this.ws.onclose = (event) => {
                    console.log('WebSocket closed:', event.code, event.reason);
                    this.isConnected = false;
                    this.handleReconnect();
                };

            } catch (error) {
                reject(error);
            }
        });
    }

    handleMessage(data) {
        try {
            const message = JSON.parse(data);
            const handler = this.messageHandlers.get(message.type);

            if (handler) {
                handler(message);
            } else {
                console.log('Unhandled message:', message);
            }
        } catch (error) {
            console.error('Message parsing error:', error);
        }
    }

    on(type, handler) {
        this.messageHandlers.set(type, handler);
    }

    send(data) {
        const message = typeof data === 'object' ? JSON.stringify(data) : data;

        if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(message);
        } else {
            // Queue message for later
            this.messageQueue.push(data);
        }
    }

    handleReconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('Max reconnection attempts reached');
            return;
        }

        this.reconnectAttempts++;
        const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);

        console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);

        setTimeout(() => {
            this.connect().catch(console.error);
        }, delay);
    }

    close() {
        if (this.ws) {
            this.ws.close();
        }
    }
}

// tests/websocket.test.js
const { WebSocketServer } = require('../websocket/server');
const WebSocketClient = require('../websocket/client');

describe('WebSocket Server Tests', () => {
    let server;
    let client;

    beforeEach(async () => {
        server = new WebSocketServer(3001);
        await server.start();

        client = new WebSocketClient('ws://localhost:3001', {
            token: 'test-token'
        });
    });

    afterEach(async () => {
        client.close();
        await server.close();
    });

    test('should connect successfully', async () => {
        await client.connect();
        expect(client.isConnected).toBe(true);
    });

    test('should join room', async () => {
        await client.connect();

        const joined = new Promise((resolve) => {
            client.on('joined', resolve);
        });

        client.send({ type: 'join', room: 'test-room' });

        const result = await joined;
        expect(result.roomId).toBe('test-room');
    });

    test('should broadcast messages', async () => {
        const client2 = new WebSocketClient('ws://localhost:3001');
        await client.connect();
        await client2.connect();

        // Both join same room
        client.send({ type: 'join', room: 'test-room' });
        client2.send({ type: 'join', room: 'test-room' });

        // Set up message listener
        const messageReceived = new Promise((resolve) => {
            client2.on('chat', resolve);
        });

        // Send message
        client.send({
            type: 'chat',
            room: 'test-room',
            content: 'Hello World'
        });

        const message = await messageReceived;
        expect(message.content).toBe('Hello World');
    });

    test('should handle reconnection', async () => {
        await client.connect();

        // Force disconnect
        server.disconnectClient(client.id);

        // Wait for reconnection
        await new Promise(resolve => setTimeout(resolve, 2000));

        expect(client.isConnected).toBe(true);
        expect(client.reconnectAttempts).toBeGreaterThan(0);
    });
});
```

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| "WebSocket connection failed" | Network issues or wrong URL | Check network and WebSocket URL |
| "Authentication required" | Missing or invalid token | Include valid auth token |
| "Rate limit exceeded" | Too many messages | Implement client-side throttling |
| "Maximum payload size exceeded" | Message too large | Split large messages or use chunking |
| "Connection timeout" | No heartbeat response | Check network stability |

## Configuration Options

**Server Options**
- `perMessageDeflate`: Enable compression
- `maxPayload`: Maximum message size
- `pingInterval`: Heartbeat frequency
- `pingTimeout`: Heartbeat timeout
- `transports`: Allowed transports (Socket.IO)

**Client Options**
- `reconnection`: Enable auto-reconnection
- `reconnectionAttempts`: Max reconnect tries
- `reconnectionDelay`: Initial reconnect delay
- `timeout`: Connection timeout
- `auth`: Authentication data

## Best Practices

DO:
- Implement heartbeat/ping-pong for connection health
- Use rooms/namespaces for logical grouping
- Add authentication before accepting connections
- Implement message acknowledgments for critical data
- Use compression for text data
- Monitor connection count and memory usage

DON'T:
- Send large payloads without chunking
- Store state only in memory without persistence
- Skip authentication for production
- Ignore connection limits
- Use synchronous operations in handlers
- Broadcast sensitive data to all clients

## Performance Considerations

- Use binary frames for large data transfers
- Implement message batching for high-frequency updates
- Use Redis adapter for horizontal scaling
- Enable compression for text-heavy payloads
- Limit connection count per IP/user
- Implement connection pooling for database queries

## Security Considerations

- Always use WSS (WebSocket Secure) in production
- Validate all incoming messages
- Implement rate limiting per connection
- Use JWT or session-based authentication
- Sanitize user input before broadcasting
- Implement CORS properly
- Monitor for abnormal connection patterns

## Related Commands

- `/webhook-handler-creator` - Handle webhooks
- `/real-time-sync` - Implement data synchronization
- `/message-queue-setup` - Configure message queues
- `/pubsub-system` - Build pub/sub architecture
- `/notification-service` - Push notifications

## Version History

- v1.0.0 (2024-10): Initial implementation with Socket.IO and native WebSocket
- Planned v1.1.0: Add WebRTC signaling server and video streaming support
