Skip to main content

Complete JavaScript Client

Here’s a full-featured Node.js WebSocket client for the QuantCite API:
const WebSocket = require('ws');
const EventEmitter = require('events');

class QuantCiteClient extends EventEmitter {
    constructor(apiKey, baseUrl = 'data.quantcite.com') {
        super();
        this.apiKey = apiKey;
        this.baseUrl = baseUrl;
        this.uri = `wss://${baseUrl}/api/v1/ws`;
        this.ws = null;
        this.authenticated = false;
        this.subscriptions = new Set();
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 10;
        this.reconnectDelay = 1000;
        this.pingInterval = null;
        this.running = true;
    }
    
    async connect() {
        return new Promise((resolve, reject) => {
            console.log(`Connecting to ${this.uri}`);
            
            this.ws = new WebSocket(this.uri);
            
            this.ws.on('open', () => {
                console.log('WebSocket connected successfully');
                this.reconnectAttempts = 0;
                this.startPingInterval();
            });
            
            this.ws.on('message', (data) => {
                try {
                    const message = JSON.parse(data.toString());
                    this.handleMessage(message);
                    
                    // Handle authentication response
                    if (message.type === 'authentication_success') {
                        this.authenticated = true;
                        console.log('Authentication successful');
                        console.log(`User ID: ${message.user_id}`);
                        console.log(`Tier: ${message.tier}`);
                        
                        const usage = message.data_usage || {};
                        console.log(`Data usage: ${usage.used_gb || 0}GB / ${usage.limit_gb || 50}GB`);
                        
                        resolve(true);
                    } else if (message.type === 'authentication_error') {
                        console.error(`Authentication failed: ${message.message}`);
                        reject(new Error('Authentication failed'));
                    } else if (message.type === 'welcome') {
                        console.log('Welcome message received, authenticating...');
                        this.authenticate();
                    }
                    
                } catch (error) {
                    console.error('Error parsing message:', error);
                }
            });
            
            this.ws.on('error', (error) => {
                console.error('WebSocket error:', error);
                this.emit('error', error);
                reject(error);
            });
            
            this.ws.on('close', (code, reason) => {
                console.log(`WebSocket closed: ${code} ${reason}`);
                this.authenticated = false;
                this.stopPingInterval();
                
                if (this.running && this.reconnectAttempts < this.maxReconnectAttempts) {
                    this.scheduleReconnect();
                }
                
                this.emit('disconnect', { code, reason });
            });
        });
    }
    
    authenticate() {
        const authMsg = {
            type: 'authenticate',
            api_key: this.apiKey
        };
        
        this.send(authMsg);
        console.log('Authentication message sent');
    }
    
