UNPKG

okx-api

Version:

Complete Node.js SDK for OKX's REST APIs and WebSockets, with TypeScript & end-to-end tests

541 lines 19.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WS_EVENT_CODE_ENUM = exports.PUBLIC_CHANNELS_WITH_AUTH = exports.PRIVATE_CHANNELS = exports.PUBLIC_WS_KEYS = exports.MIXED_WS_KEYS = exports.PRIVATE_WS_KEYS = exports.WS_KEY_MAP = exports.WS_BASE_URL_MAP = exports.WS_LOGGER_CATEGORY = void 0; exports.getDemoWsKey = getDemoWsKey; exports.getWsKeyForTopicChannel = getWsKeyForTopicChannel; exports.getWsKeyForMarket = getWsKeyForMarket; exports.requiresWSAPITag = requiresWSAPITag; exports.validateWSAPITag = validateWSAPITag; exports.getWsUrlForWsKey = getWsUrlForWsKey; exports.getMaxTopicsPerSubscribeEventForMarket = getMaxTopicsPerSubscribeEventForMarket; exports.isWsPong = isWsPong; exports.safeTerminateWs = safeTerminateWs; exports.getNormalisedTopicRequests = getNormalisedTopicRequests; exports.isWSPingFrameAvailable = isWSPingFrameAvailable; exports.isWSPongFrameAvailable = isWSPongFrameAvailable; exports.getPromiseRefForWSAPIRequest = getPromiseRefForWSAPIRequest; const ws_api_js_1 = require("../types/websockets/ws-api.js"); const requestUtils_js_1 = require("./requestUtils.js"); const typeGuards_js_1 = require("./typeGuards.js"); exports.WS_LOGGER_CATEGORY = { category: 'okx-ws' }; exports.WS_BASE_URL_MAP = { prod: { live: { public: 'wss://ws.okx.com:8443/ws/v5/public', private: 'wss://ws.okx.com:8443/ws/v5/private', // Some channels require business suffix: https://www.okx.com/help/changes-to-v5-api-websocket-subscription-parameter-and-url business: 'wss://ws.okx.com:8443/ws/v5/business', }, demo: { public: 'wss://wspap.okx.com:8443/ws/v5/public', private: 'wss://wspap.okx.com:8443/ws/v5/private', business: 'wss://wspap.okx.com:8443/ws/v5/business?brokerId=9999', }, }, // Exactly the same as "prod" // also known as "www.okx.com", the default: https://www.okx.com/docs-v5/en/#overview-production-trading-services GLOBAL: { live: { public: 'wss://ws.okx.com:8443/ws/v5/public', private: 'wss://ws.okx.com:8443/ws/v5/private', // Some channels require business suffix: https://www.okx.com/help/changes-to-v5-api-websocket-subscription-parameter-and-url business: 'wss://ws.okx.com:8443/ws/v5/business', }, demo: { public: 'wss://wspap.okx.com:8443/ws/v5/public', private: 'wss://wspap.okx.com:8443/ws/v5/private', business: 'wss://wspap.okx.com:8443/ws/v5/business?brokerId=9999', }, }, // also known as "my.okx.com" https://my.okx.com/docs-v5/en/#overview-production-trading-services EEA: { live: { public: 'wss://wseea.okx.com:8443/ws/v5/public', private: 'wss://wseea.okx.com:8443/ws/v5/private', // Some channels require business suffix: https://www.okx.com/help/changes-to-v5-api-websocket-subscription-parameter-and-url business: 'wss://wseea.okx.com:8443/ws/v5/business', }, demo: { public: 'wss://wseeapap.okx.com:8443/ws/v5/public', private: 'wss://wseeapap.okx.com:8443/ws/v5/private', business: 'wss://wseeapap.okx.com:8443/ws/v5/business?brokerId=9999', }, }, // also known as "app.okx.com" https://app.okx.com/docs-v5/en/#overview-production-trading-services US: { live: { public: 'wss://wsus.okx.com:8443/ws/v5/public', private: 'wss://wsus.okx.com:8443/ws/v5/private', // Some channels require business suffix: https://www.okx.com/help/changes-to-v5-api-websocket-subscription-parameter-and-url business: 'wss://wsus.okx.com:8443/ws/v5/business', }, demo: { public: 'wss://wsuspap.okx.com:8443/ws/v5/public', private: 'wss://wsuspap.okx.com:8443/ws/v5/private', business: 'wss://wsuspap.okx.com:8443/ws/v5/business?brokerId=9999', }, }, }; exports.WS_KEY_MAP = { // OKX Global: https://www.okx.com/docs-v5/en/#overview-production-trading-services /** * Public WS connection for OKX Global (www.okx.com), does not require auth. */ prodPublic: 'prodPublic', /** * Private WS connection for OKX Global (www.okx.com), requires auth. */ prodPrivate: 'prodPrivate', /** * Business WS connection for OKX Global (www.okx.com), sometimes requires auth. */ prodBusiness: 'prodBusiness', /** * Public DEMO WS connection for OKX Global (www.okx.com), does not require auth. */ prodDemoPublic: 'prodDemoPublic', /** * Private DEMO WS connection for OKX Global (www.okx.com), requires auth. */ prodDemoPrivate: 'prodDemoPrivate', /** * Business DEMO WS connection for OKX Global (www.okx.com), sometimes requires auth. */ prodDemoBusiness: 'prodDemoBusiness', // Also known as "my.okx.com" https://my.okx.com/docs-v5/en/#overview-production-trading-services /** * Public WS connection for OKX EEA (my.okx.com), does not require auth. */ eeaLivePublic: 'eeaLivePublic', /** * Private WS connection for OKX EEA (my.okx.com), requires auth. */ eeaLivePrivate: 'eeaLivePrivate', /** * Business WS connection for OKX EEA (my.okx.com), sometimes requires auth. */ eeaLiveBusiness: 'eeaLiveBusiness', /** * Public DEMO WS connection for OKX EEA (my.okx.com), does not require auth. */ eeaDemoPublic: 'eeaDemoPublic', /** * Private DEMO WS connection for OKX EEA (my.okx.com), requires auth. */ eeaDemoPrivate: 'eeaDemoPrivate', /** * Business DEMO WS connection for OKX EEA (my.okx.com), sometimes requires auth. */ eeaDemoBusiness: 'eeaDemoBusiness', // Also known as "app.okx.com" https://app.okx.com/docs-v5/en/#overview-production-trading-services /** * Public WS connection for OKX US (app.okx.com), does not require auth. */ usLivePublic: 'usLivePublic', /** * Private WS connection for OKX US (app.okx.com), requires auth. */ usLivePrivate: 'usLivePrivate', /** * Business WS connection for OKX US (app.okx.com), sometimes requires auth. */ usLiveBusiness: 'usLiveBusiness', usDemoPublic: 'usDemoPublic', usDemoPrivate: 'usDemoPrivate', usDemoBusiness: 'usDemoBusiness', }; exports.PRIVATE_WS_KEYS = [ exports.WS_KEY_MAP.prodPrivate, exports.WS_KEY_MAP.prodDemoPrivate, exports.WS_KEY_MAP.eeaLivePrivate, exports.WS_KEY_MAP.eeaDemoPrivate, exports.WS_KEY_MAP.usLivePrivate, exports.WS_KEY_MAP.usDemoPrivate, ]; // These sometimes need auth, depending on the topic exports.MIXED_WS_KEYS = [ exports.WS_KEY_MAP.prodBusiness, exports.WS_KEY_MAP.prodDemoBusiness, exports.WS_KEY_MAP.eeaLiveBusiness, exports.WS_KEY_MAP.eeaDemoBusiness, exports.WS_KEY_MAP.usLiveBusiness, exports.WS_KEY_MAP.usDemoBusiness, ]; exports.PUBLIC_WS_KEYS = [ exports.WS_KEY_MAP.prodPublic, exports.WS_KEY_MAP.prodDemoPublic, exports.WS_KEY_MAP.eeaLivePublic, exports.WS_KEY_MAP.eeaDemoPublic, exports.WS_KEY_MAP.usLivePublic, exports.WS_KEY_MAP.usDemoPublic, ]; /** * Returns the DEMO connection WsKey for the provided WsKey */ function getDemoWsKey(wsKey) { switch (wsKey) { case exports.WS_KEY_MAP.prodDemoPublic: case exports.WS_KEY_MAP.prodDemoPrivate: case exports.WS_KEY_MAP.prodDemoBusiness: case exports.WS_KEY_MAP.eeaDemoPublic: case exports.WS_KEY_MAP.eeaDemoPrivate: case exports.WS_KEY_MAP.eeaDemoBusiness: case exports.WS_KEY_MAP.usDemoPublic: case exports.WS_KEY_MAP.usDemoPrivate: case exports.WS_KEY_MAP.usDemoBusiness: { return wsKey; } case exports.WS_KEY_MAP.prodPublic: return exports.WS_KEY_MAP.prodDemoPublic; case exports.WS_KEY_MAP.prodPrivate: return exports.WS_KEY_MAP.prodDemoPrivate; case exports.WS_KEY_MAP.prodBusiness: return exports.WS_KEY_MAP.prodDemoBusiness; case exports.WS_KEY_MAP.eeaLivePublic: return exports.WS_KEY_MAP.eeaDemoPublic; case exports.WS_KEY_MAP.eeaLivePrivate: return exports.WS_KEY_MAP.eeaDemoPrivate; case exports.WS_KEY_MAP.eeaLiveBusiness: return exports.WS_KEY_MAP.eeaDemoBusiness; case exports.WS_KEY_MAP.usLivePublic: return exports.WS_KEY_MAP.usDemoPublic; case exports.WS_KEY_MAP.usLivePrivate: return exports.WS_KEY_MAP.usDemoPrivate; case exports.WS_KEY_MAP.usLiveBusiness: return exports.WS_KEY_MAP.usDemoBusiness; default: throw (0, typeGuards_js_1.neverGuard)(wsKey, `Unhandled wsKey "${wsKey}"`); } } /** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */ exports.PRIVATE_CHANNELS = [ 'account', 'positions', 'balance_and_position', 'orders', 'orders-algo', 'algo-advance', 'liquidation-warning', 'account-greeks', 'grid-orders-spot', 'grid-orders-contract', 'grid-orders-moon', 'grid-positions', 'grid-sub-orders', ]; exports.PUBLIC_CHANNELS_WITH_AUTH = [ // While these are market data topics on the PUBLIC channel, they do require the public connection to be authenticated to subscribe to these. See #140. 'books-l2-tbt', 'books50-l2-tpt', ]; /** * The following channels only support the new business wss endpoint: * https://www.okx.com/help-center/changes-to-v5-api-websocket-subscription-parameter-and-url */ const BUSINESS_CHANNELS = [ 'orders-algo', 'algo-advance', 'deposit-info', 'withdrawal-info', 'grid-orders-spot', 'grid-orders-contract', 'grid-orders-moon', 'grid-positions', 'grid-sub-orders', 'algo-recurring-buy', 'candle1Y', 'candle6M', 'candle3M', 'candle1M', 'candle1W', 'candle1D', 'candle2D', 'candle3D', 'candle5D', 'candle12H', 'candle6H', 'candle4H', 'candle2H', 'candle1H', 'candle30m', 'candle15m', 'candle5m', 'candle3m', 'candle1m', 'candle1s', 'candle1Yutc', 'candle3Mutc', 'candle1Mutc', 'candle1Wutc', 'candle1Dutc', 'candle2Dutc', 'candle3Dutc', 'candle5Dutc', 'candle12Hutc', 'candle6Hutc', 'mark-price-candle1Y', 'mark-price-candle6M', 'mark-price-candle3M', 'mark-price-candle1M', 'mark-price-candle1W', 'mark-price-candle1D', 'mark-price-candle2D', 'mark-price-candle3D', 'mark-price-candle5D', 'mark-price-candle12H', 'mark-price-candle6H', 'mark-price-candle4H', 'mark-price-candle2H', 'mark-price-candle1H', 'mark-price-candle30m', 'mark-price-candle15m', 'mark-price-candle5m', 'mark-price-candle3m', 'mark-price-candle1m', 'mark-price-candle1Yutc', 'mark-price-candle3Mutc', 'mark-price-candle1Mutc', 'mark-price-candle1Wutc', 'mark-price-candle1Dutc', 'mark-price-candle2Dutc', 'mark-price-candle3Dutc', 'mark-price-candle5Dutc', 'mark-price-candle12Hutc', 'mark-price-candle6Hutc', 'index-candle1Y', 'index-candle6M', 'index-candle3M', 'index-candle1M', 'index-candle1W', 'index-candle1D', 'index-candle2D', 'index-candle3D', 'index-candle5D', 'index-candle12H', 'index-candle6H', 'index-candle4H index -candle2H', 'index-candle1H', 'index-candle30m', 'index-candle15m', 'index-candle5m', 'index-candle3m', 'index-candle1m', 'index-candle1Yutc', 'index-candle3Mutc', 'index-candle1Mutc', 'index-candle1Wutc', 'index-candle1Dutc', 'index-candle2Dutc', 'index-candle3Dutc', 'index-candle5Dutc', 'index-candle12Hutc', 'index-candle6Hutc', ]; /** Determine which WsKey (ws connection) to route an event to */ function getWsKeyForTopicChannel(market, channel, isPrivate) { const isPrivateTopic = isPrivate === true || exports.PRIVATE_CHANNELS.includes(channel); const isBusinessChannel = BUSINESS_CHANNELS.includes(channel); return getWsKeyForMarket(market, isPrivateTopic, isBusinessChannel); } /** * Returns wsKey for product group. Demo resolution is handled in URL lookup function, separately. */ function getWsKeyForMarket(market, isPrivate, isBusinessChannel) { switch (market) { case 'prod': case 'GLOBAL': { if (isBusinessChannel) { return exports.WS_KEY_MAP.prodBusiness; } return isPrivate ? exports.WS_KEY_MAP.prodPrivate : exports.WS_KEY_MAP.prodPublic; } case 'EEA': { if (isBusinessChannel) { return exports.WS_KEY_MAP.eeaLiveBusiness; } return isPrivate ? exports.WS_KEY_MAP.eeaLivePrivate : exports.WS_KEY_MAP.eeaLivePublic; } case 'US': { if (isBusinessChannel) { return exports.WS_KEY_MAP.usLiveBusiness; } return isPrivate ? exports.WS_KEY_MAP.usLivePrivate : exports.WS_KEY_MAP.usLivePublic; } default: { throw (0, typeGuards_js_1.neverGuard)(market, `getWsKeyForTopic(): Unhandled market "${market}"`); } } } function requiresWSAPITag(operation, wsKey) { switch (wsKey) { case exports.WS_KEY_MAP.prodPublic: case exports.WS_KEY_MAP.prodDemoPublic: case exports.WS_KEY_MAP.eeaLivePublic: case exports.WS_KEY_MAP.eeaDemoPublic: case exports.WS_KEY_MAP.usLivePublic: case exports.WS_KEY_MAP.usDemoPublic: return false; case exports.WS_KEY_MAP.prodDemoPrivate: case exports.WS_KEY_MAP.prodDemoBusiness: case exports.WS_KEY_MAP.eeaDemoPrivate: case exports.WS_KEY_MAP.eeaDemoBusiness: case exports.WS_KEY_MAP.usDemoPrivate: case exports.WS_KEY_MAP.usDemoBusiness: case exports.WS_KEY_MAP.prodPrivate: case exports.WS_KEY_MAP.prodBusiness: case exports.WS_KEY_MAP.eeaLivePrivate: case exports.WS_KEY_MAP.eeaLiveBusiness: case exports.WS_KEY_MAP.usLivePrivate: case exports.WS_KEY_MAP.usLiveBusiness: return ws_api_js_1.WS_API_TAG_OPERATIONS.includes(operation); default: { throw (0, typeGuards_js_1.neverGuard)(wsKey, `Unhandled WsKey "${wsKey}"`); } } } function validateWSAPITag(request, wsKey) { if (!requiresWSAPITag(request.op, wsKey)) { return; } for (let i = 0; i < request.args.length; i++) { request.args[i]['tag'] = requestUtils_js_1.programId; } } /** * Maps a WS key back to a WS URL. Resolves to demo wsKey automatically, if configured. */ function getWsUrlForWsKey(wsKey, wsClientOptions, logger) { if (wsClientOptions.wsUrl) { return wsClientOptions.wsUrl; } const isDemoTrading = !!wsClientOptions?.demoTrading; const LIVE_OR_DEMO = isDemoTrading ? 'demo' : 'live'; switch (wsKey) { // Global (www.okx.com) case 'prodPublic': case 'prodDemoPublic': return exports.WS_BASE_URL_MAP.GLOBAL[LIVE_OR_DEMO].public; case 'prodPrivate': case 'prodDemoPrivate': return exports.WS_BASE_URL_MAP.GLOBAL[LIVE_OR_DEMO].private; case 'prodBusiness': case 'prodDemoBusiness': return exports.WS_BASE_URL_MAP.GLOBAL[LIVE_OR_DEMO].business; // EEA (my.okx.com) case 'eeaLivePublic': case 'eeaDemoPublic': return exports.WS_BASE_URL_MAP.EEA[LIVE_OR_DEMO].public; case 'eeaLivePrivate': case 'eeaDemoPrivate': return exports.WS_BASE_URL_MAP.EEA[LIVE_OR_DEMO].private; case 'eeaLiveBusiness': case 'eeaDemoBusiness': return exports.WS_BASE_URL_MAP.EEA[LIVE_OR_DEMO].business; // US (app.okx.com) case 'usLivePublic': case 'usDemoPublic': return exports.WS_BASE_URL_MAP.US[LIVE_OR_DEMO].public; case 'usLivePrivate': case 'usDemoPrivate': return exports.WS_BASE_URL_MAP.US[LIVE_OR_DEMO].private; case 'usLiveBusiness': case 'usDemoBusiness': return exports.WS_BASE_URL_MAP.US[LIVE_OR_DEMO].business; default: { const errorMessage = 'getWsUrl(): Unhandled wsKey: '; logger.error(errorMessage, { category: 'okx-ws', wsKey, }); throw (0, typeGuards_js_1.neverGuard)(wsKey, errorMessage); } } } function getMaxTopicsPerSubscribeEventForMarket(market) { switch (market) { case 'prod': case 'EEA': case 'GLOBAL': case 'US': { return null; } default: { throw (0, typeGuards_js_1.neverGuard)(market, 'getWsKeyForTopic(): Unhandled market'); } } } function isWsPong(event) { return (typeof event === 'object' && !!event && 'data' in event && typeof event['data'] === 'string' && event['data'] === 'pong'); } exports.WS_EVENT_CODE_ENUM = { OK: '0', LOGIN_FAILED: '60009', LOGIN_PARTIALLY_FAILED: '60022', }; /** * #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; } /** * 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; } /** * 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 WebSocket.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 WebSocket.prototype['pong'] === 'function'; } /** * WS API promises are stored using a primary key. This key is constructed using * properties found in every request & reply. */ function getPromiseRefForWSAPIRequest(requestEvent) { const promiseRef = [requestEvent.id, requestEvent.op].join('_'); return promiseRef; } //# sourceMappingURL=websocket-util.js.map