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
JavaScript
"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