v2.1.0: deprecate boolean props for websocket market. Expand types. Implement spot ws ping/pong
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bybit-api",
|
||||
"version": "2.0.8",
|
||||
"version": "2.1.0",
|
||||
"description": "Node.js connector for Bybit's REST APIs and WebSockets, with TypeScript & integration tests.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
||||
@@ -61,6 +61,9 @@ export function isPublicEndpoint (endpoint: string): boolean {
|
||||
}
|
||||
|
||||
export function isWsPong(response: any) {
|
||||
if (response.pong) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
response.request &&
|
||||
response.request.op === 'ping' &&
|
||||
|
||||
@@ -56,14 +56,77 @@ export enum WsConnectionState {
|
||||
READY_STATE_RECONNECTING
|
||||
};
|
||||
|
||||
export type APIMarket = 'inverse' | 'linear' | 'spot';
|
||||
|
||||
// Same as inverse futures
|
||||
export type WsPublicInverseTopic = 'orderBookL2_25'
|
||||
| 'orderBookL2_200'
|
||||
| 'trade'
|
||||
| 'insurance'
|
||||
| 'instrument_info'
|
||||
| 'klineV2';
|
||||
|
||||
export type WsPublicUSDTPerpTopic = 'orderBookL2_25'
|
||||
| 'orderBookL2_200'
|
||||
| 'trade'
|
||||
| 'insurance'
|
||||
| 'instrument_info'
|
||||
| 'kline';
|
||||
|
||||
export type WsPublicSpotV1Topic = 'trade'
|
||||
| 'realtimes'
|
||||
| 'kline'
|
||||
| 'depth'
|
||||
| 'mergedDepth'
|
||||
| 'diffDepth';
|
||||
|
||||
export type WsPublicSpotV2Topic = 'depth'
|
||||
| 'kline'
|
||||
| 'trade'
|
||||
| 'bookTicker'
|
||||
| 'realtimes';
|
||||
|
||||
export type WsPublicTopics = WsPublicInverseTopic
|
||||
| WsPublicUSDTPerpTopic
|
||||
| WsPublicSpotV1Topic
|
||||
| WsPublicSpotV2Topic
|
||||
| string;
|
||||
|
||||
// Same as inverse futures
|
||||
export type WsPrivateInverseTopic = 'position'
|
||||
| 'execution'
|
||||
| 'order'
|
||||
| 'stop_order';
|
||||
|
||||
export type WsPrivateUSDTPerpTopic = 'position'
|
||||
| 'execution'
|
||||
| 'order'
|
||||
| 'stop_order'
|
||||
| 'wallet';
|
||||
|
||||
export type WsPrivateSpotTopic = 'outboundAccountInfo'
|
||||
| 'executionReport'
|
||||
| 'ticketInfo';
|
||||
|
||||
export type WsPrivateTopic = WsPrivateInverseTopic
|
||||
| WsPrivateUSDTPerpTopic
|
||||
| WsPrivateSpotTopic
|
||||
| string;
|
||||
|
||||
export type WsTopic = WsPublicTopics | WsPrivateTopic;
|
||||
|
||||
export interface WSClientConfigurableOptions {
|
||||
key?: string;
|
||||
secret?: string;
|
||||
livenet?: boolean;
|
||||
|
||||
// defaults to inverse. Only set one at a time (this interface will change in future)
|
||||
// defaults to inverse.
|
||||
/**
|
||||
* @deprecated Use the property { market: 'linear' } instead
|
||||
*/
|
||||
linear?: boolean;
|
||||
spot?: boolean;
|
||||
|
||||
market?: APIMarket;
|
||||
|
||||
pongTimeout?: number;
|
||||
pingInterval?: number;
|
||||
@@ -75,8 +138,11 @@ export interface WSClientConfigurableOptions {
|
||||
|
||||
export interface WebsocketClientOptions extends WSClientConfigurableOptions {
|
||||
livenet: boolean;
|
||||
linear: boolean;
|
||||
spot: boolean;
|
||||
/**
|
||||
* @deprecated Use the property { market: 'linear' } instead
|
||||
*/
|
||||
linear?: boolean;
|
||||
market?: APIMarket;
|
||||
pongTimeout: number;
|
||||
pingInterval: number;
|
||||
reconnectTimeout: number;
|
||||
@@ -113,7 +179,14 @@ const getSpotWsKeyForTopic = (topic: string): WsKey => {
|
||||
export declare interface WebsocketClient {
|
||||
on(event: 'open' | 'reconnected', listener: ({ wsKey: WsKey, event: any }) => void): this;
|
||||
on(event: 'response' | 'update' | 'error', listener: (response: any) => void): this;
|
||||
on(event: 'reconnect' | 'close', listener: () => void): this;
|
||||
on(event: 'reconnect' | 'close', listener: ({ wsKey: WsKey }) => void): this;
|
||||
}
|
||||
|
||||
function resolveMarket(options: WSClientConfigurableOptions): APIMarket {
|
||||
if (options.linear) {
|
||||
return 'linear';
|
||||
}
|
||||
return 'inverse';
|
||||
}
|
||||
|
||||
export class WebsocketClient extends EventEmitter {
|
||||
@@ -130,19 +203,22 @@ export class WebsocketClient extends EventEmitter {
|
||||
|
||||
this.options = {
|
||||
livenet: false,
|
||||
linear: false,
|
||||
spot: false,
|
||||
pongTimeout: 1000,
|
||||
pingInterval: 10000,
|
||||
reconnectTimeout: 500,
|
||||
...options
|
||||
};
|
||||
|
||||
if (!this.options.market) {
|
||||
this.options.market = resolveMarket(this.options);
|
||||
}
|
||||
|
||||
if (this.isLinear()) {
|
||||
this.restClient = new LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
||||
} else if (this.isSpot()) {
|
||||
// TODO: spot client
|
||||
this.restClient = new LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
||||
this.connectPublic();
|
||||
} else {
|
||||
this.restClient = new InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
||||
}
|
||||
@@ -153,11 +229,11 @@ export class WebsocketClient extends EventEmitter {
|
||||
}
|
||||
|
||||
public isLinear(): boolean {
|
||||
return this.options.linear === true;
|
||||
return this.options.market === 'linear';
|
||||
}
|
||||
|
||||
public isSpot(): boolean {
|
||||
return this.options.spot === true;
|
||||
return this.options.market === 'spot';
|
||||
}
|
||||
|
||||
public isInverse(): boolean {
|
||||
@@ -167,7 +243,7 @@ export class WebsocketClient extends EventEmitter {
|
||||
/**
|
||||
* Add topic/topics to WS subscription list
|
||||
*/
|
||||
public subscribe(wsTopics: string[] | string) {
|
||||
public subscribe(wsTopics: WsTopic[] | WsTopic) {
|
||||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
||||
topics.forEach(topic => this.wsStore.addTopic(
|
||||
this.getWsKeyForTopic(topic),
|
||||
@@ -194,7 +270,7 @@ export class WebsocketClient extends EventEmitter {
|
||||
/**
|
||||
* Remove topic/topics from WS subscription list
|
||||
*/
|
||||
public unsubscribe(wsTopics: string[] | string) {
|
||||
public unsubscribe(wsTopics: WsTopic[] | WsTopic) {
|
||||
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
||||
topics.forEach(topic => this.wsStore.deleteTopic(
|
||||
this.getWsKeyForTopic(topic),
|
||||
@@ -234,6 +310,20 @@ export class WebsocketClient extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public connectPublic(): Promise<WebSocket | undefined> | undefined {
|
||||
if (this.isInverse()) {
|
||||
return this.connect(wsKeyInverse);
|
||||
}
|
||||
|
||||
if (this.isLinear()) {
|
||||
return this.connect(wsKeyLinearPublic);
|
||||
}
|
||||
|
||||
if (this.isSpot()) {
|
||||
return this.connect(wsKeySpotPublic);
|
||||
}
|
||||
}
|
||||
|
||||
public connectPrivate(): Promise<WebSocket | undefined> | undefined {
|
||||
if (this.isInverse()) {
|
||||
return this.connect(wsKeyInverse);
|
||||
@@ -374,6 +464,9 @@ export class WebsocketClient extends EventEmitter {
|
||||
* Send WS message to subscribe to topics.
|
||||
*/
|
||||
private requestSubscribeTopics(wsKey: WsKey, topics: string[]) {
|
||||
if (!topics.length) {
|
||||
return;
|
||||
}
|
||||
const wsMessage = JSON.stringify({
|
||||
op: 'subscribe',
|
||||
args: topics
|
||||
@@ -386,6 +479,9 @@ export class WebsocketClient extends EventEmitter {
|
||||
* Send WS message to unsubscribe from topics.
|
||||
*/
|
||||
private requestUnsubscribeTopics(wsKey: WsKey, topics: string[]) {
|
||||
if (!topics.length) {
|
||||
return;
|
||||
}
|
||||
const wsMessage = JSON.stringify({
|
||||
op: 'unsubscribe',
|
||||
args: topics
|
||||
@@ -400,7 +496,11 @@ export class WebsocketClient extends EventEmitter {
|
||||
if (!wsKey) {
|
||||
throw new Error('Cannot send message due to no known websocket for this wsKey');
|
||||
}
|
||||
this.getWs(wsKey)?.send(wsMessage);
|
||||
const ws = this.getWs(wsKey);
|
||||
if (!ws) {
|
||||
throw new Error(`${wsKey} socket not connected yet, call "connect(${wsKey}) first then try again when the "open" event arrives`);
|
||||
}
|
||||
ws.send(wsMessage);
|
||||
} catch (e) {
|
||||
this.logger.error(`Failed to send WS message`, { ...loggerCategory, wsMessage, wsKey, exception: e });
|
||||
}
|
||||
@@ -429,7 +529,10 @@ export class WebsocketClient extends EventEmitter {
|
||||
|
||||
this.setWsState(wsKey, READY_STATE_CONNECTED);
|
||||
|
||||
// TODO: persistence not working yet for spot topics
|
||||
if (wsKey !== 'spotPublic' && wsKey !== 'spotPrivate') {
|
||||
this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]);
|
||||
}
|
||||
|
||||
this.wsStore.get(wsKey, true)!.activePingTimer = setInterval(
|
||||
() => this.ping(wsKey),
|
||||
@@ -440,7 +543,7 @@ export class WebsocketClient extends EventEmitter {
|
||||
private onWsMessage(event, wsKey: WsKey) {
|
||||
const msg = JSON.parse(event && event.data || event);
|
||||
|
||||
if ('success' in msg) {
|
||||
if ('success' in msg || msg?.pong) {
|
||||
this.onWsMessageResponse(msg, wsKey);
|
||||
} else if (msg.topic) {
|
||||
this.onWsMessageUpdate(msg);
|
||||
@@ -461,10 +564,10 @@ export class WebsocketClient extends EventEmitter {
|
||||
|
||||
if (this.wsStore.getConnectionState(wsKey) !== READY_STATE_CLOSING) {
|
||||
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout!);
|
||||
this.emit('reconnect');
|
||||
this.emit('reconnect', { wsKey });
|
||||
} else {
|
||||
this.setWsState(wsKey, READY_STATE_INITIAL);
|
||||
this.emit('close');
|
||||
this.emit('close', { wsKey });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,4 +638,25 @@ export class WebsocketClient extends EventEmitter {
|
||||
}
|
||||
return getSpotWsKeyForTopic(topic);
|
||||
}
|
||||
|
||||
// TODO: persistance for subbed topics. Look at ftx-api implementation.
|
||||
public subscribePublicSpotTrades(symbol: string, binary?: boolean) {
|
||||
if (!this.isSpot()) {
|
||||
throw new Error(`This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: 'spot' to listen to spot topics`);
|
||||
}
|
||||
|
||||
const subscribeMessage = {
|
||||
topic: 'trade',
|
||||
event: 'sub',
|
||||
symbol,
|
||||
params: {},
|
||||
};
|
||||
if (binary) {
|
||||
subscribeMessage.params = {
|
||||
binary: !!binary,
|
||||
};
|
||||
}
|
||||
|
||||
this.tryWsSend(wsKeySpotPublic, JSON.stringify(subscribeMessage));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user