UNPKG

binance-futures-wrapper

Version:

A comprehensive TypeScript wrapper for Binance USDT-M Futures API with full REST and WebSocket support

464 lines 16.7 kB
"use strict"; /** * WebSocket client for Binance Futures */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketClient = void 0; const ws_1 = __importDefault(require("ws")); const events_1 = require("events"); const utils_1 = require("../utils"); const websocketDataConverter_1 = require("../utils/websocketDataConverter"); class WebSocketClient extends events_1.EventEmitter { constructor(options) { super(); // WebSocket connections this.marketWs = null; this.userWs = null; // Connection states this.marketConnected = false; this.userConnected = false; // Subscriptions this.marketSubscriptions = new Set(); this.messageId = 1; // Keep-alive this.keepAliveInterval = null; this.listenKey = null; this.config = options.config; this.wsConfig = { reconnect: true, reconnectInterval: 5000, maxReconnectAttempts: 10, keepAlive: true, keepAliveInterval: 30000, ...options }; const baseUrls = (0, utils_1.getBaseUrls)(this.config.testnet); this.wsBaseUrl = this.config.wsBaseURL || baseUrls.ws; this.logger = new utils_1.Logger(this.config.enableLogging, '[WebSocketClient]'); this.reconnectionManager = new utils_1.ReconnectionManager(this.wsConfig.maxReconnectAttempts, this.wsConfig.reconnectInterval); } /** * Connect to market data streams */ async connectMarket() { if (this.marketConnected) { this.logger.warn('Market WebSocket already connected'); return; } const url = `${this.wsBaseUrl}/ws`; this.logger.log(`Connecting to market stream: ${url}`); try { this.marketWs = new ws_1.default(url); this.setupMarketHandlers(); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Market WebSocket connection timeout')); }, 10000); this.marketWs.once('open', () => { clearTimeout(timeout); this.marketConnected = true; this.reconnectionManager.reset(); this.logger.log('Market WebSocket connected'); this.emit('marketConnected'); resolve(); }); this.marketWs.once('error', (error) => { clearTimeout(timeout); reject(error); }); }); } catch (error) { this.logger.error('Failed to connect market WebSocket:', error); throw error; } } /** * Connect to user data stream */ async connectUser(listenKey) { if (this.userConnected) { this.logger.warn('User WebSocket already connected'); return; } this.listenKey = listenKey; const url = `${this.wsBaseUrl}/ws/${listenKey}`; this.logger.log(`Connecting to user stream: ${url}`); try { this.userWs = new ws_1.default(url); this.setupUserHandlers(); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('User WebSocket connection timeout')); }, 10000); this.userWs.once('open', () => { clearTimeout(timeout); this.userConnected = true; this.reconnectionManager.reset(); this.logger.log('User WebSocket connected'); this.emit('userConnected'); resolve(); }); this.userWs.once('error', (error) => { clearTimeout(timeout); reject(error); }); }); if (this.wsConfig.keepAlive) { this.startKeepAlive(); } } catch (error) { this.logger.error('Failed to connect user WebSocket:', error); throw error; } } setupMarketHandlers() { if (!this.marketWs) return; this.marketWs.on('message', (data) => { try { const message = JSON.parse(data.toString()); if (message.id !== undefined) { // Response to subscription request this.handleSubscriptionResponse(message); } else if (message.stream && message.data) { // Stream data this.handleMarketData(message); } else if (message.e) { // Direct event this.handleMarketEvent(message); } } catch (error) { this.logger.error('Error parsing market message:', error); } }); this.marketWs.on('close', (code, reason) => { this.logger.warn(`Market WebSocket closed: ${code} ${reason}`); this.marketConnected = false; this.emit('marketDisconnected', { code, reason }); if (this.wsConfig.reconnect) { this.reconnectMarket(); } }); this.marketWs.on('error', (error) => { this.logger.error('Market WebSocket error:', error); this.emit('marketError', error); }); } setupUserHandlers() { if (!this.userWs) return; this.userWs.on('message', (data) => { try { const message = JSON.parse(data.toString()); this.handleUserData(message); } catch (error) { this.logger.error('Error parsing user message:', error); } }); this.userWs.on('close', (code, reason) => { this.logger.warn(`User WebSocket closed: ${code} ${reason}`); this.userConnected = false; this.emit('userDisconnected', { code, reason }); if (this.wsConfig.keepAlive && this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; } if (this.wsConfig.reconnect) { this.reconnectUser(); } }); this.userWs.on('error', (error) => { this.logger.error('User WebSocket error:', error); this.emit('userError', error); }); } handleSubscriptionResponse(message) { if ('result' in message) { this.logger.debug('Subscription response:', message); this.emit('subscriptionResponse', message); } else { this.logger.error('Subscription error:', message); this.emit('subscriptionError', message); } } handleMarketData(message) { try { // Emit raw data for backwards compatibility this.emit('marketData', message.data); this.emit(`stream:${message.stream}`, message.data); // Convert and emit processed data with type safety if ((0, websocketDataConverter_1.isMarketDataEvent)(message.data)) { const processedEvent = (0, websocketDataConverter_1.convertMarketDataEvent)(message.data); this.emit('marketDataProcessed', processedEvent); this.emit(`stream:${message.stream}:processed`, processedEvent); } } catch (error) { this.logger.error('Error processing market data:', error); // Still emit raw data if conversion fails this.emit('marketData', message.data); this.emit(`stream:${message.stream}`, message.data); } } handleMarketEvent(event) { try { // Emit raw data for backwards compatibility this.emit('marketData', event); // Convert and emit processed data with type safety if ((0, websocketDataConverter_1.isMarketDataEvent)(event)) { const processedEvent = (0, websocketDataConverter_1.convertMarketDataEvent)(event); this.emit('marketDataProcessed', processedEvent); this.emit(`event:${processedEvent.eventType}:processed`, processedEvent); } // Legacy event emission if ('e' in event && event.e) { this.emit(`event:${event.e}`, event); } } catch (error) { this.logger.error('Error processing market event:', error); // Still emit raw data if conversion fails this.emit('marketEvent', event); } } handleUserData(event) { try { // Emit raw data for backwards compatibility this.emit('userData', event); this.emit(`user:${event.e}`, event); // Convert and emit processed data with type safety if ((0, websocketDataConverter_1.isUserDataEvent)(event)) { const processedEvent = (0, websocketDataConverter_1.convertUserDataEvent)(event); this.emit('userDataProcessed', processedEvent); this.emit(`user:${processedEvent.eventType}:processed`, processedEvent); } } catch (error) { this.logger.error('Error processing user data event:', error); // Still emit raw data if conversion fails this.emit('userData', event); this.emit(`user:${event.e}`, event); } } async reconnectMarket() { if (!this.reconnectionManager.canReconnect()) { this.logger.error('Max reconnection attempts reached for market WebSocket'); return; } try { const delay = await this.reconnectionManager.getDelay(); this.logger.log(`Reconnecting market WebSocket in ${delay}ms (attempt ${this.reconnectionManager['attempts']})`); await (0, utils_1.sleep)(delay); await this.connectMarket(); // Resubscribe to previous subscriptions if (this.marketSubscriptions.size > 0) { const streams = Array.from(this.marketSubscriptions); await this.subscribe(streams); } } catch (error) { this.logger.error('Market WebSocket reconnection failed:', error); this.reconnectMarket(); } } async reconnectUser() { if (!this.reconnectionManager.canReconnect() || !this.listenKey) { this.logger.error('Cannot reconnect user WebSocket'); return; } try { const delay = await this.reconnectionManager.getDelay(); this.logger.log(`Reconnecting user WebSocket in ${delay}ms (attempt ${this.reconnectionManager['attempts']})`); await (0, utils_1.sleep)(delay); await this.connectUser(this.listenKey); } catch (error) { this.logger.error('User WebSocket reconnection failed:', error); this.reconnectUser(); } } startKeepAlive() { if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); } this.keepAliveInterval = setInterval(() => { this.emit('keepAliveRequest'); }, this.wsConfig.keepAliveInterval); } /** * Subscribe to market streams */ async subscribe(streams) { if (!this.marketConnected || !this.marketWs) { throw new Error('Market WebSocket not connected'); } const subscription = { method: 'SUBSCRIBE', params: streams, id: this.messageId++ }; this.marketWs.send(JSON.stringify(subscription)); streams.forEach(stream => { this.marketSubscriptions.add(stream); }); this.logger.log('Subscribed to streams:', streams); } /** * Unsubscribe from market streams */ async unsubscribe(streams) { if (!this.marketConnected || !this.marketWs) { throw new Error('Market WebSocket not connected'); } const subscription = { method: 'UNSUBSCRIBE', params: streams, id: this.messageId++ }; this.marketWs.send(JSON.stringify(subscription)); streams.forEach(stream => { this.marketSubscriptions.delete(stream); }); this.logger.log('Unsubscribed from streams:', streams); } /** * Subscribe to aggregate trade stream */ async subscribeAggTrade(symbol) { await this.subscribe([`${symbol.toLowerCase()}@aggTrade`]); } /** * Subscribe to mark price stream */ async subscribeMarkPrice(symbol, updateSpeed = '1s') { await this.subscribe([`${symbol.toLowerCase()}@markPrice@${updateSpeed}`]); } /** * Subscribe to all mark price stream */ async subscribeAllMarkPrice(updateSpeed = '1s') { await this.subscribe([`!markPrice@arr@${updateSpeed}`]); } /** * Subscribe to kline stream */ async subscribeKline(symbol, interval) { await this.subscribe([`${symbol.toLowerCase()}@kline_${interval}`]); } /** * Subscribe to continuous contract kline stream */ async subscribeContinuousKline(pair, contractType, interval) { await this.subscribe([`${pair.toLowerCase()}_${contractType.toLowerCase()}@continuousKline_${interval}`]); } /** * Subscribe to mini ticker stream */ async subscribeMiniTicker(symbol) { await this.subscribe([`${symbol.toLowerCase()}@miniTicker`]); } /** * Subscribe to all mini ticker stream */ async subscribeAllMiniTicker() { await this.subscribe(['!miniTicker@arr']); } /** * Subscribe to ticker stream */ async subscribeTicker(symbol) { await this.subscribe([`${symbol.toLowerCase()}@ticker`]); } /** * Subscribe to all ticker stream */ async subscribeAllTicker() { await this.subscribe(['!ticker@arr']); } /** * Subscribe to book ticker stream */ async subscribeBookTicker(symbol) { await this.subscribe([`${symbol.toLowerCase()}@bookTicker`]); } /** * Subscribe to all book ticker stream */ async subscribeAllBookTicker() { await this.subscribe(['!bookTicker']); } /** * Subscribe to liquidation order stream */ async subscribeLiquidationOrder(symbol) { await this.subscribe([`${symbol.toLowerCase()}@forceOrder`]); } /** * Subscribe to all liquidation order stream */ async subscribeAllLiquidationOrder() { await this.subscribe(['!forceOrder@arr']); } /** * Subscribe to partial book depth stream */ async subscribePartialBookDepth(symbol, levels, updateSpeed = '250ms') { await this.subscribe([`${symbol.toLowerCase()}@depth${levels}@${updateSpeed}`]); } /** * Subscribe to diff book depth stream */ async subscribeDiffBookDepth(symbol, updateSpeed = '250ms') { await this.subscribe([`${symbol.toLowerCase()}@depth@${updateSpeed}`]); } /** * Subscribe to composite index stream */ async subscribeCompositeIndex(symbol) { await this.subscribe([`${symbol.toLowerCase()}@compositeIndex`]); } /** * Close all connections */ close() { this.logger.log('Closing WebSocket connections'); if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; } if (this.marketWs) { this.marketWs.close(); this.marketWs = null; this.marketConnected = false; } if (this.userWs) { this.userWs.close(); this.userWs = null; this.userConnected = false; } this.marketSubscriptions.clear(); this.emit('closed'); } /** * Get connection status */ getStatus() { return { market: this.marketConnected, user: this.userConnected, subscriptions: Array.from(this.marketSubscriptions) }; } } exports.WebSocketClient = WebSocketClient; //# sourceMappingURL=client.js.map