v3.5.1: feat() add support for V5 public & private websockets

This commit is contained in:
tiagosiebler
2023-02-24 15:59:05 +00:00
parent fad12f460f
commit 0a1cc4ed2b
6 changed files with 358 additions and 113 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "bybit-api", "name": "bybit-api",
"version": "3.5.0-beta.0", "version": "3.5.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bybit-api", "name": "bybit-api",
"version": "3.5.0-beta.0", "version": "3.5.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^0.21.0", "axios": "^0.21.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "bybit-api", "name": "bybit-api",
"version": "3.5.0", "version": "3.5.1",
"description": "Complete & robust Node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & strong end to end tests.", "description": "Complete & robust Node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & strong end to end tests.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@@ -1,6 +1,7 @@
import { ContractClient } from '../contract-client'; import { ContractClient } from '../contract-client';
import { InverseClient } from '../inverse-client'; import { InverseClient } from '../inverse-client';
import { LinearClient } from '../linear-client'; import { LinearClient } from '../linear-client';
import { RestClientV5 } from '../rest-client-v5';
import { SpotClient } from '../spot-client'; import { SpotClient } from '../spot-client';
import { SpotClientV3 } from '../spot-client-v3'; import { SpotClientV3 } from '../spot-client-v3';
import { UnifiedMarginClient } from '../unified-margin-client'; import { UnifiedMarginClient } from '../unified-margin-client';
@@ -15,7 +16,8 @@ export type RESTClient =
| USDCOptionClient | USDCOptionClient
| USDCPerpetualClient | USDCPerpetualClient
| UnifiedMarginClient | UnifiedMarginClient
| ContractClient; | ContractClient
| RestClientV5;
export type numberInString = string; export type numberInString = string;

View File

@@ -11,7 +11,8 @@ export type APIMarket =
| 'unifiedPerp' | 'unifiedPerp'
| 'unifiedOption' | 'unifiedOption'
| 'contractUSDT' | 'contractUSDT'
| 'contractInverse'; | 'contractInverse'
| 'v5';
// Same as inverse futures // Same as inverse futures
export type WsPublicInverseTopic = export type WsPublicInverseTopic =

View File

