javascript

exercises

exercises.js
/**
 * 16.6 WebSockets and Real-Time Communication - Exercises
 */

/**
 * Exercise 1: Basic WebSocket Wrapper
 * Create a simple WebSocket wrapper with promise-based connect
 */
class SimpleWebSocket {
  // TODO: Implement
  // constructor(url)
  // connect(): Promise - resolves when connected
  // send(data): void - sends data (auto JSON stringify objects)
  // close(): void - closes connection
  // onMessage(callback): void - register message handler
  // onError(callback): void - register error handler
  // onClose(callback): void - register close handler
  // isConnected: boolean getter
}

// Test:
// const ws = new SimpleWebSocket('wss://example.com');
// await ws.connect();
// ws.onMessage(data => console.log(data));
// ws.send({ type: 'hello' });

/**
 * Exercise 2: Auto-Reconnecting WebSocket
 * Add automatic reconnection with exponential backoff
 */
class ReconnectingWebSocket {
  // TODO: Implement
  // constructor(url, options)
  // options: { maxRetries, baseDelay, maxDelay }
  // connect(): Promise
  // disconnect(): void - disable reconnection and close
  // on(event, callback): subscribe to events
  // Events: 'connect', 'disconnect', 'message', 'error', 'reconnecting'
  // getRetryCount(): number
}

// Test:
// const ws = new ReconnectingWebSocket('wss://example.com', {
//     maxRetries: 5,
//     baseDelay: 1000,
//     maxDelay: 30000
// });

/**
 * Exercise 3: Message Queue
 * Create a WebSocket that queues messages when disconnected
 */
class QueuedWebSocket {
  // TODO: Implement
  // constructor(url, options)
  // options: { maxQueueSize, persistQueue }
  // send(data): void - queue if not connected
  // sendImmediate(data): void - throw if not connected
  // getQueueSize(): number
  // clearQueue(): void
  // getQueue(): array
}

// Test:
// const ws = new QueuedWebSocket('wss://example.com');
// ws.send({ type: 'message', text: 'queued' });  // Queued
// await ws.connect();  // Auto-sends queued messages

/**
 * Exercise 4: Request-Response Protocol
 * Implement RPC-style request/response over WebSocket
 */
class WebSocketRPC {
  // TODO: Implement
  // constructor(ws: WebSocket-like)
  // request(method, params, timeout): Promise
  // Returns response or throws on timeout/error
  // Server expects: { id, method, params }
  // Server sends: { id, result } or { id, error }
}

// Test:
// const rpc = new WebSocketRPC(socket);
// const result = await rpc.request('getUser', { id: 123 });

/**
 * Exercise 5: Pub/Sub Client
 * Create a publish/subscribe WebSocket client
 */
class PubSubClient {
  // TODO: Implement
  // constructor(url)
  // subscribe(channel, callback): unsubscribe function
  // publish(channel, data): void
  // unsubscribe(channel): void - unsubscribe all from channel
  // getSubscriptions(): array of channels
}

// Test:
// const pubsub = new PubSubClient('wss://example.com');
// const unsub = pubsub.subscribe('news', (data) => {
//     console.log('News:', data);
// });
// pubsub.publish('news', { headline: 'Breaking news!' });

/**
 * Exercise 6: Chat Room Client
 * Build a chat room client with typing indicators
 */
class ChatRoom {
  // TODO: Implement
  // constructor(url, username)
  // connect(): Promise
  // join(room): void
  // leave(): void
  // sendMessage(text): void
  // startTyping(): void
  // stopTyping(): void
  // onMessage(callback): void
  // onUserJoined(callback): void
  // onUserLeft(callback): void
  // onTyping(callback): void
  // getUsers(): array - users in current room
}

// Test:
// const chat = new ChatRoom('wss://chat.example.com', 'john');
// await chat.connect();
// chat.join('general');
// chat.onMessage(msg => console.log(`${msg.user}: ${msg.text}`));

/**
 * Exercise 7: Binary Protocol
 * Handle binary WebSocket messages
 */
class BinaryWebSocket {
  // TODO: Implement
  // constructor(url)
  // sendBinary(buffer: ArrayBuffer): void
  // sendTypedArray(arr: TypedArray): void
  // sendBlob(blob: Blob): void
  // onBinary(callback): void - receives ArrayBuffer
  // onBlob(callback): void - receives Blob
  // setBinaryType(type: 'arraybuffer' | 'blob'): void
}

// Test:
// const ws = new BinaryWebSocket('wss://example.com');
// ws.onBinary(buffer => {
//     const view = new DataView(buffer);
//     console.log('First byte:', view.getUint8(0));
// });

/**
 * Exercise 8: Connection Pool
 * Manage multiple WebSocket connections
 */
class WebSocketPool {
  // TODO: Implement
  // constructor(urls: string[], options)
  // options: { maxConnections, loadBalance: 'round-robin' | 'random' }
  // connect(): Promise - connect to all
  // send(data): void - send via load balancer
  // broadcast(data): void - send to all
  // getConnection(index): WebSocket
  // getHealthyConnections(): array
  // close(): void - close all
}

// Test:
// const pool = new WebSocketPool([
//     'wss://server1.example.com',
//     'wss://server2.example.com'
// ]);
// await pool.connect();
// pool.send({ type: 'request' });  // Load balanced