    scheduleReconnect() {
        this.reconnectAttempts++;
        const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
        
        console.log(`Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
        
        setTimeout(() => {
            if (this.running) {
                this.connect().catch(error => {
                    console.error('Reconnection failed:', error);
                });
            }
        }, delay);
    }
    
    startPingInterval() {
        this.pingInterval = setInterval(() => {
            if (this.authenticated) {
                this.ping();
            }
        }, 30000); // Ping every 30 seconds
    }
    
    stopPingInterval() {
        if (this.pingInterval) {
            clearInterval(this.pingInterval);
            this.pingInterval = null;
        }
    }
    
    send(message) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(message));
            return true;
        } else {
            console.error('WebSocket not connected');
            return false;
        }
    }
    
    ping() {
        const pingMsg = { type: 'ping' };
        this.send(pingMsg);
    }
    
    subscribeToSymbol(symbol, exchanges = 'all') {
        if (!this.authenticated) {
            console.error('Not authenticated. Cannot subscribe.');
            return false;
        }
        
        const subscribeMsg = {
            type: 'subscribe_aggregated',
            symbol: symbol,
            exchanges: exchanges
        };
        
        if (this.send(subscribeMsg)) {
            this.subscriptions.add(symbol);
            console.log(`Subscription request sent for ${symbol}`);
            return true;
        }
        
        return false;
    }
    
    unsubscribeFromSymbol(symbol) {
        if (!this.subscriptions.has(symbol)) {
            console.warn(`Not subscribed to ${symbol}`);
            return false;
        }
        
        const unsubscribeMsg = {
            type: 'unsubscribe_aggregated',
            symbol: symbol
        };
        
        if (this.send(unsubscribeMsg)) {
            this.subscriptions.delete(symbol);
            console.log(`Unsubscribed from ${symbol}`);
            return true;
        }
        
        return false;
    }
    
    getCurrentOrderbook(symbol) {
        if (!this.authenticated) {
            console.error('Not authenticated. Cannot request orderbook.');
            return false;
        }
        
        const requestMsg = {
            type: 'get_aggregated_orderbook',
            symbol: symbol
        };
        
        if (this.send(requestMsg)) {
            console.log(`Orderbook snapshot requested for ${symbol}`);
            return true;
        }
        
        return false;
    }
    
    getExchanges() {
        if (!this.authenticated) {
            console.error('Not authenticated. Cannot request exchanges.');
            return false;
        }
        
        const requestMsg = { type: 'get_exchanges' };
        
        if (this.send(requestMsg)) {
            console.log('Exchange list requested');
            return true;
        }
        
        return false;
    }
    
    getTradingPairs(exchange = null) {
        if (!this.authenticated) {
            console.error('Not authenticated. Cannot request trading pairs.');
            return false;
        }
        
        const requestMsg = { type: 'get_pairs' };
        if (exchange) {
            requestMsg.exchange = exchange;
        }
        
        if (this.send(requestMsg)) {
            console.log(`Trading pairs requested for ${exchange || 'all exchanges'}`);
            return true;
        }
        
        return false;
    }
    
    handleMessage(data) {
        const msgType = data.type;
        
        switch (msgType) {
            case 'aggregated_orderbook_update':
                this.handleOrderbookUpdate(data);
                break;
                
            case 'aggregated_subscription_response':
                this.handleSubscriptionResponse(data);
                break;
                
            case 'unsubscribe_response':
                this.handleUnsubscribeResponse(data);
                break;
                
            case 'exchanges_data':
                this.handleExchangesData(data);
                break;
                
            case 'pairs_data':
                this.handlePairsData(data);
                break;
                
            case 'aggregated_orderbook_data':
                this.handleOrderbookSnapshot(data);
                break;
                
            case 'data_limit_warning':
                this.handleDataLimitWarning(data);
                break;
                
            case 'rate_limit_warning':
                this.handleRateLimitWarning(data);
                break;
                
            case 'error':
                this.handleError(data);
                break;
                
            case 'pong':
                this.handlePong(data);
                break;
                
            default:
                console.log(`Received ${msgType}:`, data);
                this.emit('message', data);
        }
    }
    
    handleOrderbookUpdate(data) {
        const symbol = data.symbol;
        const updateNum = data.update_number;
        const orderbook = data.data || {};
        const stats = orderbook.market_stats || {};
        
        console.log(`${symbol} Update #${updateNum}: ` +
                   `Bid: ${stats.best_bid}, Ask: ${stats.best_ask}, Spread: ${stats.spread}`);
        
        // Emit event for external handling
        this.emit('orderbook_update', {
            symbol,
            updateNum,
            orderbook,
            stats
        });
    }
    
    handleOrderbookSnapshot(data) {
        const symbol = data.symbol;
        const timestamp = data.timestamp;
        const orderbook = data.data || {};
        const stats = orderbook.market_stats || {};
        
        console.log(`${symbol} Snapshot at ${timestamp}: ` +
                   `Bid: ${stats.best_bid}, Ask: ${stats.best_ask}`);
        
        this.emit('orderbook_snapshot', {
            symbol,
            timestamp,
            orderbook,
            stats
        });
    }
    
    handleSubscriptionResponse(data) {
        const symbol = data.symbol;
        const success = data.success;
        
        if (success) {
            console.log(`Successfully subscribed to ${symbol}`);
            this.emit('subscribed', { symbol, data });
        } else {
            console.error(`Failed to subscribe to ${symbol}: ${data.message}`);
            this.emit('subscription_error', { symbol, data });
        }
    }
    
    handleUnsubscribeResponse(data) {
        const symbol = data.symbol;
        console.log(`Unsubscribed from ${symbol}`);
        this.emit('unsubscribed', { symbol, data });
    }
    
