Merge pull request #256 from caiusCitiriga/feat/promise-on-ws-sub
#218 Returning a promise when subscribing to topic(s)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
import { WebsocketTopicSubscriptionConfirmationEvent } from './topic-subscription-confirmation';
|
||||||
|
|
||||||
|
export interface WebsocketFailedTopicSubscriptionConfirmationEvent
|
||||||
|
extends WebsocketTopicSubscriptionConfirmationEvent {
|
||||||
|
success: false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { WebsocketTopicSubscriptionConfirmationEvent } from './topic-subscription-confirmation';
|
||||||
|
|
||||||
|
export interface WebsocketSucceededTopicSubscriptionConfirmationEvent
|
||||||
|
extends WebsocketTopicSubscriptionConfirmationEvent {
|
||||||
|
success: true;
|
||||||
|
}
|
||||||
7
src/types/ws-events/topic-subscription-confirmation.ts
Normal file
7
src/types/ws-events/topic-subscription-confirmation.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface WebsocketTopicSubscriptionConfirmationEvent {
|
||||||
|
op: 'subscribe';
|
||||||
|
req_id: string;
|
||||||
|
conn_id: string;
|
||||||
|
ret_msg: string;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { WebsocketSucceededTopicSubscriptionConfirmationEvent } from '../types/ws-events/succeeded-topic-subscription-confirmation';
|
||||||
|
import { WebsocketTopicSubscriptionConfirmationEvent } from '../types/ws-events/topic-subscription-confirmation';
|
||||||
|
|
||||||
export interface RestClientOptions {
|
export interface RestClientOptions {
|
||||||
/** Your API key */
|
/** Your API key */
|
||||||
key?: string;
|
key?: string;
|
||||||
@@ -57,7 +60,7 @@ export function serializeParams(
|
|||||||
params: object = {},
|
params: object = {},
|
||||||
strict_validation = false,
|
strict_validation = false,
|
||||||
sortProperties = true,
|
sortProperties = true,
|
||||||
encodeSerialisedValues = true
|
encodeSerialisedValues = true,
|
||||||
): string {
|
): string {
|
||||||
const properties = sortProperties
|
const properties = sortProperties
|
||||||
? Object.keys(params).sort()
|
? Object.keys(params).sort()
|
||||||
@@ -71,7 +74,7 @@ export function serializeParams(
|
|||||||
|
|
||||||
if (strict_validation === true && typeof value === 'undefined') {
|
if (strict_validation === true && typeof value === 'undefined') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Failed to sign API request due to undefined parameter'
|
'Failed to sign API request due to undefined parameter',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return `${key}=${value}`;
|
return `${key}=${value}`;
|
||||||
@@ -81,7 +84,7 @@ export function serializeParams(
|
|||||||
|
|
||||||
export function getRestBaseUrl(
|
export function getRestBaseUrl(
|
||||||
useTestnet: boolean,
|
useTestnet: boolean,
|
||||||
restInverseOptions: RestClientOptions
|
restInverseOptions: RestClientOptions,
|
||||||
): string {
|
): string {
|
||||||
const exchangeBaseUrls = {
|
const exchangeBaseUrls = {
|
||||||
livenet: 'https://api.bybit.com',
|
livenet: 'https://api.bybit.com',
|
||||||
@@ -124,6 +127,32 @@ export function isWsPong(msg: any): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTopicSubscriptionConfirmation(
|
||||||
|
msg: unknown,
|
||||||
|
): msg is WebsocketTopicSubscriptionConfirmationEvent {
|
||||||
|
if (typeof msg !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!msg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof msg['op'] !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (msg['op'] !== 'subscribe') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTopicSubscriptionSuccess(
|
||||||
|
msg: unknown,
|
||||||
|
): msg is WebsocketSucceededTopicSubscriptionConfirmationEvent {
|
||||||
|
if (!isTopicSubscriptionConfirmation(msg)) return false;
|
||||||
|
return msg.success === true;
|
||||||
|
}
|
||||||
|
|
||||||
export const APIID = 'bybitapinode';
|
export const APIID = 'bybitapinode';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,4 +168,4 @@ export const REST_CLIENT_TYPE_ENUM = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type RestClientType =
|
export type RestClientType =
|
||||||
typeof REST_CLIENT_TYPE_ENUM[keyof typeof REST_CLIENT_TYPE_ENUM];
|
(typeof REST_CLIENT_TYPE_ENUM)[keyof typeof REST_CLIENT_TYPE_ENUM];
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ import {
|
|||||||
getWsKeyForTopic,
|
getWsKeyForTopic,
|
||||||
getWsUrl,
|
getWsUrl,
|
||||||
isPrivateWsTopic,
|
isPrivateWsTopic,
|
||||||
|
isTopicSubscriptionConfirmation,
|
||||||
|
isTopicSubscriptionSuccess,
|
||||||
isWsPong,
|
isWsPong,
|
||||||
neverGuard,
|
neverGuard,
|
||||||
serializeParams,
|
serializeParams,
|
||||||
} from './util';
|
} from './util';
|
||||||
import { RestClientV5 } from './rest-client-v5';
|
import { RestClientV5 } from './rest-client-v5';
|
||||||
|
import { WebsocketTopicSubscriptionConfirmationEvent } from './types/ws-events/topic-subscription-confirmation';
|
||||||
|
|
||||||
const loggerCategory = { category: 'bybit-ws' };
|
const loggerCategory = { category: 'bybit-ws' };
|
||||||
|
|
||||||
@@ -70,6 +73,17 @@ interface WebsocketClientEvents {
|
|||||||
error: (response: any) => void;
|
error: (response: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TopicsPendingSubscriptionsResolver = () => void;
|
||||||
|
type TopicsPendingSubscriptionsRejector = (reason: string) => void;
|
||||||
|
|
||||||
|
interface TopicsPendingSubscriptions {
|
||||||
|
wsKey: string;
|
||||||
|
failedTopicsSubscriptions: Set<string>;
|
||||||
|
pendingTopicsSubscriptions: Set<string>;
|
||||||
|
resolver: TopicsPendingSubscriptionsResolver;
|
||||||
|
rejector: TopicsPendingSubscriptionsRejector;
|
||||||
|
}
|
||||||
|
|
||||||
// Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837
|
// Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837
|
||||||
export declare interface WebsocketClient {
|
export declare interface WebsocketClient {
|
||||||
on<U extends keyof WebsocketClientEvents>(
|
on<U extends keyof WebsocketClientEvents>(
|
||||||
@@ -93,6 +107,8 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
private wsStore: WsStore;
|
private wsStore: WsStore;
|
||||||
|
|
||||||
|
private pendingTopicsSubscriptions: TopicsPendingSubscriptions[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: WSClientConfigurableOptions,
|
options: WSClientConfigurableOptions,
|
||||||
logger?: typeof DefaultLogger,
|
logger?: typeof DefaultLogger,
|
||||||
@@ -144,6 +160,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
) {
|
) {
|
||||||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
||||||
|
|
||||||
|
return new Promise<void>((resolver, rejector) => {
|
||||||
topics.forEach((topic) => {
|
topics.forEach((topic) => {
|
||||||
const wsKey = getWsKeyForTopic(
|
const wsKey = getWsKeyForTopic(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -154,6 +171,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
// Persist topic for reconnects
|
// Persist topic for reconnects
|
||||||
this.wsStore.addTopic(wsKey, topic);
|
this.wsStore.addTopic(wsKey, topic);
|
||||||
|
this.upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector);
|
||||||
|
|
||||||
// if connected, send subscription request
|
// if connected, send subscription request
|
||||||
if (
|
if (
|
||||||
@@ -176,6 +194,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
return this.connect(wsKey);
|
return this.connect(wsKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,7 +206,10 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
* @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 subscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) {
|
public subscribe(
|
||||||
|
wsTopics: WsTopic[] | WsTopic,
|
||||||
|
isPrivateTopic?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
||||||
if (this.options.market === 'v5') {
|
if (this.options.market === 'v5') {
|
||||||
topics.forEach((topic) => {
|
topics.forEach((topic) => {
|
||||||
@@ -199,6 +221,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Promise<void>((resolver, rejector) => {
|
||||||
topics.forEach((topic) => {
|
topics.forEach((topic) => {
|
||||||
const wsKey = getWsKeyForTopic(
|
const wsKey = getWsKeyForTopic(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -208,6 +231,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
// Persist topic for reconnects
|
// Persist topic for reconnects
|
||||||
this.wsStore.addTopic(wsKey, topic);
|
this.wsStore.addTopic(wsKey, topic);
|
||||||
|
this.upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector);
|
||||||
|
|
||||||
// if connected, send subscription request
|
// if connected, send subscription request
|
||||||
if (
|
if (
|
||||||
@@ -230,6 +254,29 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
return this.connect(wsKey);
|
return this.connect(wsKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private upsertPendingTopicsSubscriptions(
|
||||||
|
wsKey: string,
|
||||||
|
topic: string,
|
||||||
|
resolver: TopicsPendingSubscriptionsResolver,
|
||||||
|
rejector: TopicsPendingSubscriptionsRejector,
|
||||||
|
) {
|
||||||
|
const existingWsKeyPendingSubscriptions =
|
||||||
|
this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
|
||||||
|
if (!existingWsKeyPendingSubscriptions) {
|
||||||
|
this.pendingTopicsSubscriptions.push({
|
||||||
|
wsKey,
|
||||||
|
resolver,
|
||||||
|
rejector,
|
||||||
|
failedTopicsSubscriptions: new Set(),
|
||||||
|
pendingTopicsSubscriptions: new Set([topic]),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.add(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,6 +301,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
// Remove topic from persistence for reconnects
|
// Remove topic from persistence for reconnects
|
||||||
this.wsStore.deleteTopic(wsKey, topic);
|
this.wsStore.deleteTopic(wsKey, topic);
|
||||||
|
this.removeTopicPendingSubscription(wsKey, topic);
|
||||||
|
|
||||||
// unsubscribe request only necessary if active connection exists
|
// unsubscribe request only necessary if active connection exists
|
||||||
if (
|
if (
|
||||||
@@ -264,6 +312,26 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private removeTopicPendingSubscription(wsKey: string, topic: string) {
|
||||||
|
const existingWsKeyPendingSubscriptions =
|
||||||
|
this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
|
||||||
|
if (existingWsKeyPendingSubscriptions) {
|
||||||
|
existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.delete(
|
||||||
|
topic,
|
||||||
|
);
|
||||||
|
if (!existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.size) {
|
||||||
|
this.pendingTopicsSubscriptions =
|
||||||
|
this.pendingTopicsSubscriptions.filter((s) => s.wsKey !== wsKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearTopicsPendingSubscriptions(wsKey: string) {
|
||||||
|
this.pendingTopicsSubscriptions = this.pendingTopicsSubscriptions.filter(
|
||||||
|
(s) => s.wsKey !== wsKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe from V1-V3 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
|
* Unsubscribe from V1-V3 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
|
||||||
*
|
*
|
||||||
@@ -293,6 +361,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
// Remove topic from persistence for reconnects
|
// Remove topic from persistence for reconnects
|
||||||
this.wsStore.deleteTopic(wsKey, topic);
|
this.wsStore.deleteTopic(wsKey, topic);
|
||||||
|
this.removeTopicPendingSubscription(wsKey, topic);
|
||||||
|
|
||||||
// unsubscribe request only necessary if active connection exists
|
// unsubscribe request only necessary if active connection exists
|
||||||
if (
|
if (
|
||||||
@@ -953,6 +1022,10 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
// msg: JSON.stringify(msg),
|
// msg: JSON.stringify(msg),
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
if (isTopicSubscriptionConfirmation(msg)) {
|
||||||
|
this.updatePendingTopicSubscriptionStatus(wsKey, msg);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: cleanme
|
// TODO: cleanme
|
||||||
if (msg['success'] || msg?.pong || isWsPong(msg)) {
|
if (msg['success'] || msg?.pong || isWsPong(msg)) {
|
||||||
if (isWsPong(msg)) {
|
if (isWsPong(msg)) {
|
||||||
@@ -997,6 +1070,51 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updatePendingTopicSubscriptionStatus(
|
||||||
|
wsKey: string,
|
||||||
|
msg: WebsocketTopicSubscriptionConfirmationEvent,
|
||||||
|
) {
|
||||||
|
const requestsIds = msg.req_id as string;
|
||||||
|
const pendingTopicsSubscriptions = this.pendingTopicsSubscriptions.find(
|
||||||
|
(s) => s.wsKey === wsKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!pendingTopicsSubscriptions) return;
|
||||||
|
|
||||||
|
const splitRequestsIds = requestsIds.split(',');
|
||||||
|
if (!isTopicSubscriptionSuccess(msg)) {
|
||||||
|
splitRequestsIds.forEach((req_id) =>
|
||||||
|
pendingTopicsSubscriptions.failedTopicsSubscriptions.add(req_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
splitRequestsIds.forEach((req_id) => {
|
||||||
|
this.removeTopicPendingSubscription(wsKey, req_id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!pendingTopicsSubscriptions.pendingTopicsSubscriptions.size &&
|
||||||
|
!pendingTopicsSubscriptions.failedTopicsSubscriptions.size
|
||||||
|
) {
|
||||||
|
// all topics have been subscribed successfully, so we can resolve the subscription request
|
||||||
|
pendingTopicsSubscriptions.resolver();
|
||||||
|
this.clearTopicsPendingSubscriptions(wsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!pendingTopicsSubscriptions.pendingTopicsSubscriptions.size &&
|
||||||
|
pendingTopicsSubscriptions.failedTopicsSubscriptions.size
|
||||||
|
) {
|
||||||
|
// not all topics have been subscribed successfully, so we reject the subscription request
|
||||||
|
// and let the caller handle the situation by providing the list of failed subscriptions requests
|
||||||
|
const failedSubscriptionsMessage = `(${[
|
||||||
|
...pendingTopicsSubscriptions.failedTopicsSubscriptions,
|
||||||
|
].toString()}) failed to subscribe`;
|
||||||
|
pendingTopicsSubscriptions.rejector(failedSubscriptionsMessage);
|
||||||
|
this.clearTopicsPendingSubscriptions(wsKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private onWsClose(event, wsKey: WsKey) {
|
private onWsClose(event, wsKey: WsKey) {
|
||||||
this.logger.info('Websocket connection closed', {
|
this.logger.info('Websocket connection closed', {
|
||||||
...loggerCategory,
|
...loggerCategory,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
|
|
||||||
it('getServerTime()', async () => {
|
it('getServerTime()', async () => {
|
||||||
expect(await api.getServerTime()).toMatchObject(
|
expect(await api.getServerTime()).toMatchObject(
|
||||||
successResponseObjectV3()
|
successResponseObjectV3(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -32,7 +32,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
category: 'linear',
|
category: 'linear',
|
||||||
interval: '1',
|
interval: '1',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
category: 'linear',
|
category: 'linear',
|
||||||
interval: '1',
|
interval: '1',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
category: 'linear',
|
category: 'linear',
|
||||||
interval: '1',
|
interval: '1',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
category: 'linear',
|
category: 'linear',
|
||||||
interval: '1',
|
interval: '1',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
await api.getInstrumentsInfo({
|
await api.getInstrumentsInfo({
|
||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
await api.getOrderbook({
|
await api.getOrderbook({
|
||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
await api.getTickers({
|
await api.getTickers({
|
||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
await api.getFundingRateHistory({
|
await api.getFundingRateHistory({
|
||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,8 +107,12 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
await api.getPublicTradingHistory({
|
await api.getPublicTradingHistory({
|
||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject({
|
||||||
|
...successResponseObjectV3(),
|
||||||
|
retMsg: 'OK',
|
||||||
|
retCode: 0,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getOpenInterest()', async () => {
|
it('getOpenInterest()', async () => {
|
||||||
@@ -117,7 +121,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
intervalTime: '15min',
|
intervalTime: '15min',
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,7 +129,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
expect(
|
expect(
|
||||||
await api.getHistoricalVolatility({
|
await api.getHistoricalVolatility({
|
||||||
category: 'option',
|
category: 'option',
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -138,7 +142,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
await api.getRiskLimit({
|
await api.getRiskLimit({
|
||||||
category: 'linear',
|
category: 'linear',
|
||||||
symbol: linearSymbol,
|
symbol: linearSymbol,
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,7 +150,7 @@ describe('Public V5 REST API Endpoints', () => {
|
|||||||
expect(
|
expect(
|
||||||
await api.getOptionDeliveryPrice({
|
await api.getOptionDeliveryPrice({
|
||||||
category: 'option',
|
category: 'option',
|
||||||
})
|
}),
|
||||||
).toMatchObject(successResponseObjectV3());
|
).toMatchObject(successResponseObjectV3());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
27
test/v5/public.ws.test.ts
Normal file
27
test/v5/public.ws.test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { WebsocketClient } from '../../src';
|
||||||
|
|
||||||
|
describe('Public V5 Websocket client', () => {
|
||||||
|
const api = new WebsocketClient({
|
||||||
|
market: 'v5',
|
||||||
|
});
|
||||||
|
|
||||||
|
const linearSymbol = 'BTCUSDT';
|
||||||
|
const linearCategory = 'linear';
|
||||||
|
|
||||||
|
describe('Topics subscription confirmation', () => {
|
||||||
|
it('can subscribeV5 to LINEAR with valid topic', async () => {
|
||||||
|
await expect(
|
||||||
|
api.subscribeV5(`publicTrade.${linearSymbol}`, linearCategory),
|
||||||
|
).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot subscribeV5 to LINEAR with valid topic', async () => {
|
||||||
|
try {
|
||||||
|
await api.subscribeV5(`publicTrade.${linearSymbol}X`, linearCategory);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toBeDefined();
|
||||||
|
expect(e).toMatch(`(publicTrade.${linearSymbol}X) failed to subscribe`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user