@@ -1,4 +1,4 @@
import { APIMarket, WsKey } from '../types'; import { APIMarket, CategoryV5, WsKey } from '../types';
interface NetworkMapV3 { interface NetworkMapV3 {
livenet: string; livenet: string;
@@ -9,10 +9,28 @@ interface NetworkMapV3 {
type PublicPrivateNetwork = 'public' | 'private'; type PublicPrivateNetwork = 'public' | 'private';
/**
* The following WS keys are logical.
*
* They're not directly used as a market. They usually have one private endpoint but many public ones,
* so they need a bit of extra handling for seamless messaging between endpoints.
*
* For the unified keys, the "split" happens using the symbol. Symbols suffixed with USDT are obviously USDT topics.
* For the v5 endpoints, the subscribe/unsubscribe call must specify the category the subscription should route to.
*/
type PublicOnlyWsKeys =
| 'unifiedPerpUSDT'
| 'unifiedPerpUSDC'
| 'v5SpotPublic'
| 'v5LinearPublic'
| 'v5InversePublic'
| 'v5OptionPublic';
export const WS_BASE_URL_MAP: Record< export const WS_BASE_URL_MAP: Record<
APIMarket | 'unifiedPerpUSDT' | 'unifiedPerpUSDC', APIMarket,
Record<PublicPrivateNetwork, NetworkMapV3> Record<PublicPrivateNetwork, NetworkMapV3>
> = { > &
Record<PublicOnlyWsKeys, Record<'public', NetworkMapV3>> = {
inverse: { inverse: {
public: { public: {
livenet: 'wss://stream.bybit.com/realtime', livenet: 'wss://stream.bybit.com/realtime',
@@ -106,20 +124,12 @@ export const WS_BASE_URL_MAP: Record<
livenet: 'wss://stream.bybit.com/contract/usdt/public/v3', livenet: 'wss://stream.bybit.com/contract/usdt/public/v3',
testnet: 'wss://stream-testnet.bybit.com/contract/usdt/public/v3', testnet: 'wss://stream-testnet.bybit.com/contract/usdt/public/v3',
}, },
private: {
livenet: 'useUnifiedEndpoint',
testnet: 'useUnifiedEndpoint',
},
}, },
unifiedPerpUSDC: { unifiedPerpUSDC: {
public: { public: {
livenet: 'wss://stream.bybit.com/contract/usdc/public/v3', livenet: 'wss://stream.bybit.com/contract/usdc/public/v3',
testnet: 'wss://stream-testnet.bybit.com/contract/usdc/public/v3', testnet: 'wss://stream-testnet.bybit.com/contract/usdc/public/v3',
}, },
private: {
livenet: 'useUnifiedEndpoint',
testnet: 'useUnifiedEndpoint',
},
}, },
contractUSDT: { contractUSDT: {
public: { public: {
@@ -141,6 +151,40 @@ export const WS_BASE_URL_MAP: Record<
testnet: 'wss://stream-testnet.bybit.com/contract/private/v3', testnet: 'wss://stream-testnet.bybit.com/contract/private/v3',
}, },
}, },
v5: {
public: {
livenet: 'public topics are routed internally via the public wskeys',
testnet: 'public topics are routed internally via the public wskeys',
},
private: {
livenet: 'wss://stream.bybit.com/v5/private',
testnet: 'wss://stream-testnet.bybit.com/v5/private',
},
},
v5SpotPublic: {
public: {
livenet: 'wss://stream.bybit.com/v5/public/spot',
testnet: 'wss://stream-testnet.bybit.com/v5/public/spot',
},
},
v5LinearPublic: {
public: {
livenet: 'wss://stream.bybit.com/v5/public/linear',
testnet: 'wss://stream-testnet.bybit.com/v5/public/linear',
},
},
v5InversePublic: {
public: {
livenet: 'wss://stream.bybit.com/v5/public/inverse',
testnet: 'wss://stream-testnet.bybit.com/v5/public/inverse',
},
},
v5OptionPublic: {
public: {
livenet: 'wss://stream.bybit.com/v5/public/option',
testnet: 'wss://stream-testnet.bybit.com/v5/public/option',
},
},
}; };
export const WS_KEY_MAP = { export const WS_KEY_MAP = {
@@ -163,6 +207,11 @@ export const WS_KEY_MAP = {
contractUSDTPrivate: 'contractUSDTPrivate', contractUSDTPrivate: 'contractUSDTPrivate',
contractInversePublic: 'contractInversePublic', contractInversePublic: 'contractInversePublic',
contractInversePrivate: 'contractInversePrivate', contractInversePrivate: 'contractInversePrivate',
v5SpotPublic: 'v5SpotPublic',
v5LinearPublic: 'v5LinearPublic',
v5InversePublic: 'v5InversePublic',
v5OptionPublic: 'v5OptionPublic',
v5Private: 'v5Private',
} as const; } as const;
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [ export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
@@ -172,6 +221,7 @@ export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
WS_KEY_MAP.unifiedPrivate, WS_KEY_MAP.unifiedPrivate,
WS_KEY_MAP.contractUSDTPrivate, WS_KEY_MAP.contractUSDTPrivate,
WS_KEY_MAP.contractInversePrivate, WS_KEY_MAP.contractInversePrivate,
WS_KEY_MAP.v5Private,
]; ];
export const PUBLIC_WS_KEYS = [ export const PUBLIC_WS_KEYS = [
@@ -185,15 +235,15 @@ export const PUBLIC_WS_KEYS = [
WS_KEY_MAP.unifiedPerpUSDCPublic, WS_KEY_MAP.unifiedPerpUSDCPublic,
WS_KEY_MAP.contractUSDTPublic, WS_KEY_MAP.contractUSDTPublic,
WS_KEY_MAP.contractInversePublic, WS_KEY_MAP.contractInversePublic,
WS_KEY_MAP.v5SpotPublic,
WS_KEY_MAP.v5LinearPublic,
WS_KEY_MAP.v5InversePublic,
WS_KEY_MAP.v5OptionPublic,
] as string[]; ] as string[];
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */ /** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */
const PRIVATE_TOPICS = [ const PRIVATE_TOPICS = [
'position',
'execution',
'order',
'stop_order', 'stop_order',
'wallet',
'outboundAccountInfo', 'outboundAccountInfo',
'executionReport', 'executionReport',
'ticketInfo', 'ticketInfo',
@@ -226,12 +276,23 @@ const PRIVATE_TOPICS = [
'user.execution.contractAccount', 'user.execution.contractAccount',
'user.order.contractAccount', 'user.order.contractAccount',
'user.wallet.contractAccount', 'user.wallet.contractAccount',
// v5
'position',
'execution',
'order',
'wallet',
'greeks',
]; ];
export function isPrivateWsTopic(topic: string): boolean {
return PRIVATE_TOPICS.includes(topic);
}
export function getWsKeyForTopic( export function getWsKeyForTopic(
market: APIMarket, market: APIMarket,
topic: string, topic: string,
isPrivate?: boolean isPrivate?: boolean,
category?: CategoryV5
): WsKey { ): WsKey {
const isPrivateTopic = isPrivate === true || PRIVATE_TOPICS.includes(topic); const isPrivateTopic = isPrivate === true || PRIVATE_TOPICS.includes(topic);
switch (market) { switch (market) {
@@ -297,12 +358,138 @@ export function getWsKeyForTopic(
? WS_KEY_MAP.contractUSDTPrivate ? WS_KEY_MAP.contractUSDTPrivate
: WS_KEY_MAP.contractUSDTPublic; : WS_KEY_MAP.contractUSDTPublic;
} }
case 'v5': {
if (isPrivateTopic) {
return WS_KEY_MAP.v5Private;
}
switch (category) {
case 'spot': {
return WS_KEY_MAP.v5SpotPublic;
}
case 'linear': {
return WS_KEY_MAP.v5LinearPublic;
}
case 'inverse': {
return WS_KEY_MAP.v5InversePublic;
}
case 'option': {
return WS_KEY_MAP.v5OptionPublic;
}
case undefined: {
throw new Error('Category cannot be undefined');
}
default: {
throw neverGuard(
category,
'getWsKeyForTopic(v5): Unhandled v5 category'
);
}
}
// TODO: simple way to manage many public api groups in one api market?
return isPrivateTopic ? WS_KEY_MAP.v5Private : WS_KEY_MAP.v5Private;
}
default: { default: {
throw neverGuard(market, 'getWsKeyForTopic(): Unhandled market'); throw neverGuard(market, 'getWsKeyForTopic(): Unhandled market');
} }
} }
} }
export function getWsUrl(
wsKey: WsKey,
wsUrl: string | undefined,
isTestnet: boolean
): string {
if (wsUrl) {
return wsUrl;
}
const networkKey = isTestnet ? 'testnet' : 'livenet';
switch (wsKey) {
case WS_KEY_MAP.linearPublic: {
return WS_BASE_URL_MAP.linear.public[networkKey];
}
case WS_KEY_MAP.linearPrivate: {
return WS_BASE_URL_MAP.linear.private[networkKey];
}
case WS_KEY_MAP.spotPublic: {
return WS_BASE_URL_MAP.spot.public[networkKey];
}
case WS_KEY_MAP.spotPrivate: {
return WS_BASE_URL_MAP.spot.private[networkKey];
}
case WS_KEY_MAP.spotV3Public: {
return WS_BASE_URL_MAP.spotv3.public[networkKey];
}
case WS_KEY_MAP.spotV3Private: {
return WS_BASE_URL_MAP.spotv3.private[networkKey];
}
case WS_KEY_MAP.inverse: {
// private and public are on the same WS connection
return WS_BASE_URL_MAP.inverse.public[networkKey];
}
case WS_KEY_MAP.usdcOptionPublic: {
return WS_BASE_URL_MAP.usdcOption.public[networkKey];
}
case WS_KEY_MAP.usdcOptionPrivate: {
return WS_BASE_URL_MAP.usdcOption.private[networkKey];
}
case WS_KEY_MAP.usdcPerpPublic: {
return WS_BASE_URL_MAP.usdcPerp.public[networkKey];
}
case WS_KEY_MAP.usdcPerpPrivate: {
return WS_BASE_URL_MAP.usdcPerp.private[networkKey];
}
case WS_KEY_MAP.unifiedOptionPublic: {
return WS_BASE_URL_MAP.unifiedOption.public[networkKey];
}
case WS_KEY_MAP.unifiedPerpUSDTPublic: {
return WS_BASE_URL_MAP.unifiedPerpUSDT.public[networkKey];
}
case WS_KEY_MAP.unifiedPerpUSDCPublic: {
return WS_BASE_URL_MAP.unifiedPerpUSDC.public[networkKey];
}
case WS_KEY_MAP.unifiedPrivate: {
return WS_BASE_URL_MAP.unifiedPerp.private[networkKey];
}
case WS_KEY_MAP.contractInversePrivate: {
return WS_BASE_URL_MAP.contractInverse.private[networkKey];
}
case WS_KEY_MAP.contractInversePublic: {
return WS_BASE_URL_MAP.contractInverse.public[networkKey];
}
case WS_KEY_MAP.contractUSDTPrivate: {
return WS_BASE_URL_MAP.contractUSDT.private[networkKey];
}
case WS_KEY_MAP.contractUSDTPublic: {
return WS_BASE_URL_MAP.contractUSDT.public[networkKey];
}
case WS_KEY_MAP.v5Private: {
return WS_BASE_URL_MAP.v5.private[networkKey];
}
case WS_KEY_MAP.v5SpotPublic: {
return WS_BASE_URL_MAP.v5SpotPublic.public[networkKey];
}
case WS_KEY_MAP.v5LinearPublic: {
return WS_BASE_URL_MAP.v5LinearPublic.public[networkKey];
}
case WS_KEY_MAP.v5InversePublic: {
return WS_BASE_URL_MAP.v5InversePublic.public[networkKey];
}
case WS_KEY_MAP.v5OptionPublic: {
return WS_BASE_URL_MAP.v5OptionPublic.public[networkKey];
}
default: {
this.logger.error('getWsUrl(): Unhandled wsKey: ', {
category: 'bybit-ws',
wsKey,
});
throw neverGuard(wsKey, 'getWsUrl(): Unhandled wsKey');
}
}
}
export function getMaxTopicsPerSubscribeEvent( export function getMaxTopicsPerSubscribeEvent(
market: APIMarket market: APIMarket
): number | null { ): number | null {
@@ -315,7 +502,8 @@ export function getMaxTopicsPerSubscribeEvent(
case 'unifiedPerp': case 'unifiedPerp':
case 'spot': case 'spot':
case 'contractInverse': case 'contractInverse':
case 'contractUSDT': { case 'contractUSDT':
case 'v5': {
return null; return null;
} }
case 'spotv3': { case 'spotv3': {

View File

@@ -17,6 +17,7 @@ import WsStore from './util/WsStore';
import { import {
APIMarket, APIMarket,
CategoryV5,
KlineInterval, KlineInterval,
RESTClient, RESTClient,
WSClientConfigurableOptions, WSClientConfigurableOptions,
@@ -34,10 +35,13 @@ import {
WsConnectionStateEnum, WsConnectionStateEnum,
getMaxTopicsPerSubscribeEvent, getMaxTopicsPerSubscribeEvent,
getWsKeyForTopic, getWsKeyForTopic,
getWsUrl,
isPrivateWsTopic,
isWsPong, isWsPong,
neverGuard, neverGuard,
serializeParams, serializeParams,
} from './util'; } from './util';
import { RestClientV5 } from './rest-client-v5';
const loggerCategory = { category: 'bybit-ws' }; const loggerCategory = { category: 'bybit-ws' };
@@ -119,13 +123,82 @@ export class WebsocketClient extends EventEmitter {
this.on('error', () => {}); this.on('error', () => {});
} }
/** Get the WsStore that tracks websockets & topics */
public getWsStore(): WsStore {
return this.wsStore;
}
public isTestnet(): boolean {
return this.options.testnet === true;
}
/** /**
* Subscribe to topics & track/persist them. They will be automatically resubscribed to if the connection drops/reconnects. * Subscribe to V5 topics & track/persist them.
* @param wsTopics topic or list of topics * @param wsTopics - topic or list of topics
* @param category - the API category this topic is for (e.g. "linear"). The value is only important when connecting to public topics and will be ignored for private topics.
* @param isPrivateTopic - optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
*/
public subscribeV5(
wsTopics: WsTopic[] | WsTopic[],
category: CategoryV5,
isPrivateTopic?: boolean
) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) => {
const wsKey = getWsKeyForTopic(
this.options.market,
topic,
isPrivateTopic,
category
);
// Persist topic for reconnects
this.wsStore.addTopic(wsKey, topic);
// if connected, send subscription request
if (
this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
) {
return this.requestSubscribeTopics(wsKey, [topic]);
}
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
if (
!this.wsStore.isConnectionState(
wsKey,
WsConnectionStateEnum.CONNECTING
) &&
!this.wsStore.isConnectionState(
wsKey,
WsConnectionStateEnum.RECONNECTING
)
) {
return this.connect(wsKey);
}
});
}
/**
* Subscribe to V1-V3 topics & track/persist them.
*
* Note: for public V5 topics use the `subscribeV5()` method.
*
* Topics will be automatically resubscribed to if the connection resets/drops/reconnects.
* @param wsTopics - topic or list of topics
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) * @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
*/ */
public subscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) { public subscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
if (this.options.market === 'v5') {
topics.forEach((topic) => {
if (!isPrivateWsTopic(topic)) {
throw new Error(
'For public "v5" websocket topics, use the subscribeV5() method & provide the category parameter'
);
}
});
}
topics.forEach((topic) => { topics.forEach((topic) => {
const wsKey = getWsKeyForTopic( const wsKey = getWsKeyForTopic(
@@ -161,12 +234,57 @@ export class WebsocketClient extends EventEmitter {
} }
/** /**
* Unsubscribe from topics & remove them from memory. They won't be re-subscribed to if the connection reconnects. * Unsubscribe from V5 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
* @param wsTopics - topic or list of topics
* @param category - the API category this topic is for (e.g. "linear"). The value is only important when connecting to public topics and will be ignored for private topics.
* @param isPrivateTopic - optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
*/
public unsubscribeV5(
wsTopics: WsTopic[] | WsTopic[],
category: CategoryV5,
isPrivateTopic?: boolean
) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) => {
const wsKey = getWsKeyForTopic(
this.options.market,
topic,
isPrivateTopic,
category
);
// Remove topic from persistence for reconnects
this.wsStore.deleteTopic(wsKey, topic);
// unsubscribe request only necessary if active connection exists
if (
this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
) {
this.requestUnsubscribeTopics(wsKey, [topic]);
}
});
}
/**
* Unsubscribe from V1-V3 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
*
* Note: For public V5 topics, use `unsubscribeV5()` instead!
*
* @param wsTopics topic or list of topics * @param wsTopics topic or list of topics
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet) * @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
*/ */
public unsubscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) { public unsubscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
if (this.options.market === 'v5') {
topics.forEach((topic) => {
if (!isPrivateWsTopic(topic)) {
throw new Error(
'For public "v5" websocket topics, use the unsubscribeV5() method & provide the category parameter'
);
}
});
}
topics.forEach((topic) => { topics.forEach((topic) => {
const wsKey = getWsKeyForTopic( const wsKey = getWsKeyForTopic(
this.options.market, this.options.market,
@@ -251,6 +369,13 @@ export class WebsocketClient extends EventEmitter {
); );
break; break;
} }
case 'v5': {
this.restClient = new RestClientV5(
this.options.restOptions,
this.options.requestOptions
);
break;
}
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
@@ -260,15 +385,6 @@ export class WebsocketClient extends EventEmitter {
} }
} }
/** Get the WsStore that tracks websockets & topics */
public getWsStore(): WsStore {
return this.wsStore;
}
public isTestnet(): boolean {
return this.options.testnet === true;
}
public close(wsKey: WsKey, force?: boolean) { public close(wsKey: WsKey, force?: boolean) {
this.logger.info('Closing connection', { ...loggerCategory, wsKey }); this.logger.info('Closing connection', { ...loggerCategory, wsKey });
this.setWsState(wsKey, WsConnectionStateEnum.CLOSING); this.setWsState(wsKey, WsConnectionStateEnum.CLOSING);
@@ -310,6 +426,9 @@ export class WebsocketClient extends EventEmitter {
case 'contractInverse': { case 'contractInverse': {
return [...this.connectPublic(), this.connectPrivate()]; return [...this.connectPublic(), this.connectPrivate()];
} }
case 'v5': {
return [this.connectPrivate()];
}
default: { default: {
throw neverGuard(this.options.market, 'connectAll(): Unhandled market'); throw neverGuard(this.options.market, 'connectAll(): Unhandled market');
} }
@@ -349,6 +468,14 @@ export class WebsocketClient extends EventEmitter {
return [this.connect(WS_KEY_MAP.contractUSDTPublic)]; return [this.connect(WS_KEY_MAP.contractUSDTPublic)];
case 'contractInverse': case 'contractInverse':
return [this.connect(WS_KEY_MAP.contractInversePublic)]; return [this.connect(WS_KEY_MAP.contractInversePublic)];
case 'v5': {
return [
this.connect(WS_KEY_MAP.v5SpotPublic),
this.connect(WS_KEY_MAP.v5LinearPublic),
this.connect(WS_KEY_MAP.v5InversePublic),
this.connect(WS_KEY_MAP.v5OptionPublic),
];
}
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
@@ -386,6 +513,9 @@ export class WebsocketClient extends EventEmitter {
return this.connect(WS_KEY_MAP.contractUSDTPrivate); return this.connect(WS_KEY_MAP.contractUSDTPrivate);
case 'contractInverse': case 'contractInverse':
return this.connect(WS_KEY_MAP.contractInversePrivate); return this.connect(WS_KEY_MAP.contractInversePrivate);
case 'v5': {
return this.connect(WS_KEY_MAP.v5Private);
}
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
@@ -423,8 +553,8 @@ export class WebsocketClient extends EventEmitter {
} }
const authParams = await this.getAuthParams(wsKey); const authParams = await this.getAuthParams(wsKey);
const url = this.getWsUrl(wsKey) + authParams; const url = getWsUrl(wsKey, this.options.wsUrl, this.isTestnet());
const ws = this.connectToWsUrl(url, wsKey); const ws = this.connectToWsUrl(url + authParams, wsKey);
return this.wsStore.setWs(wsKey, ws); return this.wsStore.setWs(wsKey, ws);
} catch (err) { } catch (err) {
@@ -891,85 +1021,9 @@ export class WebsocketClient extends EventEmitter {
this.wsStore.setConnectionState(wsKey, state); this.wsStore.setConnectionState(wsKey, state);
} }
private getWsUrl(wsKey: WsKey): string {
if (this.options.wsUrl) {
return this.options.wsUrl;
}
const networkKey = this.isTestnet() ? 'testnet' : 'livenet';
switch (wsKey) {
case WS_KEY_MAP.linearPublic: {
return WS_BASE_URL_MAP.linear.public[networkKey];
}
case WS_KEY_MAP.linearPrivate: {
return WS_BASE_URL_MAP.linear.private[networkKey];
}
case WS_KEY_MAP.spotPublic: {
return WS_BASE_URL_MAP.spot.public[networkKey];
}
case WS_KEY_MAP.spotPrivate: {
return WS_BASE_URL_MAP.spot.private[networkKey];
}
case WS_KEY_MAP.spotV3Public: {
return WS_BASE_URL_MAP.spotv3.public[networkKey];
}
case WS_KEY_MAP.spotV3Private: {
return WS_BASE_URL_MAP.spotv3.private[networkKey];
}
case WS_KEY_MAP.inverse: {
// private and public are on the same WS connection
return WS_BASE_URL_MAP.inverse.public[networkKey];
}
case WS_KEY_MAP.usdcOptionPublic: {
return WS_BASE_URL_MAP.usdcOption.public[networkKey];
}
case WS_KEY_MAP.usdcOptionPrivate: {
return WS_BASE_URL_MAP.usdcOption.private[networkKey];
}
case WS_KEY_MAP.usdcPerpPublic: {
return WS_BASE_URL_MAP.usdcPerp.public[networkKey];
}
case WS_KEY_MAP.usdcPerpPrivate: {
return WS_BASE_URL_MAP.usdcPerp.private[networkKey];
}
case WS_KEY_MAP.unifiedOptionPublic: {
return WS_BASE_URL_MAP.unifiedOption.public[networkKey];
}
case WS_KEY_MAP.unifiedPerpUSDTPublic: {
return WS_BASE_URL_MAP.unifiedPerpUSDT.public[networkKey];
}
case WS_KEY_MAP.unifiedPerpUSDCPublic: {
return WS_BASE_URL_MAP.unifiedPerpUSDC.public[networkKey];
}
case WS_KEY_MAP.unifiedPrivate: {
return WS_BASE_URL_MAP.unifiedPerp.private[networkKey];
}
case WS_KEY_MAP.contractInversePrivate: {
return WS_BASE_URL_MAP.contractInverse.private[networkKey];
}
case WS_KEY_MAP.contractInversePublic: {
return WS_BASE_URL_MAP.contractInverse.public[networkKey];
}
case WS_KEY_MAP.contractUSDTPrivate: {
return WS_BASE_URL_MAP.contractUSDT.private[networkKey];
}
case WS_KEY_MAP.contractUSDTPublic: {
return WS_BASE_URL_MAP.contractUSDT.public[networkKey];
}
default: {
this.logger.error('getWsUrl(): Unhandled wsKey: ', {
...loggerCategory,
wsKey,
});
throw neverGuard(wsKey, 'getWsUrl(): Unhandled wsKey');
}
}
}
private wrongMarketError(market: APIMarket) { private wrongMarketError(market: APIMarket) {
return new Error( return new Error(
`This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}' to listen to spot topics` `This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}'" to listen to ${market} topics`
); );
} }