@xtr-dev/zod-rpc
Version:
Simple, type-safe RPC library with Zod validation and automatic TypeScript inference
143 lines • 5.16 kB
JavaScript
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