    handleExchangesData(data) {
        const exchangesInfo = data.data || {};
        const supported = exchangesInfo.supported_exchanges || [];
        const active = exchangesInfo.active_exchanges || [];
        
        console.log(`Supported exchanges: ${supported.length}`);
        console.log(`Active exchanges: ${active.length}`);
        
        this.emit('exchanges_data', exchangesInfo);
    }
    
    handlePairsData(data) {
        const pairsInfo = data.data || {};
        
        if (pairsInfo.exchange) {
            const exchange = pairsInfo.exchange;
            const pairs = pairsInfo.pairs || [];
            console.log(`${exchange}: ${pairs.length} trading pairs`);
        } else {
            const totalExchanges = pairsInfo.total_exchanges || 0;
            const totalPairs = pairsInfo.total_pairs || 0;
            console.log(`Total: ${totalExchanges} exchanges, ${totalPairs} pairs`);
        }
        
        this.emit('pairs_data', pairsInfo);
    }
    
    handleDataLimitWarning(data) {
        console.warn(`Data limit warning: ${data.message}`);
        const usage = data.data_usage || {};
        console.warn(`Usage: ${usage.used_gb}GB / ${usage.limit_gb}GB`);
        
        this.emit('data_limit_warning', data);
    }
    
    handleRateLimitWarning(data) {
        console.warn(`Rate limit warning: ${data.message}`);
        this.emit('rate_limit_warning', data);
    }
    
    handleError(data) {
        console.error(`Error: ${data.error} - ${data.message}`);
        this.emit('api_error', data);
    }
    
    handlePong(data) {
        const latency = data.latency_ms || 0;
        console.log(`Pong received (latency: ${latency}ms)`);
        this.emit('pong', data);
    }
    
    async disconnect() {
        this.running = false;
        this.stopPingInterval();
        
        if (this.ws) {
            this.ws.close();
        }
    }
    
    // Utility methods
    getSubscriptions() {
        return Array.from(this.subscriptions);
    }
    
    isConnected() {
        return this.ws && this.ws.readyState === WebSocket.OPEN;
    }
    
    isAuthenticated() {
        return this.authenticated;
    }
}

// Export the client
module.exports = QuantCiteClient;

// Example usage
async function main() {
    const client = new QuantCiteClient('demo_key_123');
    
    // Set up event listeners
    client.on('orderbook_update', (data) => {
        // Handle real-time orderbook updates
        console.log(`Orderbook update for ${data.symbol}`);
    });
    
    client.on('error', (error) => {
        console.error('Client error:', error);
    });
    
    client.on('disconnect', (info) => {
        console.log('Disconnected:', info);
    });
    
    try {
        // Connect and authenticate
        await client.connect();
        
        // Wait a moment for authentication
        setTimeout(async () => {
            if (client.isAuthenticated()) {
                // Get available exchanges
                client.getExchanges();
                
                // Subscribe to BTC/USDT on specific exchanges
                client.subscribeToSymbol('BTC/USDT', ['binance', 'okx', 'bybit']);
                
                // Get current orderbook snapshot
                setTimeout(() => {
                    client.getCurrentOrderbook('BTC/USDT');
                }, 1000);
                
                // Subscribe to ETH/USDT on all exchanges
                setTimeout(() => {
                    client.subscribeToSymbol('ETH/USDT', 'all');
                }, 2000);
            }
        }, 1000);
        
    } catch (error) {
        console.error('Failed to connect:', error);
    }
    
    // Graceful shutdown
    process.on('SIGINT', async () => {
        console.log('Shutting down...');
        await client.disconnect();
        process.exit(0);
    });
}

// Run example if this file is executed directly
if (require.main === module) {
    main();
}

Package Configuration

Create a package.json file:
{
  "name": "quantcite-client",
  "version": "1.0.0",
  "description": "QuantCite WebSocket API client",
  "main": "quantcite-client.js",
  "scripts": {
    "start": "node quantcite-client.js",
    "test": "node test.js"
  },
  "dependencies": {
    "ws": "^8.14.2"
  },
  "keywords": ["cryptocurrency", "websocket", "api", "trading", "orderbook"],
  "author": "Your Name",
  "license": "MIT"
}
Install dependencies:
npm install

