UNPKG

@xtr-dev/zod-rpc

Version:

Simple, type-safe RPC library with Zod validation and automatic TypeScript inference

143 lines 5.16 kB
import { TransportError } from '../errors.js'; /** * WebSocket transport implementation for real-time RPC communication. * Supports automatic reconnection and handles connection lifecycle management. * * @example * ```typescript * // For client connections * const transport = createWebSocketTransport('ws://localhost:8080'); * * // For server connections with existing WebSocket * const transport = createWebSocketTransport(ws, false); // No auto-reconnect * ``` * * @group Transport Layer */ export class WebSocketTransport { websocket; autoReconnect; messageHandler; reconnectAttempts = 0; maxReconnectAttempts = 5; reconnectDelay = 1000; constructor(websocket, autoReconnect = true) { this.websocket = websocket; this.autoReconnect = autoReconnect; if (!websocket) { throw new Error('WebSocket instance is required'); } this.setupEventHandlers(); } async send(message) { if (!this.isConnected()) { throw new TransportError('WebSocket is not connected'); } try { this.websocket.send(JSON.stringify(message)); } catch (error) { throw new TransportError(`Failed to send message: ${error instanceof Error ? error.message : 'Unknown error'}`); } } onMessage(handler) { this.messageHandler = handler; } async connect() { return new Promise((resolve, reject) => { if (this.isConnected()) { resolve(); return; } const onOpen = () => { this.reconnectAttempts = 0; this.websocket.removeEventListener('open', onOpen); this.websocket.removeEventListener('error', onError); resolve(); }; const onError = (_event) => { this.websocket.removeEventListener('open', onOpen); this.websocket.removeEventListener('error', onError); reject(new TransportError('Failed to connect to WebSocket')); }; this.websocket.addEventListener('open', onOpen); this.websocket.addEventListener('error', onError); }); } async disconnect() { return new Promise((resolve) => { if (!this.isConnected()) { resolve(); return; } const onClose = () => { this.websocket.removeEventListener('close', onClose); resolve(); }; this.websocket.addEventListener('close', onClose); this.websocket.close(); }); } isConnected() { return this.websocket.readyState === WebSocket.OPEN; } setupEventHandlers() { this.websocket.addEventListener('message', (event) => { try { const message = JSON.parse(event.data); this.messageHandler?.(message); } catch (error) { console.error('Failed to parse WebSocket message:', error); } }); this.websocket.addEventListener('close', () => { if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) { setTimeout(() => { this.attemptReconnect(); }, this.reconnectDelay * Math.pow(2, this.reconnectAttempts)); } }); this.websocket.addEventListener('error', (event) => { console.error('WebSocket error:', event); }); } async attemptReconnect() { this.reconnectAttempts++; try { await this.connect(); } catch (error) { console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error); } } } // Implementation export function createWebSocketTransport(urlOrWebSocket, protocolsOrAutoReconnect, autoReconnect = true) { if (typeof urlOrWebSocket === 'string') { // URL string case const url = urlOrWebSocket; const protocols = typeof protocolsOrAutoReconnect === 'boolean' ? undefined : protocolsOrAutoReconnect; const shouldAutoReconnect = typeof protocolsOrAutoReconnect === 'boolean' ? protocolsOrAutoReconnect : autoReconnect; if (!url) { throw new Error('WebSocket URL is required'); } try { const websocket = new WebSocket(url, protocols); return new WebSocketTransport(websocket, shouldAutoReconnect); } catch (error) { throw new Error(`Failed to create WebSocket: ${error instanceof Error ? error.message : 'Unknown error'}`); } } else { // WebSocket instance case const websocket = urlOrWebSocket; const shouldAutoReconnect = typeof protocolsOrAutoReconnect === 'boolean' ? protocolsOrAutoReconnect : true; if (!websocket) { throw new Error('WebSocket instance is required'); } return new WebSocketTransport(websocket, shouldAutoReconnect); } } //# sourceMappingURL=websocket.js.map