TypeScript SDK
WebSocket Guide
WebSocket connections, subscriptions, reconnection handling, and heartbeats
WebSocket Guide
The TypeScript SDK provides WebSocket support for real-time market data, order updates, and account notifications.
Connection Basics
Establishing Connection
import { Client } from '@luxfi/trading';
async function connectWebSocket(): Promise<void> {
const client = new Client({
rpcUrl: 'http://localhost:8080/rpc',
wsUrl: 'ws://localhost:8081'
});
// Connect to WebSocket server
await client.connect();
console.log('WebSocket connected');
// Your subscription code here...
}Disconnecting
import { Client } from '@luxfi/trading';
function disconnectWebSocket(client: Client): void {
client.disconnect();
console.log('WebSocket disconnected');
}Subscription Management
Subscribe to Channels
import { Client } from '@luxfi/trading';
async function subscribeToChannels(): Promise<void> {
const client = new Client({
rpcUrl: 'http://localhost:8080/rpc',
wsUrl: 'ws://localhost:8081'
});
await client.connect();
// Generic subscription method
client.subscribe('custom:channel', (data: unknown) => {
console.log('Received:', data);
});
// Convenience methods
client.subscribeOrderBook('BTC-USD', (book) => {
console.log('Order book update');
});
client.subscribeTrades('BTC-USD', (trade) => {
console.log('Trade:', trade);
});
}Unsubscribe from Channels
import { Client, OrderBook } from '@luxfi/trading';
async function unsubscribeExample(): Promise<void> {
const client = new Client({
rpcUrl: 'http://localhost:8080/rpc',
wsUrl: 'ws://localhost:8081'
});
await client.connect();
// Define callback
const handleOrderBook = (book: OrderBook): void => {
console.log('Book update');
};
// Subscribe
client.subscribeOrderBook('BTC-USD', handleOrderBook);
// Unsubscribe specific callback
client.unsubscribe('orderbook:BTC-USD', handleOrderBook);
// Unsubscribe all callbacks from channel
client.unsubscribe('orderbook:BTC-USD');
}Available Channels
| Channel Pattern | Description | Data Type |
|---|---|---|
orderbook:{symbol} | Order book updates | OrderBook |
trades:{symbol} | Trade executions | Trade |
liquidations | Liquidation events | LiquidationInfo |
settlements | Settlement batches | SettlementBatch |
margin_calls:{userId} | Margin call alerts | MarginCallEvent |
Reconnection Handling
Manual Reconnection
import { Client } from '@luxfi/trading';
class ReconnectingClient {
private client: Client;
private connected: boolean = false;
private reconnecting: boolean = false;
private maxRetries: number = 5;
private retryDelay: number = 1000;
constructor(config: { rpcUrl: string; wsUrl: string }) {
this.client = new Client(config);
}
async connect(): Promise<void> {
let retries = 0;
while (retries < this.maxRetries) {
try {
await this.client.connect();
this.connected = true;
console.log('Connected to WebSocket');
return;
} catch (error) {
retries++;
console.log(`Connection attempt ${retries} failed`);
if (retries < this.maxRetries) {
await this.delay(this.retryDelay * retries);
}
}
}
throw new Error('Failed to connect after max retries');
}
async reconnect(): Promise<void> {
if (this.reconnecting) return;
this.reconnecting = true;
console.log('Attempting to reconnect...');
this.client.disconnect();
try {
await this.connect();
// Resubscribe to channels here
console.log('Reconnected successfully');
} catch (error) {
console.error('Reconnection failed:', error);
} finally {
this.reconnecting = false;
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
getClient(): Client {
return this.client;
}
}Automatic Reconnection with Event Handling
import { Client, OrderBook, Trade } from '@luxfi/trading';
interface Subscription {
channel: string;
callback: (data: unknown) => void;
}
class AutoReconnectClient {
private client: Client;
private config: { rpcUrl: string; wsUrl: string };
private subscriptions: Subscription[] = [];
private reconnectTimer: NodeJS.Timeout | null = null;
private isConnected: boolean = false;
constructor(config: { rpcUrl: string; wsUrl: string }) {
this.config = config;
this.client = new Client(config);
}
async connect(): Promise<void> {
try {
await this.client.connect();
this.isConnected = true;
this.resubscribeAll();
} catch (error) {
this.scheduleReconnect();
throw error;
}
}
private scheduleReconnect(): void {
if (this.reconnectTimer) return;
this.reconnectTimer = setTimeout(async () => {
this.reconnectTimer = null;
try {
this.client = new Client(this.config);
await this.connect();
} catch {
this.scheduleReconnect();
}
}, 5000);
}
private resubscribeAll(): void {
for (const sub of this.subscriptions) {
this.client.subscribe(sub.channel, sub.callback);
}
console.log(`Resubscribed to ${this.subscriptions.length} channels`);
}
subscribe(channel: string, callback: (data: unknown) => void): void {
// Store subscription for reconnection
this.subscriptions.push({ channel, callback });
// Subscribe if connected
if (this.isConnected) {
this.client.subscribe(channel, callback);
}
}
subscribeOrderBook(symbol: string, callback: (book: OrderBook) => void): void {
this.subscribe(`orderbook:${symbol}`, callback as (data: unknown) => void);
}
subscribeTrades(symbol: string, callback: (trade: Trade) => void): void {
this.subscribe(`trades:${symbol}`, callback as (data: unknown) => void);
}
unsubscribe(channel: string): void {
this.subscriptions = this.subscriptions.filter(s => s.channel !== channel);
this.client.unsubscribe(channel);
}
disconnect(): void {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
this.client.disconnect();
this.isConnected = false;
}
}Heartbeat Implementation
Ping-Pong Heartbeat
import { Client } from '@luxfi/trading';
class HeartbeatClient {
private client: Client;
private heartbeatInterval: NodeJS.Timeout | null = null;
private missedHeartbeats: number = 0;
private maxMissedHeartbeats: number = 3;
private heartbeatMs: number = 30000;
private onDisconnect?: () => void;
constructor(
config: { rpcUrl: string; wsUrl: string },
onDisconnect?: () => void
) {
this.client = new Client(config);
this.onDisconnect = onDisconnect;
}
async connect(): Promise<void> {
await this.client.connect();
this.startHeartbeat();
}
private startHeartbeat(): void {
this.heartbeatInterval = setInterval(async () => {
try {
await this.client.ping();
this.missedHeartbeats = 0;
} catch (error) {
this.missedHeartbeats++;
console.log(`Missed heartbeat (${this.missedHeartbeats})`);
if (this.missedHeartbeats >= this.maxMissedHeartbeats) {
console.log('Connection lost - triggering disconnect');
this.stopHeartbeat();
this.onDisconnect?.();
}
}
}, this.heartbeatMs);
}
private stopHeartbeat(): void {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
disconnect(): void {
this.stopHeartbeat();
this.client.disconnect();
}
getClient(): Client {
return this.client;
}
}
// Usage
async function useHeartbeatClient(): Promise<void> {
const client = new HeartbeatClient(
{
rpcUrl: 'http://localhost:8080/rpc',
wsUrl: 'ws://localhost:8081'
},
() => {
console.log('Connection lost - attempting reconnect');
// Reconnection logic here
}
);
await client.connect();
}Message Handling
Custom Message Handler
import { Client } from '@luxfi/trading';
interface WebSocketMessage {
channel: string;
data: unknown;
timestamp?: number;
}
class MessageHandler {
private handlers: Map<string, ((data: unknown) => void)[]> = new Map();
register(channel: string, handler: (data: unknown) => void): void {
if (!this.handlers.has(channel)) {
this.handlers.set(channel, []);
}
this.handlers.get(channel)!.push(handler);
}
unregister(channel: string, handler?: (data: unknown) => void): void {
if (!handler) {
this.handlers.delete(channel);
return;
}
const handlers = this.handlers.get(channel);
if (handlers) {
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
}
handle(message: WebSocketMessage): void {
const handlers = this.handlers.get(message.channel);
if (handlers) {
handlers.forEach(handler => {
try {
handler(message.data);
} catch (error) {
console.error(`Handler error for ${message.channel}:`, error);
}
});
}
}
}Browser Usage
Browser WebSocket Client
import { Client, OrderBook, Trade } from '@luxfi/trading';
class BrowserDexClient {
private client: Client;
private visibilityHandler: (() => void) | null = null;
constructor(config: { rpcUrl: string; wsUrl: string }) {
this.client = new Client(config);
this.setupVisibilityHandling();
}
private setupVisibilityHandling(): void {
if (typeof document === 'undefined') return;
this.visibilityHandler = (): void => {
if (document.visibilityState === 'visible') {
// Reconnect when tab becomes visible
console.log('Tab visible - checking connection');
this.checkConnection();
}
};
document.addEventListener('visibilitychange', this.visibilityHandler);
}
private async checkConnection(): Promise<void> {
try {
await this.client.ping();
} catch {
console.log('Connection lost - reconnecting');
await this.reconnect();
}
}
async connect(): Promise<void> {
await this.client.connect();
}
async reconnect(): Promise<void> {
this.client.disconnect();
await this.client.connect();
}
subscribeOrderBook(symbol: string, callback: (book: OrderBook) => void): void {
this.client.subscribeOrderBook(symbol, callback);
}
subscribeTrades(symbol: string, callback: (trade: Trade) => void): void {
this.client.subscribeTrades(symbol, callback);
}
disconnect(): void {
if (this.visibilityHandler && typeof document !== 'undefined') {
document.removeEventListener('visibilitychange', this.visibilityHandler);
}
this.client.disconnect();
}
}React WebSocket Hook
import { useState, useEffect, useCallback, useRef } from 'react';
import { Client, ClientConfig } from '@luxfi/trading';
interface UseWebSocketOptions {
autoConnect?: boolean;
reconnectOnError?: boolean;
reconnectDelay?: number;
maxReconnectAttempts?: number;
}
interface UseWebSocketResult {
connected: boolean;
connecting: boolean;
error: Error | null;
connect: () => Promise<void>;
disconnect: () => void;
client: Client | null;
}
export function useWebSocket(
config: ClientConfig,
options: UseWebSocketOptions = {}
): UseWebSocketResult {
const {
autoConnect = true,
reconnectOnError = true,
reconnectDelay = 3000,
maxReconnectAttempts = 5
} = options;
const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false);
const [error, setError] = useState<Error | null>(null);
const clientRef = useRef<Client | null>(null);
const reconnectAttempts = useRef(0);
const connect = useCallback(async () => {
if (connecting || connected) return;
setConnecting(true);
setError(null);
try {
if (!clientRef.current) {
clientRef.current = new Client(config);
}
await clientRef.current.connect();
setConnected(true);
reconnectAttempts.current = 0;
} catch (err) {
const connectError = err instanceof Error ? err : new Error('Connection failed');
setError(connectError);
if (reconnectOnError && reconnectAttempts.current < maxReconnectAttempts) {
reconnectAttempts.current++;
setTimeout(() => connect(), reconnectDelay);
}
} finally {
setConnecting(false);
}
}, [config, connecting, connected, reconnectOnError, reconnectDelay, maxReconnectAttempts]);
const disconnect = useCallback(() => {
if (clientRef.current) {
clientRef.current.disconnect();
setConnected(false);
}
}, []);
useEffect(() => {
if (autoConnect) {
connect();
}
return () => {
disconnect();
};
}, [autoConnect, connect, disconnect]);
return {
connected,
connecting,
error,
connect,
disconnect,
client: clientRef.current
};
}Using the Hook
import React from 'react';
import { useWebSocket } from './hooks/useWebSocket';
import { OrderBook } from '@luxfi/trading';
function TradingApp(): JSX.Element {
const { connected, connecting, error, client } = useWebSocket({
rpcUrl: 'http://localhost:8080/rpc',
wsUrl: 'ws://localhost:8081'
});
const [orderBook, setOrderBook] = React.useState<OrderBook | null>(null);
React.useEffect(() => {
if (!client || !connected) return;
client.subscribeOrderBook('BTC-USD', (book: OrderBook) => {
setOrderBook(book);
});
return () => {
client.unsubscribe('orderbook:BTC-USD');
};
}, [client, connected]);
if (error) {
return <div className="error">Connection error: {error.message}</div>;
}
if (connecting) {
return <div className="loading">Connecting...</div>;
}
if (!connected) {
return <div className="disconnected">Disconnected</div>;
}
return (
<div className="trading-app">
<div className="connection-status">Connected</div>
{orderBook && (
<div className="order-book">
<div>Best Bid: {orderBook.bids[0]?.price}</div>
<div>Best Ask: {orderBook.asks[0]?.price}</div>
</div>
)}
</div>
);
}Node.js Usage
Server-Side WebSocket Client
import { Client, OrderBook, Trade } from '@luxfi/trading';
async function runNodeClient(): Promise<void> {
const client = new Client({
rpcUrl: 'http://localhost:8080/rpc',
wsUrl: 'ws://localhost:8081'
});
// Connect with error handling
try {
await client.connect();
console.log('Connected to LX');
} catch (error) {
console.error('Failed to connect:', error);
process.exit(1);
}
// Subscribe to market data
client.subscribeOrderBook('BTC-USD', (book: OrderBook) => {
const spread = book.asks[0]?.price - book.bids[0]?.price;
console.log(`Spread: ${spread?.toFixed(2) || 'N/A'}`);
});
client.subscribeTrades('BTC-USD', (trade: Trade) => {
console.log(`Trade: ${trade.size} @ ${trade.price}`);
});
// Handle process termination
process.on('SIGINT', () => {
console.log('Shutting down...');
client.disconnect();
process.exit(0);
});
process.on('SIGTERM', () => {
client.disconnect();
process.exit(0);
});
}
runNodeClient();Connection Monitoring
Connection Status Monitor
import { Client } from '@luxfi/trading';
interface ConnectionStatus {
connected: boolean;
lastPing: Date | null;
latency: number | null;
reconnectCount: number;
}
class ConnectionMonitor {
private client: Client;
private status: ConnectionStatus = {
connected: false,
lastPing: null,
latency: null,
reconnectCount: 0
};
private pingInterval: NodeJS.Timeout | null = null;
private statusCallbacks: ((status: ConnectionStatus) => void)[] = [];
constructor(client: Client) {
this.client = client;
}
start(): void {
this.pingInterval = setInterval(async () => {
const start = Date.now();
try {
await this.client.ping();
this.status = {
...this.status,
connected: true,
lastPing: new Date(),
latency: Date.now() - start
};
} catch {
this.status = {
...this.status,
connected: false,
latency: null
};
}
this.notifyStatusChange();
}, 5000);
}
stop(): void {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
onStatusChange(callback: (status: ConnectionStatus) => void): void {
this.statusCallbacks.push(callback);
}
getStatus(): ConnectionStatus {
return { ...this.status };
}
private notifyStatusChange(): void {
this.statusCallbacks.forEach(cb => cb(this.getStatus()));
}
}Error Handling
WebSocket Error Types
enum WebSocketErrorType {
CONNECTION_FAILED = 'CONNECTION_FAILED',
CONNECTION_LOST = 'CONNECTION_LOST',
SUBSCRIPTION_FAILED = 'SUBSCRIPTION_FAILED',
MESSAGE_PARSE_ERROR = 'MESSAGE_PARSE_ERROR',
TIMEOUT = 'TIMEOUT'
}
class WebSocketError extends Error {
type: WebSocketErrorType;
originalError?: Error;
constructor(type: WebSocketErrorType, message: string, originalError?: Error) {
super(message);
this.type = type;
this.originalError = originalError;
this.name = 'WebSocketError';
}
}
// Error handler
function handleWebSocketError(error: WebSocketError): void {
switch (error.type) {
case WebSocketErrorType.CONNECTION_FAILED:
console.error('Failed to establish connection');
// Retry connection
break;
case WebSocketErrorType.CONNECTION_LOST:
console.error('Connection was lost');
// Attempt reconnection
break;
case WebSocketErrorType.SUBSCRIPTION_FAILED:
console.error('Failed to subscribe to channel');
// Retry subscription
break;
case WebSocketErrorType.MESSAGE_PARSE_ERROR:
console.error('Failed to parse message');
// Log and continue
break;
case WebSocketErrorType.TIMEOUT:
console.error('Operation timed out');
// Retry or fail
break;
}
}Best Practices
Connection Management Checklist
-
Always handle connection errors
try { await client.connect(); } catch (error) { // Handle connection failure } -
Implement reconnection logic
- Use exponential backoff
- Set maximum retry attempts
- Resubscribe after reconnection
-
Clean up on unmount/exit
// React useEffect(() => { return () => client.disconnect(); }, []); // Node.js process.on('SIGINT', () => client.disconnect()); -
Monitor connection health
- Implement heartbeat/ping
- Track latency
- Alert on connection issues
-
Handle visibility changes (browser)
- Reconnect when tab becomes visible
- Pause updates when tab is hidden
Next Steps
- Order Management - Place and manage orders
- Order Book - Real-time order book data
- Type Reference - Complete type definitions