From eb5f8333c1c1b79b1e6ccc33dd67d6b7b27d0dda Mon Sep 17 00:00:00 2001 From: tiagosiebler Date: Sun, 7 Feb 2021 16:41:42 +0000 Subject: [PATCH] clean linear websocket work --- README.md | 45 ++++++++++++++++++++++++----------- src/websocket-client.ts | 52 +++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 2d75d55..854b870 100644 --- a/README.md +++ b/README.md @@ -101,20 +101,22 @@ const wsConfig = { key: API_KEY, secret: PRIVATE_KEY, - // The following parameters are optional: + /* + The following parameters are optional: + */ - // defaults to false == testnet. set to true for livenet. + // defaults to false == testnet. Set to true for livenet. // livenet: true - // override which URL to use for websocket connections - // wsUrl: 'wss://stream.bytick.com/realtime' - - // how often to check (in ms) that WS connection is still alive - // pingInterval: 10000, + // defaults to fase == inverse. Set to true for linear (USDT) trading. + // linear: true // how long to wait (in ms) before deciding the connection should be terminated & reconnected // pongTimeout: 1000, + // how often to check (in ms) that WS connection is still alive + // pingInterval: 10000, + // how long to wait before attempting to reconnect (in ms) after connection is closed // reconnectTimeout: 500, @@ -123,45 +125,60 @@ const wsConfig = { // config for axios to pass to RestClient. E.g for proxy support // requestOptions: { } + + // override which URL to use for websocket connections + // wsUrl: 'wss://stream.bytick.com/realtime' }; const ws = new WebsocketClient(wsConfig); +// subscribe to multiple topics at once ws.subscribe(['position', 'execution', 'trade']); + +// and/or subscribe to individual topics on demand ws.subscribe('kline.BTCUSD.1m'); -ws.on('open', () => { - console.log('connection open'); +// Listen to events coming from websockets. This is the primary data source +ws.on('update', data => { + console.log('update', data); }); -ws.on('update', message => { - console.log('update', message); +// Optional: Listen to websocket connection open event (automatic after subscribing to one or more topics) +ws.on('open', ({ wsKey, event }) => { + console.log('connection open for websocket with ID: ' + wsKey); }); +// Optional: Listen to responses to websocket queries (e.g. the response after subscribing to a topic) ws.on('response', response => { console.log('response', response); }); +// Optional: Listen to connection close event. Unexpected connection closes are automatically reconnected. ws.on('close', () => { console.log('connection closed'); }); +// Optional: Listen to raw error events. +// Note: responses to invalid topics are currently only sent in the "response" event. ws.on('error', err => { console.error('ERR', err); }); ``` -See inverse [websocket-client.ts](./src/websocket-client.ts) for further information. +See [websocket-client.ts](./src/websocket-client.ts) for further information. ### Customise Logging Pass a custom logger which supports the log methods `silly`, `debug`, `notice`, `info`, `warning` and `error`, or override methods from the default logger as desired: ```js -const { RestClient, WebsocketClient, DefaultLogger } = require('bybit-api'); +const { WebsocketClient, DefaultLogger } = require('bybit-api'); // Disable all logging on the silly level DefaultLogger.silly = () => {}; -const ws = new WebsocketClient({key: 'xxx', secret: 'yyy'}, DefaultLogger); +const ws = new WebsocketClient( + { key: 'xxx', secret: 'yyy' }, + DefaultLogger +); ``` ## Contributions & Thanks diff --git a/src/websocket-client.ts b/src/websocket-client.ts index d21e411..92038f7 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -49,7 +49,6 @@ export interface WSClientConfigurableOptions { pongTimeout?: number; pingInterval?: number; reconnectTimeout?: number; - autoConnectWs?: boolean; restOptions?: any; requestOptions?: any; wsUrl?: string; @@ -97,7 +96,7 @@ export class WebsocketClient extends EventEmitter { ...options }; - if (this.options.linear === true) { + if (this.isLinear()) { this.restClient = new LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions); } else { this.restClient = new InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions); @@ -108,8 +107,12 @@ export class WebsocketClient extends EventEmitter { return this.options.livenet === true; } + public isLinear(): boolean { + return this.options.linear === true; + } + public isInverse(): boolean { - return !this.options.linear; + return !this.isLinear(); } /** @@ -150,15 +153,15 @@ export class WebsocketClient extends EventEmitter { topic )); - // unsubscribe not necessary if not yet connected - if (this.wsStore.isConnectionState(wsKeyInverse, READY_STATE_CONNECTED)) { - this.wsStore.getKeys().forEach(wsKey => + this.wsStore.getKeys().forEach(wsKey => { + // unsubscribe request only necessary if active connection exists + if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTED)) { this.requestUnsubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]) - ); - } + } + }); } - public close(wsKey: string = wsKeyInverse) { + public close(wsKey: string) { this.logger.info('Closing connection', { ...loggerCategory, wsKey }); this.setWsState(wsKey, READY_STATE_CLOSING); this.clearTimers(wsKey); @@ -169,17 +172,17 @@ export class WebsocketClient extends EventEmitter { /** * Request connection of all dependent websockets, instead of waiting for automatic connection by library */ - public connectAll(): Promise[] | undefined { + public connectAll(): Promise[] | undefined { if (this.isInverse()) { return [this.connect(wsKeyInverse)]; } - if (this.options.linear === true) { + if (this.isLinear()) { return [this.connect(wsKeyLinearPublic), this.connect(wsKeyLinearPrivate)]; } } - private async connect(wsKey: string = wsKeyInverse): Promise { + private async connect(wsKey: string): Promise { try { if (this.wsStore.isWsOpen(wsKey)) { this.logger.error('Refused to connect to ws with existing active connection', { ...loggerCategory, wsKey }) @@ -197,9 +200,8 @@ export class WebsocketClient extends EventEmitter { ) { this.setWsState(wsKey, READY_STATE_CONNECTING); } - // this.setWsState(wsKey, READY_STATE_CONNECTING); - const authParams = await this.getAuthParams(); + const authParams = await this.getAuthParams(wsKey); const url = this.getWsUrl(wsKey) + authParams; const ws = this.connectToWsUrl(url, wsKey); @@ -230,11 +232,11 @@ export class WebsocketClient extends EventEmitter { /** * Return params required to make authorized request */ - private async getAuthParams(): Promise { + private async getAuthParams(wsKey: string): Promise { const { key, secret } = this.options; - if (key && secret) { - this.logger.debug('Getting auth\'d request params', loggerCategory); + if (key && secret && wsKey !== wsKeyLinearPublic) { + this.logger.debug('Getting auth\'d request params', { ...loggerCategory, wsKey }); const timeOffset = await this.restClient.getTimeOffset(); @@ -247,9 +249,9 @@ export class WebsocketClient extends EventEmitter { return '?' + serializeParams(params); } else if (!key || !secret) { - this.logger.warning('Connot authenticate websocket, either api or private keys missing.', loggerCategory); + this.logger.warning('Connot authenticate websocket, either api or private keys missing.', { ...loggerCategory, wsKey }); } else { - this.logger.debug('Starting public only websocket client.', loggerCategory); + this.logger.debug('Starting public only websocket client.', { ...loggerCategory, wsKey }); } return ''; @@ -352,11 +354,11 @@ export class WebsocketClient extends EventEmitter { private onWsOpen(event, wsKey: string) { if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTING)) { - this.logger.info('Websocket connected', { ...loggerCategory, wsKey, livenet: this.options.livenet, linear: this.options.linear }); - this.emit('open'); + this.logger.info('Websocket connected', { ...loggerCategory, wsKey, livenet: this.isLivenet(), linear: this.isLinear() }); + this.emit('open', { wsKey, event }); } else if (this.wsStore.isConnectionState(wsKey, READY_STATE_RECONNECTING)) { this.logger.info('Websocket reconnected', { ...loggerCategory, wsKey }); - this.emit('reconnected'); + this.emit('reconnected', { wsKey, event }); } this.setWsState(wsKey, READY_STATE_CONNECTED); @@ -381,14 +383,14 @@ export class WebsocketClient extends EventEmitter { } } - private onWsError(err, wsKey: string = wsKeyInverse) { + private onWsError(err, wsKey: string) { this.parseWsError('Websocket error', err, wsKey); if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTED)) { this.emit('error', err); } } - private onWsClose(event, wsKey: string = wsKeyInverse) { + private onWsClose(event, wsKey: string) { this.logger.info('Websocket connection closed', { ...loggerCategory, wsKey}); if (this.wsStore.getConnectionState(wsKey) !== READY_STATE_CLOSING) { @@ -427,7 +429,7 @@ export class WebsocketClient extends EventEmitter { } const networkKey = this.options.livenet ? 'livenet' : 'testnet'; - if (this.options.linear || wsKey.startsWith('linear')){ + if (this.isLinear() || wsKey.startsWith('linear')){ if (wsKey === wsKeyLinearPublic) { return linearEndpoints.public[networkKey]; }