WebSocket Transport
The WebSocket transport provides persistent connections for IRPC, offering lower latency and real-time capabilities compared to HTTP.
Overview
WebSocket transport maintains a single persistent connection that handles multiple IRPC calls without reconnection overhead. This eliminates HTTP handshake latency and enables real-time communication patterns.
Installation
npm install @irpclib/wsBasic Usage
1. Declare Functions (Shared)
// rpc/hello/index.ts
import { irpc } from '../lib/module.js';
export type HelloFn = (name: string) => Promise<string>;
export const hello = irpc.declare<HelloFn>('hello', () => '');2. Implement Handlers (Server)
// rpc/hello/constructor.ts
import { irpc } from '../lib/module.js';
import { hello } from './index.js';
irpc.construct(hello, async (name) => `Hello ${name}`);3. Server Setup
The WebSocket upgrade happens at connection time, but IRPC messages arrive later during the session. Extract application-level values (e.g., auth tokens) from the upgrade request in fetch, attach them to ws.data, and forward them as initContext on each resolve call. This gives hooks the same standardized keys as the HTTP router.
// server.ts
import { WebSocketRouter } from '@irpclib/ws/router';
import { irpc, transport } from './lib/module.js';
import './rpc/hello/constructor.js';
const router = new WebSocketRouter(irpc, transport, {
fileBufferTTL: 30000, // Clean up orphaned binary frames after 30s (default)
});
Bun.serve({
port: 8080,
fetch(req, server) {
const token = req.headers.get('authorization');
if (server.upgrade(req, { data: { token } })) return;
return new Response('WebSocket server running');
},
websocket: {
async message(ws, message) {
await router.resolve(message.toString(), ws, [
['token', ws.data.token],
]);
},
close(ws) {
// Abort all active streams for this connection
router.disconnect(ws);
},
},
});4. Client Usage
// client.ts
import { hello } from './rpc/hello/index.js';
const message = await hello('John');
console.log(message); // 'Hello John'Configuration
WebSocketTransportConfig
type WebSocketTransportConfig = {
// WebSocket connection
url: string; // WebSocket URL to connect to
protocols?: string[]; // Sub-protocols for the connection
// Reconnection
autoReconnect?: boolean; // Enable automatic reconnection (default: true)
maxReconnectAttempts?: number; // Maximum reconnection attempts (default: 5)
reconnectDelay?: number; // Delay between reconnection attempts (default: 1000ms)
// Timeouts
connectionTimeout?: number; // Connection establishment timeout (default: 10000ms)
timeout?: number; // Request timeout (inherited from base)
// Batching
debounce?: number | boolean; // Batching delay (inherited from base)
// Retry
maxRetries?: number; // Maximum retry attempts (inherited from base)
retryMode?: 'linear' | 'exponential'; // Retry strategy (inherited from base)
retryDelay?: number; // Base retry delay (inherited from base)
// Headers (for WebSocket upgrade request)
headers?: Record<string, string>;
};Connection Management
Connection States
The transport provides real-time connection state monitoring:
// Check current state
console.log(transport.state); // 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
// Check if connected
if (transport.isOpen) {
await someFunction();
}Auto-Reconnection
WebSocket transport handles connection failures:
const transport = new WebSocketTransport({
url: 'ws://localhost:8080',
autoReconnect: true,
maxReconnectAttempts: 5,
reconnectDelay: 1000, // 1 second between attempts
});
// Manual reconnection
await transport.reconnect();Connection Events
Monitor connection lifecycle:
// The transport handles reconnection internally
// Connection state can be checked via transport.state
// Calls are queued during reconnectionPerformance Benefits
Lower Latency
- No HTTP handshake - Persistent connection eliminates TCP handshake overhead per request
- Immediate messaging - Messages sent without HTTP request/response cycle
- Connection reuse - Same WebSocket connection for multiple calls
Automatic Batching
Multiple simultaneous calls are batched into single WebSocket messages:
// These calls are batched into 1 WebSocket message
const [user, posts, stats] = await Promise.all([
getUser('123'),
getPosts('123'),
getStats('123'),
]);Streaming Responses
Because the WebSocket channel persists, responses are yielded dynamically as continuous IRPCPacketStream chunks over the socket. This enables you to bind standard UI components directly to the network proxy without writing manual WebSocket MessageEvent listeners.
File Upload Constraints
WebSocket transport fully supports IRPCFile uploads natively via length-prefixed binary framing. The router buffers incoming ArrayBuffer payloads and matches them to file pointers when the JSON payload arrives.
The fileBufferTTL option (default: 30000ms) controls how long orphaned binary frames are held in memory before being discarded. If the matching JSON payload never arrives within the TTL window, the buffered file data is automatically cleaned up.
WARNING
Sending large files over a WebSocket connection will block the persistent socket pipeline until the binary transfer completes. This means other simultaneous small RPC calls won't reach the server until the file finishes uploading. For applications heavily reliant on file uploads alongside real-time interactions, HTTP Transport is the strongly recommended approach, as standard HTTP multi-part requests are offloaded to background browser processes, keeping the WebSocket connection responsive.
Connection Disconnect
The router tracks AbortController instances for every active stream. When a WebSocket connection closes, call router.disconnect(ws) to abort all active streams for that connection:
websocket: {
close(ws) {
router.disconnect(ws);
},
}Stream Cancellation
Clients can cancel individual streams by calling .close() on the RemoteState. The transport sends a cancellation packet to the server, which aborts the stream's AbortController and triggers cleanup:
// Client
const prices = watchPrices('AAPL');
prices.start();
// Later — cancel this specific stream
prices.close();Error Handling
Network Errors
WebSocket transport includes retry logic for network failures:
const transport = new WebSocketTransport({
url: 'ws://localhost:8080',
maxRetries: 3,
retryMode: 'exponential', // 1s, 2s, 4s delays
retryDelay: 1000,
});Network errors trigger automatic retries. Handler errors fail immediately without retry.
Connection Failures
Connection failures trigger reconnection attempts. Pending calls are queued and sent once reconnected.
Advanced Usage
Custom Headers
Include headers in the WebSocket upgrade request:
const transport = new WebSocketTransport({
url: 'ws://localhost:8080',
headers: {
'Authorization': 'Bearer token',
'X-API-Key': 'key',
},
});Hooks
Because the integration point injects the same standardized keys as HTTP ('token', 'user', etc.), the exact same hook function works on both routers. No transport-specific imports, no conditional logic.
router.use(async () => {
const token = getContext<string>('token');
if (!token) {
throw new Error('Unauthorized');
}
setContext('user', await verifyToken(token));
});Custom Protocols
Specify WebSocket sub-protocols:
const transport = new WebSocketTransport({
url: 'ws://localhost:8080',
protocols: ['irpc', 'json'],
});Comparison with HTTP Transport
| Feature | WebSocket | HTTP |
|---|---|---|
| Connection Type | Persistent | Request/Response |
| Latency | Lower | Higher |
| Overhead | Minimal | HTTP headers |
| Real-time | Yes | No |
| Batching | Automatic | Automatic |
| Browser Support | Excellent | Universal |
| Setup Complexity | Medium | Simple |
Choose WebSocket transport when:
- You need persistent connections
- Lower latency is critical
- You're building real-time applications
- You want bidirectional communication
Choose HTTP transport when:
- You need maximum compatibility
- You're building REST-like APIs
- Simplicity is preferred
- You have existing HTTP infrastructure
Troubleshooting
Connection Issues
Problem: Connection fails to establish
Solutions:
- Check WebSocket URL format (
ws://orwss://) - Verify server is running and accepting WebSocket connections
- Check firewall/proxy settings
- Use browser dev tools to inspect WebSocket handshake
Reconnection Problems
Problem: Auto-reconnection not working
Solutions:
- Ensure
autoReconnect: trueis set - Check
maxReconnectAttemptsvalue - Verify server accepts reconnections
- Monitor
transport.statefor connection status
Performance Issues
Problem: High latency or slow responses
Solutions:
- Verify WebSocket connection is persistent
- Check for unnecessary reconnections
- Monitor batching efficiency
- Use appropriate timeout values
Next Steps
- Getting Started - Set up IRPC with WebSocket transport
- HTTP Transport - Alternative transport option
- Specification - Full protocol details