Usage Examples

Basic Usage

const QuantCiteClient = require('./quantcite-client');

async function basicExample() {
    const client = new QuantCiteClient('your_api_key_here');
    
    // Set up event listener
    client.on('orderbook_update', (data) => {
        console.log(`${data.symbol}: ${data.stats.best_bid} / ${data.stats.best_ask}`);
    });
    
    try {
        await client.connect();
        
        // Subscribe to Bitcoin data
        setTimeout(() => {
            client.subscribeToSymbol('BTC/USDT', ['binance', 'okx']);
        }, 1000);
        
    } catch (error) {
        console.error('Connection failed:', error);
    }
}

basicExample();

Event-Driven Processing

const client = new QuantCiteClient('your_api_key_here');

// Handle different event types
client.on('orderbook_update', (data) => {
    // Process real-time orderbook updates
    const { symbol, stats } = data;
    console.log(`${symbol}: Spread = ${stats.spread}`);
});

client.on('data_limit_warning', (data) => {
    // Handle data usage warnings
    console.warn('Approaching data limit!');
});

client.on('subscribed', (data) => {
    // Confirmation of successful subscription
    console.log(`Now receiving data for ${data.symbol}`);
});

client.on('api_error', (data) => {
    // Handle API errors
    console.error(`API Error: ${data.error}`);
});

// Connect and start
client.connect();

Multiple Symbol Management

class TradingBot {
    constructor(apiKey) {
        this.client = new QuantCiteClient(apiKey);
        this.symbols = ['BTC/USDT', 'ETH/USDT', 'BNB/USDT'];
        this.orderbooks = new Map();
    }
    
    async start() {
        this.client.on('orderbook_update', (data) => {
            this.orderbooks.set(data.symbol, data.orderbook);
            this.analyzeSpread(data.symbol, data.stats);
        });
        
        await this.client.connect();
        
        // Subscribe to multiple symbols
        setTimeout(() => {
            this.symbols.forEach(symbol => {
                this.client.subscribeToSymbol(symbol, 'all');
            });
        }, 1000);
    }
    
    analyzeSpread(symbol, stats) {
        const spread = stats.spread_percent;
        if (spread > 0.1) { // 0.1% spread threshold
            console.log(`High spread detected for ${symbol}: ${spread.toFixed(4)}%`);
        }
    }
}

const bot = new TradingBot('your_api_key_here');
bot.start();

Key Features

Event-Driven Architecture

Built with Node.js EventEmitter for efficient real-time data processing and custom event handling.

Automatic Reconnection

Handles connection drops with exponential backoff retry logic and maintains subscriptions.

Comprehensive Error Handling

Proper error handling for all message types with detailed logging and event emission.

Health Monitoring

Built-in ping/pong mechanism with connection health monitoring and latency tracking.

Browser Compatibility

For browser usage, create a simplified version without Node.js dependencies:
class QuantCiteBrowserClient {
    constructor(apiKey, baseUrl = 'data.quantcite.com') {
        this.apiKey = apiKey;
        this.uri = `wss://${baseUrl}/api/v1/ws`;
        this.ws = null;
        this.authenticated = false;
        this.callbacks = {};
    }
    
    on(event, callback) {
        if (!this.callbacks[event]) {
            this.callbacks[event] = [];
        }
        this.callbacks[event].push(callback);
    }
    
    emit(event, data) {
        if (this.callbacks[event]) {
            this.callbacks[event].forEach(callback => callback(data));
        }
    }
    
    connect() {
        return new Promise((resolve, reject) => {
            this.ws = new WebSocket(this.uri);
            
            this.ws.onopen = () => {
                console.log('Connected to QuantCite');
            };
            
            this.ws.onmessage = (event) => {
                const data = JSON.parse(event.data);
                this.handleMessage(data);
                
                if (data.type === 'authentication_success') {
                    this.authenticated = true;
                    resolve(true);
                }
            };
            
            this.ws.onerror = (error) => {
                reject(error);
            };
        });
    }
    
    // Add other methods as needed...
}
Remember to replace "demo_key_123" with your actual API key. The client includes automatic reconnection and comprehensive error handling for production use.