bybit-api 2.4.0-beta.1 → 2.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,106 +19,59 @@ const inverse_client_1 = require("./inverse-client");
19
19
  const linear_client_1 = require("./linear-client");
20
20
  const spot_client_v3_1 = require("./spot-client-v3");
21
21
  const spot_client_1 = require("./spot-client");
22
- const logger_1 = require("./util/logger");
23
22
  const node_support_1 = require("./util/node-support");
24
23
  const WsStore_1 = __importDefault(require("./util/WsStore"));
25
24
  const util_1 = require("./util");
26
- const inverseEndpoints = {
27
- livenet: 'wss://stream.bybit.com/realtime',
28
- testnet: 'wss://stream-testnet.bybit.com/realtime',
29
- };
30
- const linearEndpoints = {
31
- private: {
32
- livenet: 'wss://stream.bybit.com/realtime_private',
33
- livenet2: 'wss://stream.bytick.com/realtime_private',
34
- testnet: 'wss://stream-testnet.bybit.com/realtime_private',
35
- },
36
- public: {
37
- livenet: 'wss://stream.bybit.com/realtime_public',
38
- livenet2: 'wss://stream.bytick.com/realtime_public',
39
- testnet: 'wss://stream-testnet.bybit.com/realtime_public',
40
- },
41
- };
42
- const spotEndpoints = {
43
- private: {
44
- livenet: 'wss://stream.bybit.com/spot/ws',
45
- testnet: 'wss://stream-testnet.bybit.com/spot/ws',
46
- },
47
- public: {
48
- livenet: 'wss://stream.bybit.com/spot/quote/ws/v1',
49
- livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2',
50
- testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1',
51
- testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2',
52
- },
53
- };
25
+ const usdc_option_client_1 = require("./usdc-option-client");
26
+ const usdc_perpetual_client_1 = require("./usdc-perpetual-client");
54
27
  const loggerCategory = { category: 'bybit-ws' };
