Merge pull request #250 from tiagosiebler/wsspot

v3.5.6, feat(#249): add 10-per-event limiter to spot v5 subscriptions, chore(): enable trailing comma linting rule
This commit is contained in:
Tiago
2023-04-13 12:42:26 +01:00
committed by GitHub
7 changed files with 104 additions and 90 deletions

View File

@@ -1,4 +1,5 @@
{ {
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true "singleQuote": true,
"trailingComma": "all"
} }

View File

@@ -17,9 +17,10 @@ const wsClient = new WebsocketClient(
// market: 'spot', // market: 'spot',
// market: 'spotv3', // market: 'spotv3',
// market: 'usdcOption', // market: 'usdcOption',
market: 'usdcPerp', // market: 'usdcPerp',
// market: 'unifiedPerp', // market: 'unifiedPerp',
// market: 'unifiedOption', // market: 'unifiedOption',
market: 'contractUSDT',
}, },
logger logger
); );
@@ -139,7 +140,7 @@ wsClient.on('reconnected', (data) => {
// usdc perps (note: the syntax is different for the unified perp market) // usdc perps (note: the syntax is different for the unified perp market)
// (market: 'usdcPerp') // (market: 'usdcPerp')
// wsClient.subscribe('trade.BTCUSDC'); // wsClient.subscribe('trade.BTCUSDC');
wsClient.subscribe('instrument_info.100ms.BTCPERP'); // wsClient.subscribe('instrument_info.100ms.BTCPERP');
// unified perps // unified perps
// wsClient.subscribe('publicTrade.BTCUSDT'); // wsClient.subscribe('publicTrade.BTCUSDT');

View File

@@ -1,6 +1,6 @@
{ {
"name": "bybit-api", "name": "bybit-api",
"version": "3.5.5", "version": "3.5.6",
"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

@@ -31,6 +31,7 @@ if (
method: response.config.method, method: response.config.method,
data: response.config.data, data: response.config.data,
headers: response.config.headers, headers: response.config.headers,
params: response.config.params,
}, },
response: { response: {
status: response.status, status: response.status,
@@ -101,7 +102,7 @@ export default abstract class BaseRestClient {
*/ */
constructor( constructor(
restOptions: RestClientOptions = {}, restOptions: RestClientOptions = {},
networkOptions: AxiosRequestConfig = {} networkOptions: AxiosRequestConfig = {},
) { ) {
this.clientType = this.getClientType(); this.clientType = this.getClientType();
@@ -134,7 +135,7 @@ export default abstract class BaseRestClient {
if (this.key && !this.secret) { if (this.key && !this.secret) {
throw new Error( throw new Error(
'API Key & Secret are both required for private endpoints' 'API Key & Secret are both required for private endpoints',
); );
} }
@@ -172,21 +173,21 @@ export default abstract class BaseRestClient {
method: Method, method: Method,
signMethod: SignMethod, signMethod: SignMethod,
params?: TParams, params?: TParams,
isPublicApi?: true isPublicApi?: true,
): Promise<UnsignedRequest<TParams>>; ): Promise<UnsignedRequest<TParams>>;
private async prepareSignParams<TParams = any>( private async prepareSignParams<TParams = any>(
method: Method, method: Method,
signMethod: SignMethod, signMethod: SignMethod,
params?: TParams, params?: TParams,
isPublicApi?: false | undefined isPublicApi?: false | undefined,
): Promise<SignedRequest<TParams>>; ): Promise<SignedRequest<TParams>>;
private async prepareSignParams<TParams extends SignedRequestContext = any>( private async prepareSignParams<TParams extends SignedRequestContext = any>(
method: Method, method: Method,
signMethod: SignMethod, signMethod: SignMethod,
params?: TParams, params?: TParams,
isPublicApi?: boolean isPublicApi?: boolean,
) { ) {
if (isPublicApi) { if (isPublicApi) {
return { return {
@@ -211,7 +212,7 @@ export default abstract class BaseRestClient {
method: Method, method: Method,
url: string, url: string,
params?: any, params?: any,
isPublicApi?: boolean isPublicApi?: boolean,
): Promise<AxiosRequestConfig> { ): Promise<AxiosRequestConfig> {
const options: AxiosRequestConfig = { const options: AxiosRequestConfig = {
...this.globalRequestOptions, ...this.globalRequestOptions,
@@ -238,7 +239,7 @@ export default abstract class BaseRestClient {
method, method,
'v5auth', 'v5auth',
params, params,
isPublicApi isPublicApi,
); );
const headers = { const headers = {
@@ -269,7 +270,7 @@ export default abstract class BaseRestClient {
method, method,
'v2auth', 'v2auth',
params, params,
isPublicApi isPublicApi,
); );
if (method === 'GET' || this.isSpotV1Client()) { if (method === 'GET' || this.isSpotV1Client()) {
@@ -292,11 +293,11 @@ export default abstract class BaseRestClient {
method: Method, method: Method,
endpoint: string, endpoint: string,
params?: any, params?: any,
isPublicApi?: boolean isPublicApi?: boolean,
): Promise<any> { ): Promise<any> {
// Sanity check to make sure it's only ever prefixed by one forward slash // Sanity check to make sure it's only ever prefixed by one forward slash
const requestUrl = [this.baseUrl, endpoint].join( const requestUrl = [this.baseUrl, endpoint].join(
endpoint.startsWith('/') ? '' : '/' endpoint.startsWith('/') ? '' : '/',
); );
// Build a request and handle signature process // Build a request and handle signature process
@@ -304,7 +305,7 @@ export default abstract class BaseRestClient {
method, method,
requestUrl, requestUrl,
params, params,
isPublicApi isPublicApi,
); );
// Dispatch request // Dispatch request
@@ -355,7 +356,7 @@ export default abstract class BaseRestClient {
private async signRequest<T extends SignedRequestContext | {} = {}>( private async signRequest<T extends SignedRequestContext | {} = {}>(
data: T, data: T,
method: Method, method: Method,
signMethod: SignMethod signMethod: SignMethod,
): Promise<SignedRequest<T>> { ): Promise<SignedRequest<T>> {
const timestamp = Date.now() + (this.timeOffset || 0); const timestamp = Date.now() + (this.timeOffset || 0);
@@ -390,7 +391,7 @@ export default abstract class BaseRestClient {
res.originalParams, res.originalParams,
strictParamValidation, strictParamValidation,
sortProperties, sortProperties,
encodeSerialisedValues encodeSerialisedValues,
) )
: JSON.stringify(res.originalParams); : JSON.stringify(res.originalParams);
@@ -426,7 +427,7 @@ export default abstract class BaseRestClient {
res.originalParams, res.originalParams,
strictParamValidation, strictParamValidation,
sortProperties, sortProperties,
encodeValues encodeValues,
); );
res.sign = await signMessage(res.serializedParams, this.secret); res.sign = await signMessage(res.serializedParams, this.secret);
@@ -473,7 +474,7 @@ export default abstract class BaseRestClient {
if (!serverTime || isNaN(serverTime)) { if (!serverTime || isNaN(serverTime)) {
throw new Error( throw new Error(
`fetchServerTime() returned non-number: "${serverTime}" typeof(${typeof serverTime})` `fetchServerTime() returned non-number: "${serverTime}" typeof(${typeof serverTime})`,
); );
} }

View File

@@ -292,7 +292,7 @@ export function getWsKeyForTopic(
market: APIMarket, market: APIMarket,
topic: string, topic: string,
isPrivate?: boolean, isPrivate?: boolean,
category?: CategoryV5 category?: CategoryV5,
): WsKey { ): WsKey {
const isPrivateTopic = isPrivate === true || PRIVATE_TOPICS.includes(topic); const isPrivateTopic = isPrivate === true || PRIVATE_TOPICS.includes(topic);
switch (market) { switch (market) {
@@ -345,7 +345,7 @@ export function getWsKeyForTopic(
} }
throw new Error( throw new Error(
`Failed to determine wskey for unified perps topic: "${topic}"` `Failed to determine wskey for unified perps topic: "${topic}"`,
); );
} }
case 'contractInverse': { case 'contractInverse': {
@@ -382,7 +382,7 @@ export function getWsKeyForTopic(
default: { default: {
throw neverGuard( throw neverGuard(
category, category,
'getWsKeyForTopic(v5): Unhandled v5 category' 'getWsKeyForTopic(v5): Unhandled v5 category',
); );
} }
} }
@@ -396,7 +396,7 @@ export function getWsKeyForTopic(
export function getWsUrl( export function getWsUrl(
wsKey: WsKey, wsKey: WsKey,
wsUrl: string | undefined, wsUrl: string | undefined,
isTestnet: boolean isTestnet: boolean,
): string { ): string {
if (wsUrl) { if (wsUrl) {
return wsUrl; return wsUrl;
@@ -489,8 +489,10 @@ export function getWsUrl(
} }
export function getMaxTopicsPerSubscribeEvent( export function getMaxTopicsPerSubscribeEvent(
market: APIMarket market: APIMarket,
wsKey: WsKey,
): number | null { ): number | null {
const topicsPerEventSpot = 10;
switch (market) { switch (market) {
case 'inverse': case 'inverse':
case 'linear': case 'linear':
@@ -502,10 +504,13 @@ export function getMaxTopicsPerSubscribeEvent(
case 'contractInverse': case 'contractInverse':
case 'contractUSDT': case 'contractUSDT':
case 'v5': { case 'v5': {
if (wsKey === WS_KEY_MAP.v5SpotPublic) {
return topicsPerEventSpot;
}
return null; return null;
} }
case 'spotv3': { case 'spotv3': {
return 10; return topicsPerEventSpot;
} }
default: { default: {
throw neverGuard(market, 'getWsKeyForTopic(): Unhandled market'); throw neverGuard(market, 'getWsKeyForTopic(): Unhandled market');
@@ -515,7 +520,7 @@ export function getMaxTopicsPerSubscribeEvent(
export function getUsdcWsKeyForTopic( export function getUsdcWsKeyForTopic(
topic: string, topic: string,
subGroup: 'option' | 'perp' subGroup: 'option' | 'perp',
): WsKey { ): WsKey {
const isPrivateTopic = PRIVATE_TOPICS.includes(topic); const isPrivateTopic = PRIVATE_TOPICS.includes(topic);
if (subGroup === 'option') { if (subGroup === 'option') {

View File

@@ -74,7 +74,7 @@ interface WebsocketClientEvents {
export declare interface WebsocketClient { export declare interface WebsocketClient {
on<U extends keyof WebsocketClientEvents>( on<U extends keyof WebsocketClientEvents>(
event: U, event: U,
listener: WebsocketClientEvents[U] listener: WebsocketClientEvents[U],
): this; ): this;
emit<U extends keyof WebsocketClientEvents>( emit<U extends keyof WebsocketClientEvents>(
@@ -95,7 +95,7 @@ export class WebsocketClient extends EventEmitter {
constructor( constructor(
options: WSClientConfigurableOptions, options: WSClientConfigurableOptions,
logger?: typeof DefaultLogger logger?: typeof DefaultLogger,
) { ) {
super(); super();
@@ -140,7 +140,7 @@ export class WebsocketClient extends EventEmitter {
public subscribeV5( public subscribeV5(
wsTopics: WsTopic[] | WsTopic, wsTopics: WsTopic[] | WsTopic,
category: CategoryV5, category: CategoryV5,
isPrivateTopic?: boolean isPrivateTopic?: boolean,
) { ) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
@@ -149,7 +149,7 @@ export class WebsocketClient extends EventEmitter {
this.options.market, this.options.market,
topic, topic,
isPrivateTopic, isPrivateTopic,
category category,
); );
// Persist topic for reconnects // Persist topic for reconnects
@@ -166,11 +166,11 @@ export class WebsocketClient extends EventEmitter {
if ( if (
!this.wsStore.isConnectionState( !this.wsStore.isConnectionState(
wsKey, wsKey,
WsConnectionStateEnum.CONNECTING WsConnectionStateEnum.CONNECTING,
) && ) &&
!this.wsStore.isConnectionState( !this.wsStore.isConnectionState(
wsKey, wsKey,
WsConnectionStateEnum.RECONNECTING WsConnectionStateEnum.RECONNECTING,
) )
) { ) {
return this.connect(wsKey); return this.connect(wsKey);
@@ -193,7 +193,7 @@ export class WebsocketClient extends EventEmitter {
topics.forEach((topic) => { topics.forEach((topic) => {
if (!isPrivateWsTopic(topic)) { if (!isPrivateWsTopic(topic)) {
throw new Error( throw new Error(
'For public "v5" websocket topics, use the subscribeV5() method & provide the category parameter' 'For public "v5" websocket topics, use the subscribeV5() method & provide the category parameter',
); );
} }
}); });
@@ -203,7 +203,7 @@ export class WebsocketClient extends EventEmitter {
const wsKey = getWsKeyForTopic( const wsKey = getWsKeyForTopic(
this.options.market, this.options.market,
topic, topic,
isPrivateTopic isPrivateTopic,
); );
// Persist topic for reconnects // Persist topic for reconnects
@@ -220,11 +220,11 @@ export class WebsocketClient extends EventEmitter {
if ( if (
!this.wsStore.isConnectionState( !this.wsStore.isConnectionState(
wsKey, wsKey,
WsConnectionStateEnum.CONNECTING WsConnectionStateEnum.CONNECTING,
) && ) &&
!this.wsStore.isConnectionState( !this.wsStore.isConnectionState(
wsKey, wsKey,
WsConnectionStateEnum.RECONNECTING WsConnectionStateEnum.RECONNECTING,
) )
) { ) {
return this.connect(wsKey); return this.connect(wsKey);
@@ -241,7 +241,7 @@ export class WebsocketClient extends EventEmitter {
public unsubscribeV5( public unsubscribeV5(
wsTopics: WsTopic[] | WsTopic, wsTopics: WsTopic[] | WsTopic,
category: CategoryV5, category: CategoryV5,
isPrivateTopic?: boolean isPrivateTopic?: boolean,
) { ) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) => { topics.forEach((topic) => {
@@ -249,7 +249,7 @@ export class WebsocketClient extends EventEmitter {
this.options.market, this.options.market,
topic, topic,
isPrivateTopic, isPrivateTopic,
category category,
); );
// Remove topic from persistence for reconnects // Remove topic from persistence for reconnects
@@ -278,7 +278,7 @@ export class WebsocketClient extends EventEmitter {
topics.forEach((topic) => { topics.forEach((topic) => {
if (!isPrivateWsTopic(topic)) { if (!isPrivateWsTopic(topic)) {
throw new Error( throw new Error(
'For public "v5" websocket topics, use the unsubscribeV5() method & provide the category parameter' 'For public "v5" websocket topics, use the unsubscribeV5() method & provide the category parameter',
); );
} }
}); });
@@ -288,7 +288,7 @@ export class WebsocketClient extends EventEmitter {
const wsKey = getWsKeyForTopic( const wsKey = getWsKeyForTopic(
this.options.market, this.options.market,
topic, topic,
isPrivateTopic isPrivateTopic,
); );
// Remove topic from persistence for reconnects // Remove topic from persistence for reconnects
@@ -312,21 +312,21 @@ export class WebsocketClient extends EventEmitter {
case 'inverse': { case 'inverse': {
this.restClient = new InverseClient( this.restClient = new InverseClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
case 'linear': { case 'linear': {
this.restClient = new LinearClient( this.restClient = new LinearClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
case 'spot': { case 'spot': {
this.restClient = new SpotClient( this.restClient = new SpotClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
this.connectPublic(); this.connectPublic();
break; break;
@@ -334,21 +334,21 @@ export class WebsocketClient extends EventEmitter {
case 'spotv3': { case 'spotv3': {
this.restClient = new SpotClientV3( this.restClient = new SpotClientV3(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
case 'usdcOption': { case 'usdcOption': {
this.restClient = new USDCOptionClient( this.restClient = new USDCOptionClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
case 'usdcPerp': { case 'usdcPerp': {
this.restClient = new USDCPerpetualClient( this.restClient = new USDCPerpetualClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
@@ -356,7 +356,7 @@ export class WebsocketClient extends EventEmitter {
case 'unifiedPerp': { case 'unifiedPerp': {
this.restClient = new UnifiedMarginClient( this.restClient = new UnifiedMarginClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
@@ -364,21 +364,21 @@ export class WebsocketClient extends EventEmitter {
case 'contractUSDT': { case 'contractUSDT': {
this.restClient = new ContractClient( this.restClient = new ContractClient(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
case 'v5': { case 'v5': {
this.restClient = new RestClientV5( this.restClient = new RestClientV5(
this.options.restOptions, this.options.restOptions,
this.options.requestOptions this.options.requestOptions,
); );
break; break;
} }
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
'prepareRESTClient(): Unhandled market' 'prepareRESTClient(): Unhandled market',
); );
} }
} }
@@ -478,7 +478,7 @@ export class WebsocketClient extends EventEmitter {
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
'connectPublic(): Unhandled market' 'connectPublic(): Unhandled market',
); );
} }
} }
@@ -518,7 +518,7 @@ export class WebsocketClient extends EventEmitter {
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
'connectPrivate(): Unhandled market' 'connectPrivate(): Unhandled market',
); );
} }
} }
@@ -529,7 +529,7 @@ export class WebsocketClient extends EventEmitter {
if (this.wsStore.isWsOpen(wsKey)) { if (this.wsStore.isWsOpen(wsKey)) {
this.logger.error( this.logger.error(
'Refused to connect to ws with existing active connection', 'Refused to connect to ws with existing active connection',
{ ...loggerCategory, wsKey } { ...loggerCategory, wsKey },
); );
return this.wsStore.getWs(wsKey); return this.wsStore.getWs(wsKey);
} }
@@ -539,7 +539,7 @@ export class WebsocketClient extends EventEmitter {
) { ) {
this.logger.error( this.logger.error(
'Refused to connect to ws, connection attempt already active', 'Refused to connect to ws, connection attempt already active',
{ ...loggerCategory, wsKey } { ...loggerCategory, wsKey },
); );
return; return;
} }
@@ -586,12 +586,12 @@ export class WebsocketClient extends EventEmitter {
`${context} due to unexpected response error: "${ `${context} due to unexpected response error: "${
error?.msg || error?.message || error error?.msg || error?.message || error
}"`, }"`,
{ ...loggerCategory, wsKey, error } { ...loggerCategory, wsKey, error },
); );
this.executeReconnectableClose(wsKey, 'unhandled onWsError'); this.executeReconnectableClose(wsKey, 'unhandled onWsError');
} else { } else {
this.logger.info( this.logger.info(
`${wsKey} socket forcefully closed. Will not reconnect.` `${wsKey} socket forcefully closed. Will not reconnect.`,
); );
} }
break; break;
@@ -644,14 +644,14 @@ export class WebsocketClient extends EventEmitter {
} }
private async getWsAuthSignature( private async getWsAuthSignature(
wsKey: WsKey wsKey: WsKey,
): Promise<{ expiresAt: number; signature: string }> { ): Promise<{ expiresAt: number; signature: string }> {
const { key, secret } = this.options; const { key, secret } = this.options;
if (!key || !secret) { if (!key || !secret) {
this.logger.warning( this.logger.warning(
'Cannot authenticate websocket, either api or private keys missing.', 'Cannot authenticate websocket, either api or private keys missing.',
{ ...loggerCategory, wsKey } { ...loggerCategory, wsKey },
); );
throw new Error('Cannot auth - missing api or secret in config'); throw new Error('Cannot auth - missing api or secret in config');
} }
@@ -669,7 +669,7 @@ export class WebsocketClient extends EventEmitter {
const signature = await signMessage( const signature = await signMessage(
'GET/realtime' + signatureExpiresAt, 'GET/realtime' + signatureExpiresAt,
secret secret,
); );
return { return {
@@ -714,7 +714,7 @@ export class WebsocketClient extends EventEmitter {
this.wsStore.get(wsKey, true).activePongTimer = setTimeout( this.wsStore.get(wsKey, true).activePongTimer = setTimeout(
() => this.executeReconnectableClose(wsKey, 'Pong timeout'), () => this.executeReconnectableClose(wsKey, 'Pong timeout'),
this.options.pongTimeout this.options.pongTimeout,
); );
} }
@@ -743,7 +743,7 @@ export class WebsocketClient extends EventEmitter {
...loggerCategory, ...loggerCategory,
wsKey, wsKey,
reason, reason,
} },
); );
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout); this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
} }
@@ -790,11 +790,12 @@ export class WebsocketClient extends EventEmitter {
} }
const maxTopicsPerEvent = getMaxTopicsPerSubscribeEvent( const maxTopicsPerEvent = getMaxTopicsPerSubscribeEvent(
this.options.market this.options.market,
wsKey,
); );
if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) { if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) {
this.logger.silly( this.logger.silly(
`Subscribing to topics in batches of ${maxTopicsPerEvent}` `Subscribing to topics in batches of ${maxTopicsPerEvent}`,
); );
for (let i = 0; i < topics.length; i += maxTopicsPerEvent) { for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
const batch = topics.slice(i, i + maxTopicsPerEvent); const batch = topics.slice(i, i + maxTopicsPerEvent);
@@ -802,7 +803,7 @@ export class WebsocketClient extends EventEmitter {
this.requestSubscribeTopics(wsKey, batch); this.requestSubscribeTopics(wsKey, batch);
} }
this.logger.silly( this.logger.silly(
`Finished batch subscribing to ${topics.length} topics` `Finished batch subscribing to ${topics.length} topics`,
); );
return; return;
} }
@@ -825,11 +826,12 @@ export class WebsocketClient extends EventEmitter {
} }
const maxTopicsPerEvent = getMaxTopicsPerSubscribeEvent( const maxTopicsPerEvent = getMaxTopicsPerSubscribeEvent(
this.options.market this.options.market,
wsKey,
); );
if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) { if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) {
this.logger.silly( this.logger.silly(
`Unsubscribing to topics in batches of ${maxTopicsPerEvent}` `Unsubscribing to topics in batches of ${maxTopicsPerEvent}`,
); );
for (let i = 0; i < topics.length; i += maxTopicsPerEvent) { for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
const batch = topics.slice(i, i + maxTopicsPerEvent); const batch = topics.slice(i, i + maxTopicsPerEvent);
@@ -837,7 +839,7 @@ export class WebsocketClient extends EventEmitter {
this.requestUnsubscribeTopics(wsKey, batch); this.requestUnsubscribeTopics(wsKey, batch);
} }
this.logger.silly( this.logger.silly(
`Finished batch unsubscribing to ${topics.length} topics` `Finished batch unsubscribing to ${topics.length} topics`,
); );
return; return;
} }
@@ -859,13 +861,13 @@ export class WebsocketClient extends EventEmitter {
}); });
if (!wsKey) { if (!wsKey) {
throw new Error( throw new Error(
'Cannot send message due to no known websocket for this wsKey' 'Cannot send message due to no known websocket for this wsKey',
); );
} }
const ws = this.getWs(wsKey); const ws = this.getWs(wsKey);
if (!ws) { if (!ws) {
throw new Error( throw new Error(
`${wsKey} socket not connected yet, call "connect(${wsKey}) first then try again when the "open" event arrives` `${wsKey} socket not connected yet, call "connect(${wsKey}) first then try again when the "open" event arrives`,
); );
} }
ws.send(wsMessage); ws.send(wsMessage);
@@ -935,7 +937,7 @@ export class WebsocketClient extends EventEmitter {
this.wsStore.get(wsKey, true)!.activePingTimer = setInterval( this.wsStore.get(wsKey, true)!.activePingTimer = setInterval(
() => this.ping(wsKey), () => this.ping(wsKey),
this.options.pingInterval this.options.pingInterval,
); );
} }
@@ -1022,7 +1024,7 @@ export class WebsocketClient extends EventEmitter {
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 ${market} topics` `This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}'" to listen to ${market} topics`,
); );
} }
@@ -1041,7 +1043,7 @@ export class WebsocketClient extends EventEmitter {
params: { params: {
binary: !!binary, binary: !!binary,
}, },
}) }),
); );
} }
@@ -1060,7 +1062,7 @@ export class WebsocketClient extends EventEmitter {
params: { params: {
binary: !!binary, binary: !!binary,
}, },
}) }),
); );
} }
@@ -1068,7 +1070,7 @@ export class WebsocketClient extends EventEmitter {
public subscribePublicSpotV1Kline( public subscribePublicSpotV1Kline(
symbol: string, symbol: string,
candleSize: KlineInterval, candleSize: KlineInterval,
binary?: boolean binary?: boolean,
) { ) {
if (this.options.market !== 'spot') { if (this.options.market !== 'spot') {
throw this.wrongMarketError('spot'); throw this.wrongMarketError('spot');
@@ -1083,7 +1085,7 @@ export class WebsocketClient extends EventEmitter {
params: { params: {
binary: !!binary, binary: !!binary,
}, },
}) }),
); );
} }
@@ -1096,7 +1098,7 @@ export class WebsocketClient extends EventEmitter {
symbol: string, symbol: string,
depth: 'full' | 'merge' | 'delta', depth: 'full' | 'merge' | 'delta',
dumpScale?: number, dumpScale?: number,
binary?: boolean binary?: boolean,
) { ) {
if (this.options.market !== 'spot') { if (this.options.market !== 'spot') {
throw this.wrongMarketError('spot'); throw this.wrongMarketError('spot');

View File

@@ -28,7 +28,7 @@ describe('Private READ V5 REST API Endpoints', () => {
describe('Trade APIs', () => { describe('Trade APIs', () => {
it('getActiveOrders()', async () => { it('getActiveOrders()', async () => {
expect( expect(
await api.getActiveOrders({ category: 'linear', settleCoin }) await api.getActiveOrders({ category: 'linear', settleCoin }),
).toMatchObject({ ).toMatchObject({
...successResponseObjectV3(), ...successResponseObjectV3(),
}); });
@@ -36,11 +36,12 @@ describe('Private READ V5 REST API Endpoints', () => {
it('getHistoricOrders()', async () => { it('getHistoricOrders()', async () => {
expect(await api.getHistoricOrders({ category: 'linear' })).toMatchObject( expect(await api.getHistoricOrders({ category: 'linear' })).toMatchObject(
{ ...successResponseObjectV3() } { ...successResponseObjectV3() },
); );
}); });
it('getSpotBorrowCheck()', async () => { // 10016 system errors if the account is not UTA upgraded
it.skip('getSpotBorrowCheck()', async () => {
expect(await api.getSpotBorrowCheck(linearSymbol, 'Buy')).toMatchObject({ expect(await api.getSpotBorrowCheck(linearSymbol, 'Buy')).toMatchObject({
...successResponseObjectV3(), ...successResponseObjectV3(),
}); });
@@ -50,7 +51,7 @@ describe('Private READ V5 REST API Endpoints', () => {
describe('Position APIs', () => { describe('Position APIs', () => {
it('getPositionInfo()', async () => { it('getPositionInfo()', async () => {
expect( expect(
await api.getPositionInfo({ category: 'linear', settleCoin }) await api.getPositionInfo({ category: 'linear', settleCoin }),
).toMatchObject({ ).toMatchObject({
...successResponseObjectV3(), ...successResponseObjectV3(),
}); });
@@ -58,7 +59,10 @@ describe('Private READ V5 REST API Endpoints', () => {
it('getExecutionList()', async () => { it('getExecutionList()', async () => {
expect( expect(
await api.getExecutionList({ category: 'linear', symbol: linearSymbol }) await api.getExecutionList({
category: 'linear',
symbol: linearSymbol,
}),
).toMatchObject({ ).toMatchObject({
...successResponseObjectV3(), ...successResponseObjectV3(),
}); });
@@ -66,7 +70,7 @@ describe('Private READ V5 REST API Endpoints', () => {
it('getClosedPnL()', async () => { it('getClosedPnL()', async () => {
expect( expect(
await api.getClosedPnL({ category: 'linear', symbol: linearSymbol }) await api.getClosedPnL({ category: 'linear', symbol: linearSymbol }),
).toMatchObject({ ).toMatchObject({
...successResponseObjectV3(), ...successResponseObjectV3(),
}); });
@@ -76,7 +80,7 @@ describe('Private READ V5 REST API Endpoints', () => {
describe('Account APIs', () => { describe('Account APIs', () => {
it('getWalletBalance()', async () => { it('getWalletBalance()', async () => {
expect( expect(
await api.getWalletBalance({ accountType: 'CONTRACT' }) await api.getWalletBalance({ accountType: 'CONTRACT' }),
).toMatchObject({ ...successResponseObjectV3() }); ).toMatchObject({ ...successResponseObjectV3() });
}); });
@@ -138,13 +142,13 @@ describe('Private READ V5 REST API Endpoints', () => {
it('getDeliveryRecord()', async () => { it('getDeliveryRecord()', async () => {
expect(await api.getDeliveryRecord({ category: 'option' })).toMatchObject( expect(await api.getDeliveryRecord({ category: 'option' })).toMatchObject(
{ ...successResponseObjectV3() } { ...successResponseObjectV3() },
); );
}); });
it('getSettlementRecords()', async () => { it('getSettlementRecords()', async () => {
expect( expect(
await api.getSettlementRecords({ category: 'linear' }) await api.getSettlementRecords({ category: 'linear' }),
).toMatchObject({ ...successResponseObjectV3() }); ).toMatchObject({ ...successResponseObjectV3() });
}); });
@@ -156,19 +160,19 @@ describe('Private READ V5 REST API Endpoints', () => {
it('getAllCoinsBalance()', async () => { it('getAllCoinsBalance()', async () => {
expect( expect(
await api.getAllCoinsBalance({ accountType: 'SPOT' }) await api.getAllCoinsBalance({ accountType: 'SPOT' }),
).toMatchObject({ ...successResponseObjectV3() }); ).toMatchObject({ ...successResponseObjectV3() });
}); });
it('getCoinBalance()', async () => { it('getCoinBalance()', async () => {
expect( expect(
await api.getCoinBalance({ accountType: 'SPOT', coin: settleCoin }) await api.getCoinBalance({ accountType: 'SPOT', coin: settleCoin }),
).toMatchObject({ ...successResponseObjectV3() }); ).toMatchObject({ ...successResponseObjectV3() });
}); });
it('getTransferableCoinList()', async () => { it('getTransferableCoinList()', async () => {
expect( expect(
await api.getTransferableCoinList('SPOT', 'CONTRACT') await api.getTransferableCoinList('SPOT', 'CONTRACT'),
).toMatchObject({ ...successResponseObjectV3() }); ).toMatchObject({ ...successResponseObjectV3() });
}); });
@@ -204,7 +208,7 @@ describe('Private READ V5 REST API Endpoints', () => {
it('getSubAccountDepositRecords()', async () => { it('getSubAccountDepositRecords()', async () => {
expect( expect(
await api.getSubAccountDepositRecords({ subMemberId: 'fakeid' }) await api.getSubAccountDepositRecords({ subMemberId: 'fakeid' }),
).toMatchObject({ ).toMatchObject({
// ...successResponseObjectV3(), // ...successResponseObjectV3(),
// Expected, since sub account ID is fake // Expected, since sub account ID is fake
@@ -220,7 +224,7 @@ describe('Private READ V5 REST API Endpoints', () => {
it('querySubMemberAddress()', async () => { it('querySubMemberAddress()', async () => {
expect( expect(
await api.querySubMemberAddress(settleCoin, 'TRC20', 'fakeid') await api.querySubMemberAddress(settleCoin, 'TRC20', 'fakeid'),
).toMatchObject({ ).toMatchObject({
// ...successResponseObjectV3(), // ...successResponseObjectV3(),
// Expected, since sub account ID is fake // Expected, since sub account ID is fake