javascript
exercises
exercises.js⚡javascript
/**
* 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.');