From 10ac2ec384af84ae06197d8b45f7d6301bcd5691 Mon Sep 17 00:00:00 2001 From: tiagosiebler Date: Sun, 15 Aug 2021 11:11:33 +0100 Subject: [PATCH] expand spot public socket support --- examples/ws-public.ts | 55 ++++++++++++++++++++ src/types/shared.ts | 13 +++++ src/websocket-client.ts | 109 ++++++++++++++++++++++++++++++++++------ 3 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 examples/ws-public.ts create mode 100644 src/types/shared.ts diff --git a/examples/ws-public.ts b/examples/ws-public.ts new file mode 100644 index 0000000..130c87c --- /dev/null +++ b/examples/ws-public.ts @@ -0,0 +1,55 @@ +import { DefaultLogger } from '../src'; +import { WebsocketClient, wsKeySpotPublic } from '../src/websocket-client'; + +// or +// import { DefaultLogger, WebsocketClient } from 'bybit-api'; + +(async () => { + const logger = { + ...DefaultLogger, + // silly: () => {}, + }; + + const wsClient = new WebsocketClient({ + // key: key, + // secret: secret, + // market: 'inverse', + // market: 'linear', + market: 'spot', + }, logger); + + wsClient.on('update', (data) => { + console.log('raw message received ', JSON.stringify(data, null, 2)); + }); + + wsClient.on('open', (data) => { + console.log('connection opened open:', data.wsKey); + + if (data.wsKey === wsKeySpotPublic) { + // Spot public. + // wsClient.subscribePublicSpotTrades('BTCUSDT'); + // wsClient.subscribePublicSpotTradingPair('BTCUSDT'); + // wsClient.subscribePublicSpotV1Kline('BTCUSDT', '1m'); + // wsClient.subscribePublicSpotOrderbook('BTCUSDT', 'full'); + } + }); + wsClient.on('response', (data) => { + console.log('log response: ', JSON.stringify(data, null, 2)); + }); + wsClient.on('reconnect', ({ wsKey }) => { + console.log('ws automatically reconnecting.... ', wsKey); + }); + wsClient.on('reconnected', (data) => { + console.log('ws has reconnected ', data?.wsKey ); + }); + + // Inverse + // wsClient.subscribe('trade'); + + // Linear + // wsClient.subscribe('trade.BTCUSDT'); + + // For spot, request public connection first then send required topics on 'open' + // wsClient.connectPublic(); + +})(); diff --git a/src/types/shared.ts b/src/types/shared.ts new file mode 100644 index 0000000..73ea211 --- /dev/null +++ b/src/types/shared.ts @@ -0,0 +1,13 @@ +export type KlineInterval = '1m' + | '3m' + | '5m' + | '15m' + | '30m' + | '1h' + | '2h' + | '4h' + | '6h' + | '12h' + | '1d' + | '1w' + | '1M'; diff --git a/src/websocket-client.ts b/src/websocket-client.ts index 20c460a..b7ccb1c 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -4,6 +4,7 @@ import WebSocket from 'isomorphic-ws'; import { InverseClient } from './inverse-client'; import { LinearClient } from './linear-client'; import { DefaultLogger } from './logger'; +import { KlineInterval } from './types/shared'; import { signMessage } from './util/node-support'; import { serializeParams, isWsPong } from './util/requestUtils'; @@ -541,14 +542,17 @@ export class WebsocketClient extends EventEmitter { } private onWsMessage(event, wsKey: WsKey) { - const msg = JSON.parse(event && event.data || event); - - if ('success' in msg || msg?.pong) { - this.onWsMessageResponse(msg, wsKey); - } else if (msg.topic) { - this.onWsMessageUpdate(msg); - } else { - this.logger.warning('Got unhandled ws message', { ...loggerCategory, message: msg, event, wsKey}); + try { + const msg = JSON.parse(event && event.data || event); + if ('success' in msg || msg?.pong) { + this.onWsMessageResponse(msg, wsKey); + } else if (msg.topic) { + this.onWsMessageUpdate(msg); + } else { + this.logger.warning('Got unhandled ws message', { ...loggerCategory, message: msg, event, wsKey}); + } + } catch (e) { + this.logger.error('Failed to parse ws event message', { ...loggerCategory, error: e, event, wsKey}) } } @@ -639,24 +643,97 @@ export class WebsocketClient extends EventEmitter { return getSpotWsKeyForTopic(topic); } + private wrongMarketError(market: APIMarket) { + 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`); + } + // 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`); + throw this.wrongMarketError('spot'); } - const subscribeMessage = { + return this.tryWsSend(wsKeySpotPublic, JSON.stringify({ topic: 'trade', event: 'sub', symbol, - params: {}, - }; - if (binary) { - subscribeMessage.params = { + params: { binary: !!binary, - }; + } + })); + } + + public subscribePublicSpotTradingPair(symbol: string, binary?: boolean) { + if (!this.isSpot()) { + throw this.wrongMarketError('spot'); } - this.tryWsSend(wsKeySpotPublic, JSON.stringify(subscribeMessage)); + return this.tryWsSend(wsKeySpotPublic, JSON.stringify({ + symbol, + topic: 'realtimes', + event: 'sub', + params: { + binary: !!binary, + }, + })); } + + public subscribePublicSpotV1Kline(symbol: string, candleSize: KlineInterval, binary?: boolean) { + if (!this.isSpot()) { + throw this.wrongMarketError('spot'); + } + + return this.tryWsSend(wsKeySpotPublic, JSON.stringify({ + symbol, + topic: 'kline_' + candleSize, + event: 'sub', + params: { + binary: !!binary, + }, + })); + } + + //ws.send('{"symbol":"BTCUSDT","topic":"depth","event":"sub","params":{"binary":false}}'); + //ws.send('{"symbol":"BTCUSDT","topic":"mergedDepth","event":"sub","params":{"binary":false,"dumpScale":1}}'); + //ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}'); + public subscribePublicSpotOrderbook(symbol: string, depth: 'full' | 'merge' | 'delta', dumpScale?: number, binary?: boolean) { + if (!this.isSpot()) { + throw this.wrongMarketError('spot'); + } + + let topic: string; + switch (depth) { + case 'full': { + topic = 'depth'; + break; + }; + case 'merge': { + topic = 'mergedDepth'; + if (!dumpScale) { + throw new Error(`Dumpscale must be provided for merged orderbooks`); + } + break; + } + case 'delta': { + topic = 'diffDepth'; + break; + } + } + + const msg: any = { + symbol, + topic, + event: 'sub', + params: { + binary: !!binary, + }, + }; + if (dumpScale) { + msg.params.dumpScale = dumpScale; + } + return this.tryWsSend(wsKeySpotPublic, JSON.stringify(msg)); + } + + + };