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
JavaScript
"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()}`]);
}
/**
* Subscribe to mark price stream
*/
async subscribeMarkPrice(symbol, updateSpeed = '1s') {
await this.subscribe([`${symbol.toLowerCase()}@${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()}${interval}`]);
}
/**
* Subscribe to continuous contract kline stream
*/
async subscribeContinuousKline(pair, contractType, interval) {
await this.subscribe([`${pair.toLowerCase()}_${contractType.toLowerCase()}${interval}`]);
}
/**
* Subscribe to mini ticker stream
*/
async subscribeMiniTicker(symbol) {
await this.subscribe([`${symbol.toLowerCase()}`]);
}
/**
* 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()}`]);
}
/**
* 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()}`]);
}
/**
* 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()}`]);
}
/**
* 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()}${levels}@${updateSpeed}`]);
}
/**
* Subscribe to diff book depth stream
*/
async subscribeDiffBookDepth(symbol, updateSpeed = '250ms') {
await this.subscribe([`${symbol.toLowerCase()}@${updateSpeed}`]);
}
/**
* Subscribe to composite index stream
*/
async subscribeCompositeIndex(symbol) {
await this.subscribe([`${symbol.toLowerCase()}`]);
}
/**
* 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