UNPKG

binance

Version:

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

916 lines 38.4 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WS_ERROR_ENUM = exports.WS_AUTH_ON_CONNECT_KEYS = exports.WS_KEY_DEMO_URL_MAP = exports.WS_KEY_MM_URL_MAP = exports.WS_KEY_URL_MAP = exports.WS_KEYS_FUTURES = exports.WS_KEYS_SPOT = exports.WS_KEY_MAP = exports.EVENT_TYPES_USER_DATA = exports.WS_LOGGER_CATEGORY = void 0; exports.getWsURLSuffix = getWsURLSuffix; exports.getWSConnectionCategoryForTopic = getWSConnectionCategoryForTopic; exports.isPrivateWsTopic = isPrivateWsTopic; exports.getTestnetWsKey = getTestnetWsKey; exports.getWsUrl = getWsUrl; exports.getMaxTopicsPerSubscribeEvent = getMaxTopicsPerSubscribeEvent; exports.safeTerminateWs = safeTerminateWs; exports.getPromiseRefForWSAPIRequest = getPromiseRefForWSAPIRequest; exports.getNormalisedTopicRequests = getNormalisedTopicRequests; exports.getUsdmMarketDataWsKeyForTopic = getUsdmMarketDataWsKeyForTopic; exports.getTopicsPerWSKey = getTopicsPerWSKey; exports.parseEventTypeFromMessage = parseEventTypeFromMessage; exports.resolveUserDataMarketForWsKey = resolveUserDataMarketForWsKey; exports.getWsKeyForProductGroup = getWsKeyForProductGroup; exports.parseRawWsMessageLegacy = parseRawWsMessageLegacy; exports.parseRawWsMessage = parseRawWsMessage; exports.getContextFromWsKey = getContextFromWsKey; exports.getLegacyWsStoreKeyWithContext = getLegacyWsStoreKeyWithContext; exports.getLegacyWsKeyContext = getLegacyWsKeyContext; exports.getRealWsKeyFromDerivedWsKey = getRealWsKeyFromDerivedWsKey; exports.appendEventMarket = appendEventMarket; exports.isWSPingFrameAvailable = isWSPingFrameAvailable; exports.isWSPongFrameAvailable = isWSPongFrameAvailable; /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const typeGuards_1 = require("../typeGuards"); exports.WS_LOGGER_CATEGORY = { category: 'binance-ws' }; exports.EVENT_TYPES_USER_DATA = [ 'balanceUpdate', 'executionReport', 'listStatus', 'listenKeyExpired', 'outboundAccountPosition', 'ACCOUNT_CONFIG_UPDATE', 'ACCOUNT_UPDATE', 'MARGIN_CALL', 'ORDER_TRADE_UPDATE', 'ALGO_UPDATE', 'TRADE_LITE', 'CONDITIONAL_ORDER_TRIGGER_REJECT', ]; exports.WS_KEY_MAP = { // https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams main: 'main', // spot, margin, isolated margin, user data main2: 'main2', // spot, margin, isolated margin, user data | alternative main3: 'main3', // spot, margin, isolated margin | alternative | MARKET DATA ONLY | NO USER DATA // https://developers.binance.com/docs/binance-spot-api-docs/testnet/web-socket-streams#general-wss-information mainTestnetPublic: 'mainTestnetPublic', mainTestnetUserData: 'mainTestnetUserData', // https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/general-api-information mainWSAPI: 'mainWSAPI', // trading over WS in spot, margin, isolated margin. User data supported too. mainWSAPI2: 'mainWSAPI2', // trading over WS in spot, margin, isolated margin. User data supported too. mainWSAPITestnet: 'mainWSAPITestnet', // trading over WS in spot, margin, isolated margin | TESTNET // https://developers.binance.com/docs/margin_trading/risk-data-stream marginRiskUserData: 'marginRiskUserData', // https://developers.binance.com/docs/margin_trading/trade-data-stream // Used for the margin user data stream, via the WebSocket API, via the listenToken mechanic. marginUserData: 'marginUserData', // https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams // market data, user data /** * USD-M Futures product-group alias. * * For raw subscribe/unsubscribe calls, this is auto-routed to the required * split USD-M market data key (`usdmPublic` or `usdmMarket`) per topic. * * For direct endpoint control, use `usdmPublic`, `usdmMarket`, or * `usdmPrivate`. */ usdm: 'usdm', usdmPrivate: 'usdmPrivate', // user data only (for split streams) usdmPublic: 'usdmPublic', // high freq public market data (primarily book and tickers) usdmMarket: 'usdmMarket', // all other market data // https://developers.binance.com/docs/derivatives/usds-margined-futures/general-info usdmTestnet: 'usdmTestnet', usdmTestnetPrivate: 'usdmTestnetPrivate', // user data only (for split streams) usdmTestnetPublic: 'usdmTestnetPublic', // high freq public market data (primarily book and tickers) usdmTestnetMarket: 'usdmTestnetMarket', // all other market data // https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-api-general-info // ONLY WS API | NO USER DATA usdmWSAPI: 'usdmWSAPI', usdmWSAPITestnet: 'usdmWSAPITestnet', // https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams // market data, user data coinm: 'coinm', coinm2: 'coinm2', // coinmPrivate: 'coinmPrivate', // user data only (for split streams) // coinmPublic: 'coinmPublic', // high freq public market data (primarily book and tickers) // coinmMarket: 'coinmMarket', // all other market data // https://developers.binance.com/docs/derivatives/coin-margined-futures/general-info coinmTestnet: 'coinmTestnet', // coinmTestnetPrivate: 'coinmTestnetPrivate', // user data only (for split streams) // coinmTestnetPublic: 'coinmTestnetPublic', // high freq public market data (primarily book and tickers) // coinmTestnetMarket: 'coinmTestnetMarket', // all other market data // https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-api-general-info // ONLY WS API | NO USER DATA coinmWSAPI: 'coinmWSAPI', coinmWSAPITestnet: 'coinmWSAPITestnet', eoptions: 'eoptions', // optionsTestnet: 'optionsTestnet', // https://developers.binance.com/docs/derivatives/portfolio-margin/user-data-streams portfolioMarginUserData: 'portfolioMarginUserData', // https://developers.binance.com/docs/derivatives/portfolio-margin-pro/portfolio-margin-pro-user-data-stream portfolioMarginProUserData: 'portfolioMarginProUserData', // Alpha Trading WebSocket Market Data - https://developers.binance.com/docs/alpha/change-log alpha: 'alpha', }; exports.WS_KEYS_SPOT = [ exports.WS_KEY_MAP.main, exports.WS_KEY_MAP.main2, exports.WS_KEY_MAP.main3, exports.WS_KEY_MAP.mainTestnetPublic, exports.WS_KEY_MAP.mainTestnetUserData, exports.WS_KEY_MAP.mainWSAPI, exports.WS_KEY_MAP.mainWSAPI2, exports.WS_KEY_MAP.mainWSAPITestnet, exports.WS_KEY_MAP.marginRiskUserData, ]; exports.WS_KEYS_FUTURES = [ exports.WS_KEY_MAP.usdm, exports.WS_KEY_MAP.usdmPrivate, exports.WS_KEY_MAP.usdmPublic, exports.WS_KEY_MAP.usdmMarket, exports.WS_KEY_MAP.usdmTestnet, exports.WS_KEY_MAP.usdmTestnetPrivate, exports.WS_KEY_MAP.usdmTestnetPublic, exports.WS_KEY_MAP.usdmTestnetMarket, exports.WS_KEY_MAP.usdmWSAPI, exports.WS_KEY_MAP.usdmWSAPITestnet, exports.WS_KEY_MAP.coinm, exports.WS_KEY_MAP.coinm2, exports.WS_KEY_MAP.coinmTestnet, exports.WS_KEY_MAP.coinmWSAPI, exports.WS_KEY_MAP.coinmWSAPITestnet, ]; exports.WS_KEY_URL_MAP = { // https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams main: 'wss://stream.binance.com:9443', // spot, margin, isolated margin, user data main2: 'wss://stream.binance.com:443', // spot, margin, isolated margin, user data | alternative main3: 'wss://data-stream.binance.vision', // spot, margin, isolated margin | alternative | MARKET DATA ONLY | NO USER DATA // https://developers.binance.com/docs/binance-spot-api-docs/testnet/web-socket-streams#general-wss-information mainTestnetPublic: 'wss://stream.testnet.binance.vision', mainTestnetUserData: 'wss://stream.testnet.binance.vision:9443', // https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/general-api-information mainWSAPI: 'wss://ws-api.binance.com:443', mainWSAPI2: 'wss://ws-api.binance.com:9443', mainWSAPITestnet: 'wss://ws-api.testnet.binance.vision', // https://developers.binance.com/docs/margin_trading/risk-data-stream // Margin websocket only support Cross Margin Accounts marginRiskUserData: 'wss://margin-stream.binance.com', // New margin WS API endpoint with listenToken mechanic marginUserData: 'wss://ws-api.binance.com:443', // https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams // market data, user data usdm: 'wss://fstream.binance.com', usdmPublic: 'wss://fstream.binance.com', usdmMarket: 'wss://fstream.binance.com', usdmPrivate: 'wss://fstream.binance.com', // https://developers.binance.com/docs/derivatives/usds-margined-futures/general-info usdmTestnet: 'wss://stream.binancefuture.com', usdmTestnetPublic: 'wss://stream.binancefuture.com', usdmTestnetMarket: 'wss://stream.binancefuture.com', usdmTestnetPrivate: 'wss://stream.binancefuture.com', // https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-api-general-info // ONLY WS API // Suffix is handled in getWsURLSuffix usdmWSAPI: 'wss://ws-fapi.binance.com', usdmWSAPITestnet: 'wss://testnet.binancefuture.com', // https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-market-streams // market data, user data coinm: 'wss://dstream.binance.com', coinm2: 'wss://dstream-auth.binance.com', // Warning, coinm2 requires a listenkey // coinmPublic: 'wss://dstream.binance.com', // coinmMarket: 'wss://dstream.binance.com', // coinmPrivate: 'wss://dstream.binance.com', // https://developers.binance.com/docs/derivatives/coin-margined-futures/general-info coinmTestnet: 'wss://dstream.binancefuture.com', // coinmTestnetPublic: 'wss://dstream.binancefuture.com', // coinmTestnetMarket: 'wss://dstream.binancefuture.com', // coinmTestnetPrivate: 'wss://dstream.binancefuture.com', // https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-api-general-info // ONLY WS API | NO USER DATA // Suffix is handled in getWsURLSuffix coinmWSAPI: 'wss://ws-dapi.binance.com', coinmWSAPITestnet: 'coinmWSAPITestnet', // https://developers.binance.com/docs/derivatives/options-trading/websocket-market-streams // https://developers.binance.com/docs/derivatives/options-trading/user-data-streams eoptions: 'wss://fstream.binance.com', // eoptions: 'wss://nbstream.binance.com/eoptions', // optionsTestnet: 'wss://testnetws.binanceops.com', // https://developers.binance.com/docs/derivatives/portfolio-margin/user-data-streams portfolioMarginUserData: 'wss://fstream.binance.com', // https://developers.binance.com/docs/derivatives/portfolio-margin-pro/portfolio-margin-pro-user-data-stream portfolioMarginProUserData: 'wss://fstream.binance.com', // Alpha Trading WebSocket Market Data - https://developers.binance.com/docs/alpha-trading // Base URL is domain only; suffix in getWsURLSuffix alpha: 'wss://nbstream.binance.com', }; exports.WS_KEY_MM_URL_MAP = { // Spot connections - no MM endpoints main: undefined, main2: undefined, main3: undefined, mainTestnetPublic: undefined, mainTestnetUserData: undefined, mainWSAPI: undefined, mainWSAPI2: undefined, mainWSAPITestnet: undefined, marginRiskUserData: undefined, marginUserData: undefined, // USDM Futures MM endpoints usdm: 'wss://fstream-mm.binance.com', usdmMarket: 'wss://fstream-mm.binance.com', usdmPrivate: 'wss://fstream-mm.binance.com', usdmPublic: 'wss://fstream-mm.binance.com', usdmTestnet: undefined, // No MM endpoint for testnet usdmTestnetPublic: undefined, usdmTestnetMarket: undefined, usdmTestnetPrivate: undefined, usdmWSAPI: 'wss://ws-fapi-mm.binance.com', usdmWSAPITestnet: undefined, // No MM endpoint for testnet // COINM Futures MM endpoints coinm: 'wss://dstream-mm.binance.com', coinm2: undefined, // No MM endpoint for coinm2 // coinmPublic: 'wss://dstream-mm.binance.com', // coinmMarket: 'wss://dstream-mm.binance.com', // coinmPrivate: 'wss://dstream-mm.binance.com', coinmTestnet: undefined, // No MM endpoint for testnet // coinmTestnetPublic: undefined, // coinmTestnetMarket: undefined, // coinmTestnetPrivate: undefined, coinmWSAPI: 'wss://ws-dapi-mm.binance.com', coinmWSAPITestnet: undefined, // No MM endpoint for testnet // Other connections - no MM endpoints eoptions: undefined, portfolioMarginUserData: undefined, portfolioMarginProUserData: undefined, // Alpha - no MM endpoint alpha: undefined, }; // Demo Trading WebSocket URLs exports.WS_KEY_DEMO_URL_MAP = { // Demo Trading - Spot main: 'wss://demo-stream.binance.com:9443', main2: 'wss://demo-stream.binance.com:443', main3: 'wss://demo-stream-sbe.binance.com', // Alternative stream mainTestnetPublic: undefined, // No demo for testnet mainTestnetUserData: undefined, // No demo for testnet // Demo Trading - Spot WebSocket API mainWSAPI: 'wss://demo-ws-api.binance.com:443', mainWSAPI2: 'wss://demo-ws-api.binance.com:9443', mainWSAPITestnet: undefined, // No demo for testnet // Margin Risk - no demo endpoint marginRiskUserData: undefined, marginUserData: undefined, // Demo Trading - USDM Futures usdm: 'wss://fstream.binancefuture.com', usdmPublic: 'wss://fstream.binancefuture.com', usdmMarket: 'wss://fstream.binancefuture.com', usdmPrivate: 'wss://fstream.binancefuture.com', usdmTestnet: undefined, // No demo for testnet usdmTestnetMarket: undefined, usdmTestnetPrivate: undefined, usdmTestnetPublic: undefined, usdmWSAPI: 'wss://testnet.binancefuture.com', usdmWSAPITestnet: undefined, // No demo for testnet // Demo Trading - COINM Futures coinm: 'wss://dstream.binancefuture.com', // coinmPublic: 'wss://dstream.binancefuture.com', // coinmMarket: 'wss://dstream.binancefuture.com', // coinmPrivate: 'wss://dstream.binancefuture.com', coinm2: undefined, // No demo for coinm2 coinmTestnet: undefined, // No demo for testnet // coinmTestnetPublic: undefined, // coinmTestnetMarket: undefined, // coinmTestnetPrivate: undefined, coinmWSAPI: 'wss://testnet.binancefuture.com', coinmWSAPITestnet: undefined, // No demo for testnet // Other connections - no demo endpoints eoptions: undefined, portfolioMarginUserData: undefined, portfolioMarginProUserData: undefined, // Alpha - no demo endpoint documented alpha: undefined, }; function getWsURLSuffix(wsKey, connectionType) { switch (wsKey) { case 'main': case 'main2': case 'main3': case 'marginRiskUserData': case 'mainTestnetPublic': case 'mainTestnetUserData': { switch (connectionType) { case 'public': case 'market': return '/stream'; case 'private': return '/ws/'; // /ws/listenKeyHere default: { throw (0, typeGuards_1.neverGuard)(connectionType, `Unhandled connectionType "${wsKey}/${connectionType}"`); } } } case 'mainWSAPITestnet': case 'mainWSAPI': case 'mainWSAPI2': case 'marginUserData': { return '/ws-api/v3'; } // USDM Futures case 'usdm': case 'usdmTestnet': { switch (connectionType) { // Legacy market data endpoints subscribe via /stream. // In future, routing should automatically direct topics to usdmPublic or usdmMarket, depending on the topic case 'public': case 'market': return '/stream'; case 'private': // listen key will be suffixed to this return '/ws/'; // /ws/listenKeyHere default: { throw (0, typeGuards_1.neverGuard)(connectionType, `Unhandled connectionType "${wsKey}/${connectionType}"`); } } } case 'usdmPublic': case 'usdmTestnetPublic': return '/public/stream'; case 'usdmMarket': case 'usdmTestnetMarket': return '/market/stream'; case 'usdmPrivate': case 'usdmTestnetPrivate': // listen key will be suffixed to this return '/private/stream?listenKey='; case 'usdmWSAPI': case 'usdmWSAPITestnet': { return '/ws-fapi/v1'; } case 'coinmWSAPI': case 'coinmWSAPITestnet': { return '/ws-dapi/v1'; } // CoinM Futures & European Options case 'coinm': case 'coinmTestnet': case 'eoptions': switch (connectionType) { case 'public': case 'market': return '/stream'; case 'private': // listen key will be suffixed to this, both coinm & eoptions return '/ws/'; // /ws/listenKeyHere default: { throw (0, typeGuards_1.neverGuard)(connectionType, `Unhandled connectionType "${wsKey}/${connectionType}"`); } } // case 'coinmPublic': // case 'coinmTestnetPublic': // return '/public/stream'; // case 'coinmMarket': // case 'coinmTestnetMarket': // return '/market/stream'; // case 'coinmPrivate': // case 'coinmTestnetPrivate': // return '/private/stream'; case 'coinm2': return '/stream&listenKey='; case 'portfolioMarginUserData': // listen key will be suffixed to this return '/pm/ws/'; // pm/ws/listenKeyHere case 'portfolioMarginProUserData': // listen key will be suffixed to this return '/pm-classic/ws/'; // pm-classic/ws/listenKeyHere case 'alpha': // https://developers.binance.com/docs/alpha/market-data/websocket-market-data // wss://nbstream.binance.com/w3w/wsa/stream return '/w3w/wsa/stream'; default: { throw (0, typeGuards_1.neverGuard)(wsKey, `Unhandled WsKey "${wsKey}"`); } } } /** * Follows API spec to route topics to specific WS Endpoints: * https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Important-WebSocket-Change-Notice#public--market-combined-subscriptions */ function getWSConnectionCategoryForTopic(wsTopic) { switch (wsTopic) { case 'diffDepth': case 'mergedDepth': case 'orderBookL2_200': case 'orderBookL2_25': case 'realtimes': // are these remnants from upgrade? case 'bookTicker': case 'depth': { // Public (high-frequency public data) return 'public'; } case 'insurance': case 'klineV2': case 'aggTrade': case 'markPrice': case 'kline': case 'continuousKline': case 'miniTicker': case 'ticker': case 'forceOrder': case 'compositeIndex': case 'contractInfo': case 'assetIndex': case 'ticketInfo': case 'instrument_info': { return 'market'; } case 'outboundAccountInfo': case 'order': case 'stop_order': case 'position': case 'trade': case 'wallet': case 'execution': case 'executionReport': { return 'private'; } default: { return 'market'; // const err = neverGuard( // wsTopic, // `Unhandled WsTopic "${wsTopic}" - cannot resolve connection category. Defaulting to 'market'`, // ); // console.warn(new Date(), err); } } } exports.WS_AUTH_ON_CONNECT_KEYS = []; /** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */ const PRIVATE_TOPICS = []; function isPrivateWsTopic(topic) { return PRIVATE_TOPICS.includes(topic); } function getTestnetWsKey(wsKey) { switch (wsKey) { case exports.WS_KEY_MAP.mainTestnetPublic: case exports.WS_KEY_MAP.mainTestnetUserData: case exports.WS_KEY_MAP.coinmTestnet: case exports.WS_KEY_MAP.usdmTestnet: case exports.WS_KEY_MAP.mainWSAPITestnet: case exports.WS_KEY_MAP.usdmWSAPITestnet: case exports.WS_KEY_MAP.coinmWSAPITestnet: // case WS_KEY_MAP.coinmTestnetPrivate: // case WS_KEY_MAP.coinmTestnetMarket: // case WS_KEY_MAP.coinmTestnetPublic: case exports.WS_KEY_MAP.usdmTestnetMarket: case exports.WS_KEY_MAP.usdmTestnetPublic: case exports.WS_KEY_MAP.usdmTestnetPrivate: { return wsKey; } case exports.WS_KEY_MAP.main: case exports.WS_KEY_MAP.main2: case exports.WS_KEY_MAP.main3: { return exports.WS_KEY_MAP.mainTestnetUserData; } case exports.WS_KEY_MAP.mainWSAPI: case exports.WS_KEY_MAP.mainWSAPI2: { return exports.WS_KEY_MAP.mainWSAPITestnet; } case exports.WS_KEY_MAP.usdm: { return exports.WS_KEY_MAP.usdmTestnet; } case exports.WS_KEY_MAP.usdmWSAPI: { return exports.WS_KEY_MAP.usdmWSAPITestnet; } case exports.WS_KEY_MAP.usdmMarket: return exports.WS_KEY_MAP.usdmTestnetMarket; case exports.WS_KEY_MAP.usdmPublic: return exports.WS_KEY_MAP.usdmTestnetPublic; case exports.WS_KEY_MAP.usdmPrivate: return exports.WS_KEY_MAP.usdmTestnetPrivate; case exports.WS_KEY_MAP.coinm: case exports.WS_KEY_MAP.coinm2: { return exports.WS_KEY_MAP.coinmTestnet; } case exports.WS_KEY_MAP.coinmWSAPI: { return exports.WS_KEY_MAP.coinmWSAPITestnet; } // case WS_KEY_MAP.coinmMarket: // return WS_KEY_MAP.coinmTestnetMarket; // case WS_KEY_MAP.coinmPublic: // return WS_KEY_MAP.coinmTestnetPublic; // case WS_KEY_MAP.coinmPrivate: // return WS_KEY_MAP.coinmTestnetPrivate; case exports.WS_KEY_MAP.marginRiskUserData: case exports.WS_KEY_MAP.marginUserData: case exports.WS_KEY_MAP.eoptions: case exports.WS_KEY_MAP.portfolioMarginUserData: case exports.WS_KEY_MAP.portfolioMarginProUserData: case exports.WS_KEY_MAP.alpha: { throw new Error(`Testnet not supported for "${wsKey}"`); } default: throw (0, typeGuards_1.neverGuard)(wsKey, `Unhandled wsKey "${wsKey}"`); } } function getWsUrl(wsKey, wsClientOptions, logger) { const wsUrl = wsClientOptions.wsUrl; if (wsUrl) { return wsUrl; } const isTestnet = !!wsClientOptions.testnet; const isDemoTrading = !!wsClientOptions.demoTrading; const useMMSubdomain = !!wsClientOptions.useMMSubdomain; const resolvedWsKey = isTestnet ? getTestnetWsKey(wsKey) : wsKey; // Use demo trading endpoints if requested and available if (isDemoTrading && !isTestnet) { const demoUrl = exports.WS_KEY_DEMO_URL_MAP[resolvedWsKey]; if (demoUrl) { return demoUrl; } throw new Error(`Demo trading is currently not supported for the WebSocket key "${resolvedWsKey}". If demo trading should be available here, please open an issue on GitHub.`); } // Use MM endpoints if requested and available if (useMMSubdomain && !isTestnet) { const mmUrl = exports.WS_KEY_MM_URL_MAP[resolvedWsKey]; if (mmUrl) { return mmUrl; } } return exports.WS_KEY_URL_MAP[resolvedWsKey]; } function getMaxTopicsPerSubscribeEvent(wsKey) { switch (wsKey) { // case 'v5': { // if (wsKey === WS_KEY_MAP.v5SpotPublic) { // return 10; // } // return null; // } default: { return 250; // throw neverGuard(wsKey, 'getMaxTopicsPerSubscribeEvent(): Unhandled wsKey'); } } } exports.WS_ERROR_ENUM = { NOT_AUTHENTICATED_SPOT_V3: '-1004', API_ERROR_GENERIC: '10001', API_SIGN_AUTH_FAILED: '10003', USDC_OPTION_AUTH_FAILED: '3303006', }; /** * #305: ws.terminate() is undefined in browsers. * This only works in node.js, not in browsers. * Does nothing if `ws` is undefined. Does nothing in browsers. */ function safeTerminateWs(ws, fallbackToClose) { if (!ws) { return false; } if (typeof ws['terminate'] === 'function') { ws.terminate(); return true; } else if (fallbackToClose) { ws.close(); } return false; } /** * WS API promises are stored using a primary key. This key is constructed using * properties found in every request & reply. * * The counterpart to this is in resolveEmittableEvents */ function getPromiseRefForWSAPIRequest(wsKey, requestEvent) { const promiseRef = [wsKey, requestEvent.id].join('_'); return promiseRef; } /** * Users can conveniently pass topics as strings or objects (object has topic name + optional params). * * This method normalises topics into objects (object has topic name + optional params). */ function getNormalisedTopicRequests(wsTopicRequests) { const normalisedTopicRequests = []; for (const wsTopicRequest of wsTopicRequests) { // passed as string, convert to object if (typeof wsTopicRequest === 'string') { const topicRequest = { topic: wsTopicRequest, payload: undefined, }; normalisedTopicRequests.push(topicRequest); continue; } // already a normalised object, thanks to user normalisedTopicRequests.push(wsTopicRequest); } return normalisedTopicRequests; } /** * These topics resolve to the public (high-frequency public data) WS endpoint for USDM futures: * https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Important-WebSocket-Change-Notice#public-high-frequency-public-data * * All other public topics revert to the market endpoint. */ const USDM_PUBLIC_MARKET_DATA_STREAMS = ['bookTicker', 'depth']; /** * USD-M Futures retired the legacy combined market data endpoint on 2026-04-23. * Keep the legacy `usdm` wsKey convenient by routing raw market data streams to * Binance's split public/market stream keys. */ function getUsdmMarketDataWsKeyForTopic(wsKey, topic) { if (wsKey !== exports.WS_KEY_MAP.usdm && wsKey !== exports.WS_KEY_MAP.usdmTestnet) { return wsKey; } const streamName = extractStreamNameFromWsTopic(topic); const isPublicStream = USDM_PUBLIC_MARKET_DATA_STREAMS.includes(streamName); if (wsKey === exports.WS_KEY_MAP.usdmTestnet) { return isPublicStream ? exports.WS_KEY_MAP.usdmTestnetPublic : exports.WS_KEY_MAP.usdmTestnetMarket; } return isPublicStream ? exports.WS_KEY_MAP.usdmPublic : exports.WS_KEY_MAP.usdmMarket; } function extractStreamNameFromWsTopic(topic) { if (topic.startsWith('!')) { return topic.slice(1).split('@')[0].split('_')[0]; } const firstStreamSegment = topic.includes('@') ? topic.slice(topic.indexOf('@') + 1).split('@')[0] : topic; if (firstStreamSegment.startsWith('depth')) { return 'depth'; } return firstStreamSegment.split('_')[0]; } /** * Groups topics in request into per-wsKey groups * @param normalisedTopicRequests * @param wsKey * @param isPrivateTopic * @returns */ function getTopicsPerWSKey(normalisedTopicRequests, wsKey) { const perWsKeyTopics = {}; // Sort into per wsKey arrays, in case topics are mixed together for different wsKeys for (const topicRequest of normalisedTopicRequests) { const derivedWsKey = getUsdmMarketDataWsKeyForTopic(wsKey, topicRequest.topic); if (!perWsKeyTopics[derivedWsKey] || !Array.isArray(perWsKeyTopics[derivedWsKey])) { perWsKeyTopics[derivedWsKey] = []; } perWsKeyTopics[derivedWsKey].push(topicRequest); } return perWsKeyTopics; } /** * Some of the newer multiplex websocket topics don't include an eventType ("e") property. * * This attempts to extract that from the streamName, which is included with these incoming events. */ function parseEventTypeFromMessage(wsKey, parsedMsg) { // console.log(`parseEventTypeFromMessage(${wsKey})`, parsedMsg); if (parsedMsg === null || parsedMsg === void 0 ? void 0 : parsedMsg.e) { return parsedMsg.e; } const streamName = (parsedMsg === null || parsedMsg === void 0 ? void 0 : parsedMsg.stream) || (parsedMsg === null || parsedMsg === void 0 ? void 0 : parsedMsg.streamName); if (streamName && typeof streamName === 'string') { // console.log(`parseEventTypeFromMessage(${wsKey}) `, streamName, parsedMsg); const eventType = streamName.split('@'); // All symbol streams can be returned as is, just need to extract the left-most text before any @ or _ if (streamName.startsWith('!')) { const subEventType = eventType[0].split('_'); return subEventType[0].replace('!', ''); } // Per symbol streams can have the symbol trimmed off (string before first "@") // E.g. btcusdt@kline_5m@+08:00 -> kline_5m@+08:00 if (eventType.length) { // remove first, keep the rest rejoined eventType.shift(); // Edge case, for european options, the suffix is a variable date so will never match the map if (wsKey === 'eoptions') { return eventType[0]; } return eventType.join('@'); } console.error('parseEventTypeFromMessage(): Cannot extract event type from message: ', parsedMsg); } if (parsedMsg === null || parsedMsg === void 0 ? void 0 : parsedMsg.data) { return parseEventTypeFromMessage(wsKey, parsedMsg.data); } if (Array.isArray(parsedMsg) && parsedMsg.length) { return parseEventTypeFromMessage(wsKey, parsedMsg[0]); } return; } function resolveUserDataMarketForWsKey(wsKey) { switch (wsKey) { case 'main': case 'main2': case 'main3': case 'mainWSAPI': case 'mainWSAPI2': case 'marginRiskUserData': return 'spot'; case 'mainTestnetPublic': case 'mainTestnetUserData': case 'mainWSAPITestnet': return 'spotTestnet'; case 'usdm': case 'usdmMarket': case 'usdmPrivate': case 'usdmPublic': case 'usdmWSAPI': return 'usdm'; case 'usdmTestnet': case 'usdmTestnetMarket': case 'usdmTestnetPrivate': case 'usdmTestnetPublic': case 'usdmWSAPITestnet': return 'usdmTestnet'; case 'coinm': case 'coinm2': // case 'coinmMarket': // case 'coinmPrivate': // case 'coinmPublic': case 'coinmWSAPI': return 'coinm'; case 'coinmTestnet': // case 'coinmTestnetMarket': // case 'coinmTestnetPrivate': // case 'coinmTestnetPublic': case 'coinmWSAPITestnet': return 'coinmTestnet'; case 'eoptions': return 'options'; case 'portfolioMarginUserData': case 'portfolioMarginProUserData': return 'portfoliom'; case 'marginUserData': return 'crossMargin'; case 'alpha': return 'alpha'; default: { throw (0, typeGuards_1.neverGuard)(wsKey, `resolveMarketForWsKey(): Unhandled WsKey "${wsKey}"`); } } } /** * Used by the legacy subscribe* utility methods to determine which wsKey to route the subscription to. */ function getWsKeyForProductGroup(market, topic) { const wsConnectionCategory = getWSConnectionCategoryForTopic(topic); switch (market) { case 'spot': { return 'main'; } case 'coinm': { return 'coinm'; // switch (wsConnectionCategory) { // case 'market': // return 'coinmMarket'; // case 'private': // return 'coinmPrivate'; // case 'public': // return 'coinmPublic'; // default: { // throw neverGuard( // wsConnectionCategory, // `getWsKeyForProductGroup(${market}, ${topic}): Unhandled wsConnectionCategory "${wsConnectionCategory}"`, // ); // } // } } case 'usdm': { switch (wsConnectionCategory) { case 'market': return 'usdmMarket'; case 'private': return 'usdmPrivate'; case 'public': return 'usdmPublic'; default: { throw (0, typeGuards_1.neverGuard)(wsConnectionCategory, `getWsKeyForProductGroup(${market}, ${topic}): Unhandled wsConnectionCategory "${wsConnectionCategory}"`); } } } } } function parseRawWsMessageLegacy(event, options) { if (typeof event === 'string') { const parsedEvent = typeof options.customParseJSONFn === 'function' ? options.customParseJSONFn(event) : JSON.parse(event); // WS events are wrapped into "data" if (parsedEvent.data) { if (typeof parsedEvent.data === 'string') { return parseRawWsMessageLegacy(parsedEvent.data, options); } return parsedEvent.data; } // WS API wraps responses in "event" if (parsedEvent.event) { const { event } = parsedEvent, other = __rest(parsedEvent, ["event"]); return Object.assign(Object.assign({}, other), event); } return parsedEvent; } if (event === null || event === void 0 ? void 0 : event.data) { return parseRawWsMessageLegacy(event.data, options); } return event; } /** * One simple purpose - extract JSON event from raw WS Message. * * Any mapping or additonal handling should not be done here. */ function parseRawWsMessage(event, options) { // WS MessageLike->data (contains JSON as a string) if (event === null || event === void 0 ? void 0 : event.data) { return parseRawWsMessage(event.data, options); } if (typeof event === 'string') { // For: // - multiplex subscriptions, as of v3 // - user data, dedicated listen key connection // - user data, via ws api (Without listen key) // - ws api responses if (typeof options.customParseJSONFn === 'function') { return options.customParseJSONFn(event); } const parsedEvent = JSON.parse(event); return parsedEvent; } return event; } /** * @deprecated Only works for legacy WS client, where one connection exists per key */ function getContextFromWsKey(legacyWsKey) { const [market, streamName, symbol, listenKey, wsKey, ...otherParams] = legacyWsKey.split('_'); return { symbol: symbol === 'undefined' ? undefined : symbol, legacyWsKey, wsKey, market: market, isTestnet: market.includes('estnet'), isUserData: legacyWsKey.includes('userData'), streamName, listenKey: listenKey === 'undefined' ? undefined : listenKey, otherParams, }; } /** * The legacy WS client creates a deterministic WS Key based on consistent input parameters */ function getLegacyWsStoreKeyWithContext(market, streamName, symbol = undefined, listenKey = undefined, ...otherParams) { return [market, streamName, symbol, listenKey, ...otherParams].join('_'); } function getLegacyWsKeyContext(wsKey) { if (wsKey.indexOf('userData') !== -1) { return getContextFromWsKey(wsKey); } return undefined; } function getRealWsKeyFromDerivedWsKey(wsKey) { if (!wsKey.includes('userData')) { return wsKey; } const legacyWsKeyContext = getLegacyWsKeyContext(wsKey); if (!legacyWsKeyContext || !legacyWsKeyContext.wsKey) { throw new Error(`getRealWsKeyFromDerivedWsKey(): no context found in supplied wsKey: "${wsKey}" | "${legacyWsKeyContext}"`); } return legacyWsKeyContext.wsKey; } function appendEventMarket(wsMsg, wsKey) { const { market } = getContextFromWsKey(wsKey); wsMsg.wsMarket = market; wsMsg.wsKey = wsKey; } /** * WebSocket.ping() is not available in browsers. This is a simple check used to * disable heartbeats in browers, for exchanges that use native WebSocket ping/pong frames. */ function isWSPingFrameAvailable() { return typeof isomorphic_ws_1.default.prototype['ping'] === 'function'; } /** * WebSocket.pong() is not available in browsers. This is a simple check used to * disable heartbeats in browers, for exchanges that use native WebSocket ping/pong frames. */ function isWSPongFrameAvailable() { return typeof isomorphic_ws_1.default.prototype['pong'] === 'function'; } //# sourceMappingURL=websocket-util.js.map