UNPKG

binance

Version:

Professional Node.js & JavaScript SDK for Binance REST APIs & WebSockets, with TypeScript & end-to-end tests.

869 lines (866 loc) 40.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebsocketAPIClient = void 0; const typeGuards_1 = require("./util/typeGuards"); const websocket_util_1 = require("./util/websockets/websocket-util"); const websocket_client_1 = require("./websocket-client"); function getFuturesMarketWsKey(market) { if (market === 'usdm') { return websocket_util_1.WS_KEY_MAP.usdmWSAPI; } return websocket_util_1.WS_KEY_MAP.coinmWSAPI; } /** * This is a minimal Websocket API wrapper around the WebsocketClient. * * Some methods support passing in a custom "wsKey". This is a reference to which WS connection should * be used to transmit that message. This is only useful if you wish to use an alternative wss * domain that is supported by the SDK. * * Note: To use testnet, don't set the wsKey - use `testnet: true` in * the constructor instead. * * Note: You can also directly use the sendWSAPIRequest() method to make WS API calls, but some * may find the below methods slightly more intuitive. * * Refer to the WS API promises example for a more detailed example on using sendWSAPIRequest() directly: * https://github.com/tiagosiebler/binance/blob/master/examples/WebSockets/ws-api-raw-promises.ts#L108 */ class WebsocketAPIClient { constructor(options, logger) { /** * Minimal state store around automating sticky "userDataStream.subscribe" sessions */ this.subscribedUserDataStreamState = {}; this.wsClient = new websocket_client_1.WebsocketClient(options, logger); this.options = Object.assign({ resubscribeUserDataStreamAfterReconnect: true, resubscribeUserDataStreamDelaySeconds: 2, attachEventListeners: true, muteLatencyWarning: false, keepMarginListenTokenRefreshed: true }, options); this.logger = this.wsClient.logger; this.setupOptionalEventListeners() .setupRequiredEventListeners() .setupSignMechanic(); } getWSClient() { return this.wsClient; } setTimeOffsetMs(newOffset) { return this.getWSClient().setTimeOffsetMs(newOffset); } disconnectAll() { return __awaiter(this, void 0, void 0, function* () { yield this.wsClient.closeAll(); }); } /* * * SPOT - General requests * */ /** * Test connectivity to the WebSocket API */ testSpotConnectivity(wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'ping', undefined, { authIsOptional: true }); } /** * Test connectivity to the WebSocket API and get the current server time */ getSpotServerTime(wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'time', undefined, { authIsOptional: true }); } /** * Query current exchange trading rules, rate limits, and symbol information */ getSpotExchangeInfo(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'exchangeInfo', params, { authIsOptional: true }); } /* * * SPOT - Market data requests * */ /** * Get current order book * Note: If you need to continuously monitor order book updates, consider using WebSocket Streams */ getSpotOrderBook(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'depth', params, { authIsOptional: true }); } /** * Get recent trades * Note: If you need access to real-time trading activity, consider using WebSocket Streams */ getSpotRecentTrades(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'trades.recent', params, { authIsOptional: true }); } /** * Get historical trades * Note: If fromId is not specified, the most recent trades are returned */ getSpotHistoricalTrades(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'trades.historical', params, { authIsOptional: true }); } /** * Get aggregate trades * Note: An aggregate trade represents one or more individual trades that fill at the same time */ getSpotAggregateTrades(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'trades.aggregate', params, { authIsOptional: true }); } /** * Get klines (candlestick bars) * Note: If you need access to real-time kline updates, consider using WebSocket Streams */ getSpotKlines(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'klines', params, { authIsOptional: true }); } /** * Get klines (candlestick bars) optimized for presentation * Note: This request is similar to klines, having the same parameters and response */ getSpotUIKlines(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'uiKlines', params, { authIsOptional: true }); } /** * Get current average price for a symbol */ getSpotAveragePrice(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'avgPrice', params, { authIsOptional: true }); } /** * Query execution rules (e.g. PRICE_RANGE) for symbol(s) or by symbol status. */ getSpotExecutionRules(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'executionRules', params, { authIsOptional: true }); } /** * Query reference price for a symbol. */ getSpotReferencePrice(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'referencePrice', params, { authIsOptional: true }); } /** * Query how reference price is calculated for a symbol. */ getSpotReferencePriceCalculation(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'referencePrice.calculation', params, { authIsOptional: true }); } /** * Get 24-hour rolling window price change statistics * Note: If you need to continuously monitor trading statistics, consider using WebSocket Streams */ getSpot24hrTicker(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'ticker.24hr', params, { authIsOptional: true }); } /** * Get price change statistics for a trading day */ getSpotTradingDayTicker(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'ticker.tradingDay', params, { authIsOptional: true }); } /** * Get rolling window price change statistics with a custom window * Note: Window size precision is limited to 1 minute */ getSpotTicker(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'ticker', params, { authIsOptional: true }); } /** * Get the latest market price for a symbol * Note: If you need access to real-time price updates, consider using WebSocket Streams */ getSpotSymbolPriceTicker(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'ticker.price', params, { authIsOptional: true }); } /** * Get the current best price and quantity on the order book * Note: If you need access to real-time order book ticker updates, consider using WebSocket Streams */ getSpotSymbolOrderBookTicker(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'ticker.book', params, { authIsOptional: true }); } /* * * SPOT - Session authentication requests * * Note: authentication is automatic * */ getSpotSessionStatus(wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'session.status'); } /* * * SPOT - Trading requests * */ /** * Submit a spot order */ submitNewSpotOrder(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'order.place', params); } /** * Test order placement * Note: Validates new order parameters and verifies your signature but does not send the order into the matching engine */ testSpotOrder(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'order.test', params); } /** * Check execution status of an order * Note: If both orderId and origClientOrderId parameters are specified, only orderId is used */ getSpotOrderStatus(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'order.status', params); } /** * Cancel an active order * Note: If both orderId and origClientOrderId parameters are specified, only orderId is used */ cancelSpotOrder(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'order.cancel', params); } /** * Cancel an existing order and immediately place a new order * Note: If both cancelOrderId and cancelOrigClientOrderId parameters are specified, only cancelOrderId is used */ cancelReplaceSpotOrder(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'order.cancelReplace', params); } /** * Reduce the quantity of an existing open order. * * Read for more info: https://developers.binance.com/docs/binance-spot-api-docs/faqs/order_amend_keep_priority */ amendSpotOrderKeepPriority(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'order.amend.keepPriority', params); } /** * Query execution status of all open orders * Note: If you need to continuously monitor order status updates, consider using WebSocket Streams */ getSpotOpenOrders(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'openOrders.status', params); } /** * Cancel all open orders on a symbol * Note: This includes orders that are part of an order list */ cancelAllSpotOpenOrders(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'openOrders.cancelAll', params); } /** * Place a new order list * Note: This is a deprecated endpoint, consider using placeOCOOrderList instead */ placeSpotOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.place', params); } /** * Place a new OCO (One-Cancels-the-Other) order list * Note: Activation of one order immediately cancels the other */ placeSpotOCOOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.place.oco', params); } /** * Place a new OTO (One-Triggers-the-Other) order list * Note: The pending order is placed only when the working order is fully filled */ placeSpotOTOOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.place.oto', params); } /** * Place a new OTOCO (One-Triggers-One-Cancels-the-Other) order list * Note: The pending orders are placed only when the working order is fully filled */ placeSpotOTOCOOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.place.otoco', params); } /** * Place a new OPO (One-Pays-the-Other) order list * Note: One order pays for the other - when the working order is filled, the pending order is placed */ placeSpotOPOOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.place.opo', params); } /** * Place a new OPOCO (One-Pays-One-Cancels-the-Other) order list * Note: Combines OPO and OCO - working order pays for two pending orders, one cancels the other */ placeSpotOPOCOOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.place.opoco', params); } /** * Check execution status of an order list * Note: If both origClientOrderId and orderListId parameters are specified, only origClientOrderId is used */ getSpotOrderListStatus(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.status', params); } /** * Cancel an active order list * Note: If both orderListId and listClientOrderId parameters are specified, only orderListId is used */ cancelSpotOrderList(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'orderList.cancel', params); } /** * Query execution status of all open order lists * Note: If you need to continuously monitor order status updates, consider using WebSocket Streams */ getSpotOpenOrderLists(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'openOrderLists.status', params); } /** * Place a new order using Smart Order Routing (SOR) * Note: Only supports LIMIT and MARKET orders. quoteOrderQty is not supported */ placeSpotSOROrder(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'sor.order.place', params); } /** * Test new order creation and signature/recvWindow using Smart Order Routing (SOR) * Note: Creates and validates a new order but does not send it into the matching engine */ testSpotSOROrder(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'sor.order.test', params); } /* * * SPOT - Account requests * */ /** * Query information about your account, including balances * Note: Weight: 20 */ getSpotAccountInformation(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'account.status', params); } /** * Query your current unfilled order count for all intervals * Note: Weight: 40 */ getSpotOrderRateLimits(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'account.rateLimits.orders', params); } /** * Query information about all your orders – active, canceled, filled – filtered by time range * Note: Weight: 20 */ getSpotAllOrders(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'allOrders', params); } /** * Query information about all your order lists, filtered by time range * Note: Weight: 20 */ getSpotAllOrderLists(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'allOrderLists', params); } /** * Query information about all your trades, filtered by time range * Note: Weight: 20 */ getSpotMyTrades(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'myTrades', params); } /** * Displays the list of orders that were expired due to STP * Note: Weight varies based on query type (2-20) */ getSpotPreventedMatches(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'myPreventedMatches', params); } /** * Retrieves allocations resulting from SOR order placement * Note: Weight: 20 */ getSpotAllocations(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'myAllocations', params); } /** * Get current account commission rates * Note: Weight: 20 */ getSpotAccountCommission(params, wsKey) { return this.wsClient.sendWSAPIRequest(wsKey || websocket_util_1.WS_KEY_MAP.mainWSAPI, 'account.commission', params); } /* * * FUTURES - Market data requests * */ /** * Get current order book for futures * Note: If you need to continuously monitor order book updates, consider using WebSocket Streams */ getFuturesOrderBook(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'depth', params, { authIsOptional: true }); } /** * Get latest price for a futures symbol or symbols * Note: If symbol is not provided, prices for all symbols will be returned */ getFuturesSymbolPriceTicker(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'ticker.price', params, { authIsOptional: true }); } /** * Get best price/qty on the order book for a futures symbol or symbols * Note: If symbol is not provided, bookTickers for all symbols will be returned */ getFuturesSymbolOrderBookTicker(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'ticker.book', params, { authIsOptional: true }); } /* * * FUTURES - Trading requests * */ /** * Submit a futures order * * This endpoint is used for both USDM and COINM futures. */ submitNewFuturesOrder(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'order.place', params); } /** * Modify an existing futures order * * This endpoint is used for both USDM and COINM futures. */ modifyFuturesOrder(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'order.modify', params); } /** * Cancel a futures order * * This endpoint is used for both USDM and COINM futures. */ cancelFuturesOrder(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'order.cancel', params); } /** * Query futures order status * * This endpoint is used for both USDM and COINM futures. */ getFuturesOrderStatus(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'order.status', params); } /** * Get current position information (V2) * Note: Only symbols that have positions or open orders will be returned */ getFuturesPositionV2(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'v2/account.position', params); } /** * Get current position information * Note: Only symbols that have positions or open orders will be returned * * This endpoint is used for both USDM and COINM futures. */ getFuturesPosition(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'account.position', params); } /** * Send in a new algo order * * Ref: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/New-Algo-Order */ submitNewFuturesAlgoOrder(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'algoOrder.place', params); } /** * Cancel an active algo order. * * Ref: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Cancel-Algo-Order * @param params */ cancelFuturesAlgoOrder(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'algoOrder.cancel', params); } /* * * FUTURES - Account requests * */ /** * Get account balance information (V2) * Note: Returns balance information for all assets */ getFuturesAccountBalanceV2(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'v2/account.balance', params); } /** * Get account balance information * Note: Returns balance information for all assets * * This endpoint is used for both USDM and COINM futures. */ getFuturesAccountBalance(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'account.balance', params); } /** * Get account information (V2) * Note: Returns detailed account information including positions and assets */ getFuturesAccountStatusV2(params) { return this.wsClient.sendWSAPIRequest(websocket_util_1.WS_KEY_MAP.usdmWSAPI, 'v2/account.status', params); } /** * Get account information * Note: Returns detailed account information including positions and assets * * This endpoint is used for both USDM and COINM futures. */ getFuturesAccountStatus(market, params) { return this.wsClient.sendWSAPIRequest(getFuturesMarketWsKey(market), 'account.status', params); } /* * * User data stream requests * */ /** * Start the user data stream for an apiKey (passed as param). * * Note: for "Spot" markets, the listenKey workflow is deprecated, use `subscribeUserDataStream()` instead. * * @param params * @param wsKey * @returns listenKey */ startUserDataStreamForKey(params, wsKey = websocket_util_1.WS_KEY_MAP.mainWSAPI) { return this.wsClient.sendWSAPIRequest(wsKey, 'userDataStream.start', params); } /** * Attempt to "ping" a listen key. * * Note: for "Spot" markets, the listenKey workflow is deprecated, use `subscribeUserDataStream()` instead. * * @param params * @param wsKey * @returns */ pingUserDataStreamForKey(params, wsKey = websocket_util_1.WS_KEY_MAP.mainWSAPI) { return this.wsClient.sendWSAPIRequest(wsKey, 'userDataStream.ping', params); } /** * Stop the user data stream listen key. * * @param params * @param wsKey * @returns */ stopUserDataStreamForKey(params, wsKey = websocket_util_1.WS_KEY_MAP.mainWSAPI) { return this.wsClient.sendWSAPIRequest(wsKey, 'userDataStream.stop', params); } /** * Consolidated method to clear any timers related to user data stream subscriptions for a given wsKey, if found. * * @param wsKey */ clearUserDataStreamTimers(wsKey) { var _a, _b; // Just in case the refresh timer is still running // Harmless given one connection, but still unnecessary if ((_a = this.subscribeUserDataStream[wsKey]) === null || _a === void 0 ? void 0 : _a.refreshTimeout) { clearTimeout(this.subscribeUserDataStream[wsKey].refreshTimeout); delete this.subscribeUserDataStream[wsKey].refreshTimeout; } if ((_b = this.subscribedUserDataStreamState[wsKey]) === null || _b === void 0 ? void 0 : _b.respawnTimeout) { clearTimeout(this.subscribedUserDataStreamState[wsKey].respawnTimeout); delete this.subscribedUserDataStreamState[wsKey].respawnTimeout; } } /** * Request user data stream subscription on the currently authenticated connection. * * If reconnected, this will automatically resubscribe unless you unsubscribe manually. */ subscribeUserDataStream(wsKey_1) { return __awaiter(this, arguments, void 0, function* (wsKey, isRefreshingToken = false) { const resolvedWsKey = this.options.testnet ? (0, websocket_util_1.getTestnetWsKey)(wsKey) : wsKey; const keyType = this.getWSClient().getSignKeyType(); let res; // User data stream works differently for margin, since Feb 2026, via a listen token mechanic if (resolvedWsKey === websocket_util_1.WS_KEY_MAP.marginUserData) { const { token, expirationTime } = yield this.wsClient.fetchMarginListenToken({ // validity: 30 * 1000, // milliseconds (30 secs) // validity: 5 * 60 * 1000, // milliseconds (5 mins ) }); this.logger.trace('subscribeUserDataStream() - fetched margin listen token, preparing to subscribe with token: ', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey, token, expirationTime, expirationDt: new Date(expirationTime), isRefreshingToken })); res = yield this.wsClient.sendWSAPIRequest(resolvedWsKey, 'userDataStream.subscribe.listenToken', { listenToken: token, }, { authIsOptional: true }); this.subscribedUserDataStreamState[resolvedWsKey] = { subscribedAt: new Date(), subscribeAttempt: 0, }; // Set respawn timer, to automatically fetch and sub to new token before expiry. Should be seamless on existing connection. if (this.options.keepMarginListenTokenRefreshed) { const hours24Ms = 24 * 60 * 60 * 1000; // try to respawn 30 seconds before expiration const respawnAfterMs = (expirationTime || hours24Ms) - Date.now() - 30 * 1000; this.subscribedUserDataStreamState[resolvedWsKey].respawnTimeout = setTimeout(() => { this.tryResubscribeUserDataStream(wsKey, true); }, respawnAfterMs); } return res; } const FUTURES_USER_DATA_WARNING = `Note: For the ${wsKey} user data stream, only the start/ping/stop commands are currently available for managing the active listenKey. The SDK will now automatically start the traditional listenKey user data stream instead. You can also directly use the WebsocketClient's subscribeUsdFuturesUserDataStream()/subscribeCoinFuturesUserDataStream() methods if preferred.`; if (wsKey === websocket_util_1.WS_KEY_MAP.usdmWSAPI || wsKey === websocket_util_1.WS_KEY_MAP.usdmWSAPITestnet) { this.logger.info(FUTURES_USER_DATA_WARNING, Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey })); yield this.getWSClient().subscribeUsdFuturesUserDataStream(wsKey === websocket_util_1.WS_KEY_MAP.usdmWSAPI ? websocket_util_1.WS_KEY_MAP.usdm : websocket_util_1.WS_KEY_MAP.usdmTestnet); return; } if (wsKey === websocket_util_1.WS_KEY_MAP.coinmWSAPI || wsKey === websocket_util_1.WS_KEY_MAP.coinmWSAPITestnet) { this.logger.info(FUTURES_USER_DATA_WARNING, Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey })); yield this.getWSClient().subscribeCoinFuturesUserDataStream(wsKey === websocket_util_1.WS_KEY_MAP.coinmWSAPI ? websocket_util_1.WS_KEY_MAP.coinm : websocket_util_1.WS_KEY_MAP.coinmTestnet); return; } if (keyType === 'Ed25519') { // for Ed25519 keys, no signature is needed, we should already be authenticated in session res = yield this.wsClient.sendWSAPIRequest(resolvedWsKey, 'userDataStream.subscribe'); } else { // for HMAC & RSA keys, request will be signed and sent to a dedicated topic res = yield this.wsClient.sendWSAPIRequest(resolvedWsKey, 'userDataStream.subscribe.signature', { timestamp: Date.now() }); } // Used to track whether this connection had the general "userDataStream.subscribe" called. // Used as part of `resubscribeUserDataStreamAfterReconnect` to know which connections to resub. this.subscribedUserDataStreamState[resolvedWsKey] = { subscribedAt: new Date(), subscribeAttempt: 0, }; return res; }); } /** * Unsubscribe from the user data stream subscription on the currently authenticated connection. * * If reconnected, this will also stop it from automatically resubscribing after reconnect. */ unsubscribeUserDataStream(wsKey) { const resolvedWsKey = this.options.testnet ? (0, websocket_util_1.getTestnetWsKey)(wsKey) : wsKey; delete this.subscribedUserDataStreamState[resolvedWsKey]; return this.wsClient.sendWSAPIRequest(resolvedWsKey, 'userDataStream.unsubscribe'); } /** * * * * * * * * Private methods for handling some of the convenience/automation provided by the WS API Client * * * * * * * */ setupOptionalEventListeners() { if (this.options.attachEventListeners) { /** * General event handlers for monitoring the WebsocketClient */ this.wsClient .on('open', (data) => { console.log(new Date(), 'ws connected', data.wsKey); }) .on('reconnecting', ({ wsKey }) => { console.log(new Date(), 'ws automatically reconnecting.... ', wsKey); }) .on('reconnected', (data) => { console.log(new Date(), 'ws has reconnected ', data === null || data === void 0 ? void 0 : data.wsKey); }) .on('authenticated', (data) => { console.info(new Date(), 'ws has authenticated ', data === null || data === void 0 ? void 0 : data.wsKey); }) .on('exception', (data) => { try { // Blind JSON.stringify can fail on circular references console.error(new Date(), 'ws exception: ', JSON.stringify(data)); } catch (_a) { console.error(new Date(), 'ws exception: ', data); } }); } return this; } setupRequiredEventListeners() { /** * Required event handlers for handling automatic resubscribe to user data stream after reconnect, etc */ this.wsClient .on('close', ({ wsKey }) => { this.handleWSCloseEvent({ wsKey }); }) .on('reconnected', ({ wsKey }) => { this.handleWSReconnectedEvent({ wsKey }); }) .on('message', (data) => { // Handle notification that user data stream has been terminated (e.g. due to expired listen token) and attempt to automatically resubscribe with new token // Designed around margin mode mechanics: https://developers.binance.com/docs/margin_trading/trade-data-stream#response-example-1 if ((0, typeGuards_1.isWsEventStreamTerminatedRaw)(data)) { const wsKey = data.wsKey; const wsMarket = data.wsMarket; const wsUserDataStreamState = this.subscribedUserDataStreamState[wsKey]; if (!wsUserDataStreamState) { console.warn('Received eventStreamTerminated message for a wsKey that does not have an active user data stream subscription, ignoring.', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey, wsMarket, eventData: data })); return; } if (wsKey === websocket_util_1.WS_KEY_MAP.marginUserData) { this.logger.error('Received eventStreamTerminated message, likely due to expired listen token. Will attempt to fetch new token and resubscribe.', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey, wsMarket, eventData: data })); this.tryResubscribeUserDataStream(wsKey, false); return; } this.logger.error('Fatal: Received eventStreamTerminated message for wsKey that is not known to receive this event. May not be able to automatically recover. Report this with steps to reproduce, if you see this message.', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey, wsMarket, eventData: data })); return; } // console.log(new Date(), 'ws message received: ', data); }); return this; } setupSignMechanic() { const signKeyType = this.wsClient.getSignKeyType(); switch (signKeyType) { case undefined: case 'Ed25519': { break; } case 'HMAC': case 'RSASSA-PKCS1-v1_5': { this.wsClient.setAuthOnConnect(false).setAuthEveryRequest(true); if (this.options.muteLatencyWarning !== true) { this.logger.info(`NOTICE: Detected "${signKeyType}" API Keys. Your API key will work correctly, but with the following differences: - Each request will be individually signed (per-request signing mode) - Session authentication is NOT available for HMAC/RSA keys - This may result in slightly higher latency per request If you are only using the user data stream, you can ignore this warning as only applies if you are making WS API requests. If you are latency sensitive, consider using Ed25519 keys instead. For more information refer to the readme: https://github.com/tiagosiebler/binance?tab=readme-ov-file#websocket-api`, Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { signKeyType })); } break; } default: { (0, typeGuards_1.neverGuard)(signKeyType, `Unhandled sign key type "${signKeyType}"`); } } return this; } tryResubscribeUserDataStream(wsKey, isRefreshingToken) { return __awaiter(this, void 0, void 0, function* () { const subscribeState = this.getSubscribedUserDataStreamState(wsKey); const respawnDelayInSeconds = this.options.resubscribeUserDataStreamDelaySeconds; this.logger.error('tryResubscribeUserDataStream(): resubscribing to user data stream....', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey, isRefreshingToken })); try { // Just in case the refresh timer is still running this.clearUserDataStreamTimers(wsKey); subscribeState.subscribeAttempt++; const subscribeAttempts = subscribeState.subscribeAttempt; yield this.subscribeUserDataStream(wsKey, isRefreshingToken); this.logger.info('tryResubscribeUserDataStream()->ok', Object.assign(Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), subscribeState), { wsKey, isRefreshingToken, subscribeAttempts })); } catch (e) { this.logger.error('tryResubscribeUserDataStream() exception - retry after timeout', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey, exception: e, subscribeState, isRefreshingToken })); subscribeState.respawnTimeout = setTimeout(() => { this.tryResubscribeUserDataStream(wsKey, isRefreshingToken); }, 1000 * respawnDelayInSeconds); this.subscribedUserDataStreamState[wsKey] = Object.assign({}, subscribeState); } }); } getSubscribedUserDataStreamState(wsKey) { const subscribedState = this.subscribedUserDataStreamState[wsKey] || { subscribedAt: new Date(), subscribeAttempt: 0, }; return subscribedState; } handleWSCloseEvent(params) { var _a; const wsKey = params.wsKey; // Not a WS API connection if (!(0, typeGuards_1.isWSAPIWsKey)(wsKey)) { return; } // If connection closes and we have a record of subscribing to user data stream on this connection, then we can assume it was likely an unintentional disconnect and we should attempt to resubscribe when it reconnects if (this.subscribedUserDataStreamState[wsKey]) { this.logger.error('handleWSCloseEvent() -> detected close on connection with active user data stream subscription, will attempt to resubscribe when it reconnects', Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey })); if ((_a = this.subscribeUserDataStream[wsKey]) === null || _a === void 0 ? void 0 : _a.refreshTimeout) { clearTimeout(this.subscribeUserDataStream[wsKey].refreshTimeout); delete this.subscribeUserDataStream[wsKey].refreshTimeout; } } } handleWSReconnectedEvent(params) { var _a, _b; const wsKey = params.wsKey; // Not a WS API connection if (!(0, typeGuards_1.isWSAPIWsKey)(wsKey)) { return; } const fnName = 'handleWSReconnectedEvent()'; if (!this.subscribedUserDataStreamState[wsKey]) { // No record of ever subscribing to user data stream on this connection, so no need to resubscribe return; } // Clear token refresh timer, just in case. No need on a dead & potentially respawning connection if ((_a = this.subscribeUserDataStream[wsKey]) === null || _a === void 0 ? void 0 : _a.refreshTimeout) { clearTimeout(this.subscribeUserDataStream[wsKey].refreshTimeout); delete this.subscribeUserDataStream[wsKey].refreshTimeout; } // Feature enabled if (this.options.resubscribeUserDataStreamAfterReconnect) { // Delay existing timer, if exists if ((_b = this.subscribedUserDataStreamState[wsKey]) === null || _b === void 0 ? void 0 : _b.respawnTimeout) { clearTimeout(this.subscribedUserDataStreamState[wsKey].respawnTimeout); delete this.subscribedUserDataStreamState[wsKey].respawnTimeout; this.logger.error(`${fnName} -> resubUserData(): Respawn timer already active while trying to queue respawn...delaying existing timer further...`, Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey })); } this.logger.trace(`${fnName} -> resubUserData():: queued resubscribe for wsKey user data stream`, Object.assign(Object.assign({}, websocket_util_1.WS_LOGGER_CATEGORY), { wsKey })); // Queue resubscribe workflow this.subscribedUserDataStreamState[wsKey].respawnTimeout = setTimeout(() => { const isRefreshingToken = false; this.tryResubscribeUserDataStream(wsKey, isRefreshingToken); }, 1000 * this.options.resubscribeUserDataStreamDelaySeconds); } } } exports.WebsocketAPIClient = WebsocketAPIClient; //# sourceMappingURL=websocket-api-client.js.map