55
28
  class WebsocketClient extends events_1.EventEmitter {
56
29
  constructor(options, logger) {
57
30
  super();
58
- this.logger = logger || logger_1.DefaultLogger;
31
+ this.logger = logger || util_1.DefaultLogger;
59
32
  this.wsStore = new WsStore_1.default(this.logger);
60
- this.options = Object.assign({ livenet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options);
61
- if (this.isV3()) {
62
- this.restClient = new spot_client_v3_1.SpotClientV3(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
63
- }
64
- else if (this.isLinear()) {
65
- this.restClient = new linear_client_1.LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
66
- }
67
- else if (this.isSpot()) {
68
- this.restClient = new spot_client_1.SpotClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
69
- this.connectPublic();
70
- }
71
- else {
72
- this.restClient = new inverse_client_1.InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
73
- }
74
- }
75
- isLivenet() {
76
- return this.options.livenet === true;
77
- }
78
- isLinear() {
79
- return this.options.market === 'linear';
80
- }
81
- isSpot() {
82
- return this.options.market === 'spot';
83
- }
84
- isInverse() {
85
- return this.options.market === 'inverse';
86
- }
87
- /** USDC, spot v3, unified margin, account asset */
88
- isV3() {
89
- return this.options.market === 'v3';
33
+ this.options = Object.assign({ testnet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options);
34
+ this.prepareRESTClient();
90
35
  }
91
36
  /**
92
- * Add topic/topics to WS subscription list
37
+ * Only used if we fetch exchange time before attempting auth.
38
+ * Disabled by default.
39
+ * I've removed this for ftx and it's working great, tempted to remove this here
93
40
  */
94
- subscribe(wsTopics) {
95
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
96
- topics.forEach((topic) => this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic));
97
- // attempt to send subscription topic per websocket
98
- this.wsStore.getKeys().forEach((wsKey) => {
99
- // if connected, send subscription request
100
- if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
101
- return this.requestSubscribeTopics(wsKey, topics);
41
+ prepareRESTClient() {
42
+ switch (this.options.market) {
43
+ case 'inverse': {
44
+ this.restClient = new inverse_client_1.InverseClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
45
+ break;
102
46
  }
103
- // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
104
- if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) &&
105
- !this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
106
- return this.connect(wsKey);
47
+ case 'linear': {
48
+ this.restClient = new linear_client_1.LinearClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
49
+ break;
107
50
  }
108
- });
109
- }
110
- /**
111
- * Remove topic/topics from WS subscription list
112
- */
113
- unsubscribe(wsTopics) {
114
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
115
- topics.forEach((topic) => this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic));
116
- this.wsStore.getKeys().forEach((wsKey) => {
117
- // unsubscribe request only necessary if active connection exists
118
- if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
119
- this.requestUnsubscribeTopics(wsKey, topics);
51
+ case 'spot': {
52
+ this.restClient = new spot_client_1.SpotClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
53
+ this.connectPublic();
54
+ break;
120
55
  }
121
- });
56
+ case 'spotv3': {
57
+ this.restClient = new spot_client_v3_1.SpotClientV3(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
58
+ break;
59
+ }
60
+ case 'usdcOption': {
61
+ this.restClient = new usdc_option_client_1.USDCOptionClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
62
+ break;
63
+ }
64
+ case 'usdcPerp': {
65
+ this.restClient = new usdc_perpetual_client_1.USDCPerpetualClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
66
+ break;
67
+ }
68
+ default: {
69
+ throw util_1.neverGuard(this.options.market, `prepareRESTClient(): Unhandled market`);
70
+ }
71
+ }
72
+ }
73
+ isTestnet() {
74
+ return this.options.testnet === true;
122
75
  }
123
76
  close(wsKey) {
124
77
  var _a;
@@ -127,43 +80,82 @@ class WebsocketClient extends events_1.EventEmitter {
127
80
  this.clearTimers(wsKey);
128
81
  (_a = this.getWs(wsKey)) === null || _a === void 0 ? void 0 : _a.close();
129
82
  }
83
+ closeAll() {
84
+ const keys = this.wsStore.getKeys();
85
+ keys.forEach((key) => {
86
+ this.close(key);
87
+ });
88
+ }
130
89
  /**
131
90
  * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library
132
91
  */
133
92
  connectAll() {
134
- if (this.isInverse()) {
135
- return [this.connect(util_1.wsKeyInverse)];
136
- }
137
- if (this.isLinear()) {
138
- return [
139
- this.connect(util_1.wsKeyLinearPublic),
140
- this.connect(util_1.wsKeyLinearPrivate),
141
- ];
142
- }
143
- if (this.isSpot()) {
144
- return [this.connect(util_1.wsKeySpotPublic), this.connect(util_1.wsKeySpotPrivate)];
93
+ switch (this.options.market) {
94
+ case 'inverse': {
95
+ // only one for inverse
96
+ return [this.connectPublic()];
97
+ }
98
+ // these all have separate public & private ws endpoints
99
+ case 'linear':
100
+ case 'spot':
101
+ case 'spotv3':
102
+ case 'usdcOption':
103
+ case 'usdcPerp': {
104
+ return [this.connectPublic(), this.connectPrivate()];
105
+ }
106
+ default: {
107
+ throw util_1.neverGuard(this.options.market, `connectAll(): Unhandled market`);
108
+ }
145
109
  }
146
110
  }
147
111
  connectPublic() {
148
- if (this.isInverse()) {
149
- return this.connect(util_1.wsKeyInverse);
150
- }
151
- if (this.isLinear()) {
152
- return this.connect(util_1.wsKeyLinearPublic);
153
- }
154
- if (this.isSpot()) {
155
- return this.connect(util_1.wsKeySpotPublic);
112
+ switch (this.options.market) {
113
+ case 'inverse': {
114
+ return this.connect(util_1.WS_KEY_MAP.inverse);
115
+ }
116
+ case 'linear': {
117
+ return this.connect(util_1.WS_KEY_MAP.linearPublic);
118
+ }
119
+ case 'spot': {
120
+ return this.connect(util_1.WS_KEY_MAP.spotPublic);
121
+ }
122
+ case 'spotv3': {
123
+ return this.connect(util_1.WS_KEY_MAP.spotV3Public);
124
+ }
125
+ case 'usdcOption': {
126
+ return this.connect(util_1.WS_KEY_MAP.usdcOptionPublic);
127
+ }
128
+ case 'usdcPerp': {
129
+ return this.connect(util_1.WS_KEY_MAP.usdcPerpPublic);
130
+ }
131
+ default: {
132
+ throw util_1.neverGuard(this.options.market, `connectPublic(): Unhandled market`);
133
+ }
156
134
  }
157
135
  }
158
136
  connectPrivate() {
159
- if (this.isInverse()) {
160
- return this.connect(util_1.wsKeyInverse);
161
- }
162
- if (this.isLinear()) {
163
- return this.connect(util_1.wsKeyLinearPrivate);
164
- }
165
- if (this.isSpot()) {
166
- return this.connect(util_1.wsKeySpotPrivate);
137
+ switch (this.options.market) {
138
+ case 'inverse': {
139
+ return this.connect(util_1.WS_KEY_MAP.inverse);
140
+ }
141
+ case 'linear': {
142
+ return this.connect(util_1.WS_KEY_MAP.linearPrivate);
143
+ }
144
+ case 'spot': {
145
+ return this.connect(util_1.WS_KEY_MAP.spotPrivate);
146
+ }
147
+ case 'spotv3': {
148
+ return this.connect(util_1.WS_KEY_MAP.spotV3Private);
149
+ }
150
+ case 'usdcOption': {
151
+ return this.connect(util_1.WS_KEY_MAP.usdcOptionPrivate);
152
+ }
153
+ case 'usdcPerp': {
154
+ return this.connect(util_1.WS_KEY_MAP.usdcPerpPrivate);
155
+ }
156
+ default: {
157
+ throw util_1.neverGuard(this.options.market, `connectPrivate(): Unhandled market`);
158
+ }
167
159
  }
168
160
  }
169
161
  connect(wsKey) {
@@ -195,6 +187,7 @@ class WebsocketClient extends events_1.EventEmitter {
195
187
  parseWsError(context, error, wsKey) {
196
188
  if (!error.message) {
197
189
  this.logger.error(`${context} due to unexpected error: `, error);
190
+ this.emit('errorEvent', error);
198
191
  return;
199
192
  }
200
193
  switch (error.message) {
@@ -205,35 +198,66 @@ class WebsocketClient extends events_1.EventEmitter {
205
198
  this.logger.error(`{context} due to unexpected response error: ${error.msg}`, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
206
199
  break;
207
200
  }
201
+ this.emit('errorEvent', error);
208
202
  }
209
203
  /**
210
204
  * Return params required to make authorized request
211
205
  */
212
206
  getAuthParams(wsKey) {
213
207
  return __awaiter(this, void 0, void 0, function* () {
214
- const { key, secret } = this.options;
215
- if (key &&
216
- secret &&
217
- wsKey !== util_1.wsKeyLinearPublic &&
218
- wsKey !== util_1.wsKeySpotPublic) {
219
- this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey }));
220
- const timeOffset = this.options.fetchTimeOffsetBeforeAuth
221
- ? yield this.restClient.fetchTimeOffset()
222
- : 0;
223
- const params = {
208
+ if (util_1.PUBLIC_WS_KEYS.includes(wsKey)) {
209
+ this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
210
+ return '';
211
+ }
212
+ try {
213
+ const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey);
214
+ const authParams = {
224
215
  api_key: this.options.key,
225
- expires: Date.now() + timeOffset + 5000,
216
+ expires: expiresAt,
217
+ signature,
226
218
  };
227
- params.signature = yield node_support_1.signMessage('GET/realtime' + params.expires, secret);
228
- return '?' + util_1.serializeParams(params);
219
+ return '?' + util_1.serializeParams(authParams);
229
220
  }
230
- else if (!key || !secret) {
231
- this.logger.warning('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
221
+ catch (e) {
222
+ this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
223
+ return '';
232
224
  }
233
- else {
234
- this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
225
+ });
226
+ }
227
+ sendAuthRequest(wsKey) {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ try {
230
+ const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey);
231
+ const request = {
232
+ op: 'auth',
233
+ args: [this.options.key, expiresAt, signature],
234
+ req_id: `${wsKey}-auth`,
235
+ };
236
+ return this.tryWsSend(wsKey, JSON.stringify(request));
237
+ }
238
+ catch (e) {
239
+ this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
235
240
  }
236
- return '';
241
+ });
242
+ }
243
+ getWsAuthSignature(wsKey) {
244
+ var _a;
245
+ return __awaiter(this, void 0, void 0, function* () {
246
+ const { key, secret } = this.options;
247
+ if (!key || !secret) {
248
+ this.logger.warning('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
249
+ throw new Error(`Cannot auth - missing api or secret in config`);
250
+ }
251
+ this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey }));
252
+ const timeOffset = this.options.fetchTimeOffsetBeforeAuth
253
+ ? (yield ((_a = this.restClient) === null || _a === void 0 ? void 0 : _a.fetchTimeOffset())) || 0
254
+ : 0;
255
+ const signatureExpiresAt = Date.now() + timeOffset + 5000;
256
+ const signature = yield node_support_1.signMessage('GET/realtime' + signatureExpiresAt, secret);
257
+ return {
258
+ expiresAt: signatureExpiresAt,
259
+ signature,
260
+ };
237
261
  });
238
262
  }
239
263
  reconnectWithDelay(wsKey, connectionDelayMs) {
@@ -242,7 +266,7 @@ class WebsocketClient extends events_1.EventEmitter {
242
266
  util_1.WsConnectionStateEnum.CONNECTING) {
243
267
  this.setWsState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING);
244
268
  }
245
- setTimeout(() => {
269
+ this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
246
270
  this.logger.info('Reconnecting to websocket', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
247
271
  this.connect(wsKey);
248
272
  }, connectionDelayMs);
@@ -260,6 +284,10 @@ class WebsocketClient extends events_1.EventEmitter {
260
284
  clearTimers(wsKey) {
261
285
  this.clearPingTimer(wsKey);
262
286
  this.clearPongTimer(wsKey);
287
+ const wsState = this.wsStore.get(wsKey);
288
+ if (wsState === null || wsState === void 0 ? void 0 : wsState.activeReconnectTimer) {
289
+ clearTimeout(wsState.activeReconnectTimer);
290
+ }
263
291
  }
264
292
  // Send a ping at intervals
265
293
  clearPingTimer(wsKey) {
@@ -285,6 +313,7 @@ class WebsocketClient extends events_1.EventEmitter {
285
313
  return;
286
314
  }
287
315
  const wsMessage = JSON.stringify({
316
+ req_id: topics.join(','),
288
317
  op: 'subscribe',
289
318
  args: topics,
290
319
  });
@@ -333,33 +362,60 @@ class WebsocketClient extends events_1.EventEmitter {
333
362
  return ws;
334
363
  }
335
364
  onWsOpen(event, wsKey) {
336
- if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) {
337
- this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, livenet: this.isLivenet(), linear: this.isLinear(), spot: this.isSpot() }));
338
- this.emit('open', { wsKey, event });
339
- }
340
- else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
341
- this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
342
- this.emit('reconnected', { wsKey, event });
343
- }
344
- this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED);
345
- // TODO: persistence not working yet for spot topics
346
- if (wsKey !== 'spotPublic' && wsKey !== 'spotPrivate') {
347
- this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]);
348
- }
349
- this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval);
365
+ return __awaiter(this, void 0, void 0, function* () {
366
+ if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) {
367
+ this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, testnet: this.isTestnet(), market: this.options.market }));
368
+ this.emit('open', { wsKey, event });
369
+ }
370
+ else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
371
+ this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
372
+ this.emit('reconnected', { wsKey, event });
373
+ }
374
+ this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED);
375
+ // Some websockets require an auth packet to be sent after opening the connection
376
+ if (util_1.WS_AUTH_ON_CONNECT_KEYS.includes(wsKey)) {
377
+ this.logger.info(`Sending auth request...`);
378
+ yield this.sendAuthRequest(wsKey);
379
+ }
380
+ // TODO: persistence not working yet for spot v1 topics
381
+ if (wsKey !== util_1.WS_KEY_MAP.spotPublic && wsKey !== util_1.WS_KEY_MAP.spotPrivate) {
382
+ this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]);
383
+ }
384
+ this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval);
385
+ });
350
386
  }