/**
 * Exercise 9: Presence System
 * Track user presence (online/offline/away)
 */
class PresenceClient {
  // TODO: Implement
  // constructor(url, userId)
  // connect(): Promise
  // setStatus(status: 'online' | 'away' | 'offline'): void
  // setCustomStatus(message: string): void
  // onPresenceChange(userId, callback): unsubscribe function
  // getPresence(userId): { status, customStatus, lastSeen }
  // getAllPresence(): Map<userId, presence>
  // Automatically send periodic heartbeats
  // Auto-set to 'away' after idle timeout
}

/**
 * Exercise 10: Real-Time Sync
 * Synchronize state between clients
 */
class StateSync {
  // TODO: Implement
  // constructor(url, initialState)
  // connect(): Promise
  // getState(): current state
  // setState(path, value): void - update state at path
  // subscribe(path, callback): unsubscribe function
  // Handles conflicts with last-write-wins or custom resolver
  // Syncs state across all connected clients
}

// Test:
// const sync = new StateSync('wss://example.com', { count: 0 });
// await sync.connect();
// sync.subscribe('count', (value) => console.log('Count:', value));
// sync.setState('count', 1);

/**
 * BONUS CHALLENGES
 */

/**
 * Bonus 1: WebSocket with Compression
 * Implement message compression
 */
class CompressedWebSocket {
  // TODO: Compress large messages
  // Use pako or similar for gzip compression
}

/**
 * Bonus 2: WebSocket Multiplexer
 * Multiple virtual channels over single connection
 */
class WebSocketMultiplexer {
  // TODO: Create virtual channels
  // Each channel acts like separate WebSocket
}

/**
 * Bonus 3: Reliable WebSocket
 * Implement message delivery guarantees
 */
class ReliableWebSocket {
  // TODO: Add acknowledgments
  // Retry failed messages
  // Ensure ordered delivery
}

// ============================================
// SOLUTION KEY (for reference)
// ============================================

/*
// Exercise 1 Solution:
class SimpleWebSocket {
    constructor(url) {
        this.url = url;
        this.socket = null;
        this.messageHandler = null;
        this.errorHandler = null;
        this.closeHandler = null;
    }
    
    connect() {
        return new Promise((resolve, reject) => {
            this.socket = new WebSocket(this.url);
            
            this.socket.onopen = () => resolve(this);
            this.socket.onerror = (e) => {
                if (this.errorHandler) this.errorHandler(e);
                reject(e);
            };
            this.socket.onmessage = (e) => {
                if (this.messageHandler) {
                    try {
                        this.messageHandler(JSON.parse(e.data));
                    } catch {
                        this.messageHandler(e.data);
                    }
                }
            };
            this.socket.onclose = (e) => {
                if (this.closeHandler) this.closeHandler(e);
            };
        });
    }
    
    send(data) {
        const msg = typeof data === 'object' ? JSON.stringify(data) : data;
        this.socket.send(msg);
    }
    
    close() {
        this.socket.close();
    }
    
    onMessage(callback) { this.messageHandler = callback; }
    onError(callback) { this.errorHandler = callback; }
    onClose(callback) { this.closeHandler = callback; }
    
    get isConnected() {
        return this.socket && this.socket.readyState === WebSocket.OPEN;
    }
}

// Exercise 2 Solution:
class ReconnectingWebSocket {
    constructor(url, options = {}) {
        this.url = url;
        this.options = {
            maxRetries: 5,
            baseDelay: 1000,
            maxDelay: 30000,
            ...options
        };
        this.socket = null;
        this.retryCount = 0;
        this.shouldReconnect = true;
        this.listeners = new Map();
    }
    
    connect() {
        return new Promise((resolve, reject) => {
            this.socket = new WebSocket(this.url);
            
            this.socket.onopen = () => {
                this.retryCount = 0;
                this.emit('connect');
                resolve(this);
            };
            
            this.socket.onclose = (e) => {
                this.emit('disconnect', e);
                if (this.shouldReconnect && !e.wasClean) {
                    this.attemptReconnect();
                }
            };
            
            this.socket.onerror = (e) => {
                this.emit('error', e);
                reject(e);
            };
            
            this.socket.onmessage = (e) => {
                this.emit('message', JSON.parse(e.data));
            };
        });
    }
    
    attemptReconnect() {
        if (this.retryCount >= this.options.maxRetries) {
            this.emit('error', new Error('Max retries exceeded'));
            return;
        }
        
        this.retryCount++;
        const delay = Math.min(
            this.options.baseDelay * Math.pow(2, this.retryCount - 1),
            this.options.maxDelay
        );
        
        this.emit('reconnecting', { attempt: this.retryCount, delay });
        setTimeout(() => this.connect().catch(() => {}), delay);
    }
    
    disconnect() {
        this.shouldReconnect = false;
        if (this.socket) this.socket.close();
    }
    
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(cb => cb(data));
    }
    
    getRetryCount() {
        return this.retryCount;
    }
}
*/

console.log('Complete the exercises above!');
console.log('Check the solution key at the bottom for guidance.');
Exercises - JavaScript Tutorial | DeepML