351
387
  onWsMessage(event, wsKey) {
352
388
  try {
353
389
  // any message can clear the pong timer - wouldn't get a message if the ws dropped
354
390
  this.clearPongTimer(wsKey);
355
391
  const msg = JSON.parse((event && event.data) || event);
356
- if (msg['success'] || (msg === null || msg === void 0 ? void 0 : msg.pong)) {
357
- return this.onWsMessageResponse(msg, wsKey);
392
+ this.logger.silly('Received event', Object.assign(Object.assign({}, this.logger), { wsKey, msg: JSON.stringify(msg, null, 2) }));
393
+ // TODO: cleanme
394
+ if (msg['success'] || (msg === null || msg === void 0 ? void 0 : msg.pong) || util_1.isWsPong(msg)) {
395
+ if (util_1.isWsPong(msg)) {
396
+ this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
397
+ }
398
+ else {
399
+ this.emit('response', msg);
400
+ }
401
+ return;
358
402
  }
359
- if (msg.topic) {
403
+ if (msg['finalFragment']) {
404
+ return this.emit('response', msg);
405
+ }
406
+ if (msg === null || msg === void 0 ? void 0 : msg.topic) {
360
407
  return this.emit('update', msg);
361
408
  }
362
- this.logger.warning('Got unhandled ws message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
409
+ if (
410
+ // spot v1
411
+ (msg === null || msg === void 0 ? void 0 : msg.code) ||
412
+ // spot v3
413
+ (msg === null || msg === void 0 ? void 0 : msg.type) === 'error' ||
414
+ // usdc options
415
+ (msg === null || msg === void 0 ? void 0 : msg.success) === false) {
416
+ return this.emit('errorEvent', msg);
417
+ }
418
+ this.logger.warning('Unhandled/unrecognised ws event message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
363
419
  wsKey }));
364
420
  }
365
421
  catch (e) {
@@ -370,26 +426,18 @@ class WebsocketClient extends events_1.EventEmitter {
370
426
  onWsError(error, wsKey) {
371
427
  this.parseWsError('Websocket error', error, wsKey);
372
428
  if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
373
- this.emit('error', error);
429
+ this.emit('errorEvent', error);
374
430
  }
375
431
  }
376
432
  onWsClose(event, wsKey) {
377
433
  this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
378
434
  if (this.wsStore.getConnectionState(wsKey) !== util_1.WsConnectionStateEnum.CLOSING) {
379
435
  this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
380
- this.emit('reconnect', { wsKey });
436
+ this.emit('reconnect', { wsKey, event });
381
437
  }
382
438
  else {
383
439
  this.setWsState(wsKey, util_1.WsConnectionStateEnum.INITIAL);
384
- this.emit('close', { wsKey });
385
- }
386
- }
387
- onWsMessageResponse(response, wsKey) {
388
- if (util_1.isWsPong(response)) {
389
- this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
390
- }
391
- else {
392
- this.emit('response', response);
440
+ this.emit('close', { wsKey, event });
393
441
  }
394
442
  }
395
443
  getWs(wsKey) {
@@ -402,49 +450,93 @@ class WebsocketClient extends events_1.EventEmitter {
402
450
  if (this.options.wsUrl) {
403
451
  return this.options.wsUrl;
404
452
  }
405
- const networkKey = this.isLivenet() ? 'livenet' : 'testnet';
406
- // TODO: repetitive
407
- if (this.isLinear() || wsKey.startsWith('linear')) {
408
- if (wsKey === util_1.wsKeyLinearPublic) {
409
- return linearEndpoints.public[networkKey];
453
+ const networkKey = this.isTestnet() ? 'testnet' : 'livenet';
454
+ switch (wsKey) {
455
+ case util_1.WS_KEY_MAP.linearPublic: {
456
+ return util_1.WS_BASE_URL_MAP.linear.public[networkKey];
410
457
  }
411
- if (wsKey === util_1.wsKeyLinearPrivate) {
412
- return linearEndpoints.private[networkKey];
458
+ case util_1.WS_KEY_MAP.linearPrivate: {
459
+ return util_1.WS_BASE_URL_MAP.linear.private[networkKey];
413
460
  }
414
- this.logger.error('Unhandled linear wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
415
- return linearEndpoints[networkKey];
416
- }
417
- if (this.isSpot() || wsKey.startsWith('spot')) {
418
- if (wsKey === util_1.wsKeySpotPublic) {
419
- return spotEndpoints.public[networkKey];
461
+ case util_1.WS_KEY_MAP.spotPublic: {
462
+ return util_1.WS_BASE_URL_MAP.spot.public[networkKey];
420
463
  }
421
- if (wsKey === util_1.wsKeySpotPrivate) {
422
- return spotEndpoints.private[networkKey];
464
+ case util_1.WS_KEY_MAP.spotPrivate: {
465
+ return util_1.WS_BASE_URL_MAP.spot.private[networkKey];
466
+ }
467
+ case util_1.WS_KEY_MAP.spotV3Public: {
468
+ return util_1.WS_BASE_URL_MAP.spotv3.public[networkKey];
469
+ }
470
+ case util_1.WS_KEY_MAP.spotV3Private: {
471
+ return util_1.WS_BASE_URL_MAP.spotv3.private[networkKey];
472
+ }
473
+ case util_1.WS_KEY_MAP.inverse: {
474
+ // private and public are on the same WS connection
475
+ return util_1.WS_BASE_URL_MAP.inverse.public[networkKey];
476
+ }
477
+ case util_1.WS_KEY_MAP.usdcOptionPublic: {
478
+ return util_1.WS_BASE_URL_MAP.usdcOption.public[networkKey];
479
+ }
480
+ case util_1.WS_KEY_MAP.usdcOptionPrivate: {
481
+ return util_1.WS_BASE_URL_MAP.usdcOption.private[networkKey];
482
+ }
483
+ case util_1.WS_KEY_MAP.usdcPerpPublic: {
484
+ return util_1.WS_BASE_URL_MAP.usdcPerp.public[networkKey];
485
+ }
486
+ case util_1.WS_KEY_MAP.usdcPerpPrivate: {
487
+ return util_1.WS_BASE_URL_MAP.usdcPerp.private[networkKey];
488
+ }
489
+ default: {
490
+ this.logger.error('getWsUrl(): Unhandled wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
491
+ throw util_1.neverGuard(wsKey, `getWsUrl(): Unhandled wsKey`);
423
492
  }
424
- this.logger.error('Unhandled spot wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
425
- return spotEndpoints[networkKey];
426
- }
427
- // fallback to inverse
428
- return inverseEndpoints[networkKey];
429
- }
430
- getWsKeyForTopic(topic) {
431
- if (this.isInverse()) {
432
- return util_1.wsKeyInverse;
433
- }
434
- if (this.isLinear()) {
435
- return util_1.getLinearWsKeyForTopic(topic);
436
493
  }
437
- return util_1.getSpotWsKeyForTopic(topic);
438
494
  }
439
495
  wrongMarketError(market) {
440
496
  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`);
441
497
  }
442
- // TODO: persistance for subbed topics. Look at ftx-api implementation.
498
+ /**
499
+ * Add topic/topics to WS subscription list
500
+ * @param wsTopics topic or list of topics
501
+ * @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)
502
+ */
503
+ subscribe(wsTopics, isPrivateTopic) {
504
+ const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
505
+ topics.forEach((topic) => this.wsStore.addTopic(util_1.getWsKeyForTopic(this.options.market, topic, isPrivateTopic), topic));
506
+ // attempt to send subscription topic per websocket
507
+ this.wsStore.getKeys().forEach((wsKey) => {
508
+ // if connected, send subscription request
509
+ if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
510
+ return this.requestSubscribeTopics(wsKey, topics);
511
+ }
512
+ // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
513
+ if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) &&
514
+ !this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
515
+ return this.connect(wsKey);
516
+ }
517
+ });
518
+ }
519
+ /**
520
+ * Remove topic/topics from WS subscription list
521
+ * @param wsTopics topic or list of topics
522
+ * @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)
523
+ */
524
+ unsubscribe(wsTopics, isPrivateTopic) {
525
+ const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
526
+ topics.forEach((topic) => this.wsStore.deleteTopic(util_1.getWsKeyForTopic(this.options.market, topic, isPrivateTopic), topic));
527
+ this.wsStore.getKeys().forEach((wsKey) => {
528
+ // unsubscribe request only necessary if active connection exists
529
+ if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
530
+ this.requestUnsubscribeTopics(wsKey, topics);
531
+ }
532
+ });
533
+ }
534
+ /** @deprecated use "market: 'spotv3" client */
443
535
  subscribePublicSpotTrades(symbol, binary) {
444
- if (!this.isSpot()) {
536
+ if (this.options.market !== 'spot') {
445
537
  throw this.wrongMarketError('spot');
446
538
  }
447
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({
539
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
448
540
  topic: 'trade',
449
541
  event: 'sub',
450
542
  symbol,
@@ -453,11 +545,12 @@ class WebsocketClient extends events_1.EventEmitter {
453
545
  },
454
546
  }));
455
547
  }
548
+ /** @deprecated use "market: 'spotv3" client */
456
549
  subscribePublicSpotTradingPair(symbol, binary) {
457
- if (!this.isSpot()) {
550
+ if (this.options.market !== 'spot') {
458
551
  throw this.wrongMarketError('spot');
459
552
  }
460
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({
553
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
461
554
  symbol,
462
555
  topic: 'realtimes',
463
556
  event: 'sub',
@@ -466,11 +559,12 @@ class WebsocketClient extends events_1.EventEmitter {
466
559
  },
467
560
  }));
468
561
  }
562
+ /** @deprecated use "market: 'spotv3" client */
469
563
  subscribePublicSpotV1Kline(symbol, candleSize, binary) {
470
- if (!this.isSpot()) {
564
+ if (this.options.market !== 'spot') {
471
565
  throw this.wrongMarketError('spot');
472
566
  }
473
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({
567
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
474
568
  symbol,
475
569
  topic: 'kline_' + candleSize,
476
570
  event: 'sub',
@@ -482,8 +576,9 @@ class WebsocketClient extends events_1.EventEmitter {
482
576
  //ws.send('{"symbol":"BTCUSDT","topic":"depth","event":"sub","params":{"binary":false}}');
483
577
  //ws.send('{"symbol":"BTCUSDT","topic":"mergedDepth","event":"sub","params":{"binary":false,"dumpScale":1}}');
484
578
  //ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}');
579
+ /** @deprecated use "market: 'spotv3" client */
485
580
  subscribePublicSpotOrderbook(symbol, depth, dumpScale, binary) {
486
- if (!this.isSpot()) {
581
+ if (this.options.market !== 'spot') {
487
582
  throw this.wrongMarketError('spot');
488
583
  }
489
584
  let topic;
@@ -515,7 +610,7 @@ class WebsocketClient extends events_1.EventEmitter {
515
610
  if (dumpScale) {
516
611
  msg.params.dumpScale = dumpScale;
517
612
  }
518
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify(msg));
613
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify(msg));
519
614
  }
520
615
  }
521
616
  exports.WebsocketClient = WebsocketClient;