bybit-api 4.5.3 → 4.6.1

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.
@@ -33,6 +33,31 @@ const logger_1 = require("./logger");
33
33
  const webCryptoAPI_1 = require("./webCryptoAPI");
34
34
  const websockets_1 = require("./websockets");
35
35
  const WsStore_1 = require("./websockets/WsStore");
36
+ /**
37
+ * Appends wsKey and isWSAPIResponse to all events.
38
+ * Some events are arrays, this handles that nested scenario too.
39
+ */
40
+ function getFinalEmittable(emittable, wsKey, isWSAPIResponse) {
41
+ if (Array.isArray(emittable)) {
42
+ return emittable.map((subEvent) => getFinalEmittable(subEvent, wsKey, isWSAPIResponse));
43
+ }
44
+ if (Array.isArray(emittable.event)) {
45
+ // Some topics just emit an array.
46
+ // This is consistent with how it was before the WS API upgrade:
47
+ return emittable.event.map((subEvent) => getFinalEmittable(subEvent, wsKey, isWSAPIResponse));
48
+ // const { event, ...others } = emittable;
49
+ // return {
50
+ // ...others,
51
+ // event: event.map((subEvent) =>
52
+ // getFinalEmittable(subEvent, wsKey, isWSAPIResponse),
53
+ // ),
54
+ // };
55
+ }
56
+ if (emittable.event) {
57
+ return Object.assign(Object.assign({}, emittable.event), { wsKey: wsKey, isWSAPIResponse: !!isWSAPIResponse });
58
+ }
59
+ return Object.assign(Object.assign({}, emittable), { wsKey: wsKey, isWSAPIResponse: !!isWSAPIResponse });
60
+ }
36
61
  /**
37
62
  * Base WebSocket abstraction layer. Handles connections, tracking each connection as a unique "WS Key"
38
63
  */
@@ -48,6 +73,9 @@ class BaseWebsocketClient extends events_1.default {
48
73
  */
49
74
  this.pendingTopicSubscriptionRequests = {};
50
75
  this.logger = logger || logger_1.DefaultLogger;
76
+ this.WS_LOGGER_CATEGORY = {
77
+ category: (options === null || options === void 0 ? void 0 : options.wsLoggerCategory) || 'WsClient',
78
+ };
51
79
  this.wsStore = new WsStore_1.WsStore(this.logger);
52
80
  this.options = Object.assign({
53
81
  // Some defaults:
@@ -57,11 +85,19 @@ class BaseWebsocketClient extends events_1.default {
57
85
  // Calls to subscribeV5() are wrapped in a promise, allowing you to await a subscription request.
58
86
  // Note: due to internal complexity, it's only recommended if you connect before subscribing.
59
87
  promiseSubscribeRequests: false,
88
+ // Requires a confirmation "response" from the ws connection before assuming it is ready
89
+ requireConnectionReadyConfirmation: false,
60
90
  // Automatically send an authentication op/request after a connection opens, for private connections.
61
91
  authPrivateConnectionsOnConnect: true,
62
- // Individual requests do not require a signature, so this is disabled.
63
- authPrivateRequests: false }, options);
64
- // Check Web Crypto API support when credentials are provided and no custom sign function is used
92
+ // Automatically include auth/sign/token with every WS request.
93
+ // Automatically handled during getWsRequestEvents.
94
+ authPrivateRequests: false,
95
+ // Automatically re-auth WS API, if we were auth'd before and get reconnected
96
+ reauthWSAPIOnReconnect: true,
97
+ // Whether to use native heartbeats (depends on the exchange)
98
+ useNativeHeartbeats: false }, options);
99
+ // Check Web Crypto API support
100
+ // when credentials are provided and no custom sign function is used
65
101
  if (this.options.key &&
66
102
  this.options.secret &&
67
103
  !this.options.customSignMessageFn) {
@@ -179,7 +215,7 @@ class BaseWebsocketClient extends events_1.default {
179
215
  /**
180
216
  * Are we in the process of connection? Nothing to send yet.
181
217
  */
182
- this.logger.trace('WS not connected - requests queued for retry once connected.', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
218
+ this.logger.trace('WS not connected - requests queued for retry once connected.', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
183
219
  wsTopicRequests }));
184
220
  return isConnectionInProgress;
185
221
  }
@@ -192,6 +228,7 @@ class BaseWebsocketClient extends events_1.default {
192
228
  *
193
229
  * Auth should already automatically be in progress, so no action needed from here. Topics will automatically subscribe post-auth success.
194
230
  */
231
+ this.logger.trace('WS connected but not authenticated yet - awaiting auth before subscribing');
195
232
  return false;
196
233
  }
197
234
  // Finally, request subscription to topics if the connection is healthy and ready
@@ -250,7 +287,7 @@ class BaseWebsocketClient extends events_1.default {
250
287
  return this.wsStore;
251
288
  }
252
289
  close(wsKey, force) {
253
- this.logger.info('Closing connection', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
290
+ this.logger.info('Closing connection', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
254
291
  this.setWsState(wsKey, websockets_1.WsConnectionStateEnum.CLOSING);
255
292
  this.clearTimers(wsKey);
256
293
  const ws = this.getWs(wsKey);
@@ -277,11 +314,11 @@ class BaseWebsocketClient extends events_1.default {
277
314
  var _a, _b;
278
315
  try {
279
316
  if (this.wsStore.isWsOpen(wsKey)) {
280
- this.logger.error('Refused to connect to ws with existing active connection', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
281
- return { wsKey };
317
+ this.logger.error('Refused to connect to ws with existing active connection', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
318
+ return { wsKey, ws: this.wsStore.getWs(wsKey) };
282
319
  }
283
320
  if (this.wsStore.isConnectionState(wsKey, websockets_1.WsConnectionStateEnum.CONNECTING)) {
284
- this.logger.error('Refused to connect to ws, connection attempt already active', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
321
+ this.logger.error('Refused to connect to ws, connection attempt already active', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
285
322
  return (_a = this.wsStore.getConnectionInProgressPromise(wsKey)) === null || _a === void 0 ? void 0 : _a.promise;
286
323
  }
287
324
  if (!this.wsStore.getConnectionState(wsKey) ||
@@ -291,23 +328,34 @@ class BaseWebsocketClient extends events_1.default {
291
328
  if (!this.wsStore.getConnectionInProgressPromise(wsKey)) {
292
329
  this.wsStore.createConnectionInProgressPromise(wsKey, false);
293
330
  }
294
- const url = customUrl || (yield this.getWsUrl(wsKey));
295
- const ws = this.connectToWsUrl(url, wsKey);
296
- this.wsStore.setWs(wsKey, ws);
297
- return (_b = this.wsStore.getConnectionInProgressPromise(wsKey)) === null || _b === void 0 ? void 0 : _b.promise;
331
+ try {
332
+ const url = customUrl || (yield this.getWsUrl(wsKey));
333
+ const ws = this.connectToWsUrl(url, wsKey);
334
+ this.wsStore.setWs(wsKey, ws);
335
+ }
336
+ catch (e) {
337
+ this.logger.error('Exception fetching WS URL', e);
338
+ throw e;
339
+ }
298
340
  }
299
341
  catch (err) {
300
- this.parseWsError('Connection failed', err, wsKey);
301
- this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
342
+ const canRetry = this.parseWsError('Connection failed', err, wsKey);
343
+ if (canRetry) {
344
+ this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
345
+ }
346
+ else {
347
+ this.logger.info('Preventing retry due to error type to prevent deadlock');
348
+ }
302
349
  if (throwOnError) {
303
350
  throw err;
304
351
  }
305
352
  }
353
+ return (_b = this.wsStore.getConnectionInProgressPromise(wsKey)) === null || _b === void 0 ? void 0 : _b.promise;
306
354
  });
307
355
  }
308
356
  connectToWsUrl(url, wsKey) {
309
357
  var _a;
310
- this.logger.trace(`Opening WS connection to URL: ${url}`, Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
358
+ this.logger.trace(`Opening WS connection to URL: ${url}`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
311
359
  // Support both wsOptions (new) and requestOptions (legacy, for backwards compatibility)
312
360
  const wsOptionsConfig = this.options.wsOptions || {};
313
361
  const legacyAgent = (_a = this.options.requestOptions) === null || _a === void 0 ? void 0 : _a.agent;
@@ -316,56 +364,81 @@ class BaseWebsocketClient extends events_1.default {
316
364
  const finalWsOptions = !wsOptions.agent && legacyAgent
317
365
  ? Object.assign(Object.assign({}, wsOptions), { agent: legacyAgent }) : wsOptions;
318
366
  const ws = new isomorphic_ws_1.default(url, protocols, finalWsOptions);
319
- ws.onopen = (event) => this.onWsOpen(event, wsKey);
367
+ ws.onopen = (event) => this.onWsOpen(event, wsKey, url, ws);
320
368
  ws.onmessage = (event) => this.onWsMessage(event, wsKey, ws);
321
369
  ws.onerror = (event) => this.parseWsError('Websocket onWsError', event, wsKey);
322
370
  ws.onclose = (event) => this.onWsClose(event, wsKey);
323
371
  ws.wsKey = wsKey;
372
+ if (this.options.useNativeHeartbeats) {
373
+ if (typeof ws.on === 'function') {
374
+ ws.on('ping', (event) => this.onWsPing(event, wsKey, ws, 'frame'));
375
+ ws.on('pong', (event) => this.onWsPong(event, wsKey, 'frame'));
376
+ }
377
+ }
324
378
  return ws;
325
379
  }
326
380
  parseWsError(context, error, wsKey) {
381
+ if (this.wsStore.isConnectionAttemptInProgress(wsKey)) {
382
+ this.setWsState(wsKey, websockets_1.WsConnectionStateEnum.ERROR);
383
+ }
384
+ // Allow retry by default (in some places that call this). Prevent deadloop in hard failure (401)
385
+ let canRetry = true;
327
386
  if (!error.message) {
328
387
  this.logger.error(`${context} due to unexpected error: `, error);
329
388
  this.emit('response', Object.assign(Object.assign({}, error), { wsKey }));
330
389
  this.emit('exception', Object.assign(Object.assign({}, error), { wsKey }));
331
- return;
390
+ return canRetry;
332
391
  }
333
392
  switch (error.message) {
334
393
  case 'Unexpected server response: 401':
335
- this.logger.error(`${context} due to 401 authorization failure.`, Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
394
+ this.logger.error(`${context} due to 401 authorization failure.`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
395
+ canRetry = false;
336
396
  break;
337
397
  default:
338
398
  if (this.wsStore.getConnectionState(wsKey) !==
339
399
  websockets_1.WsConnectionStateEnum.CLOSING) {
340
- this.logger.error(`${context} due to unexpected response error: "${(error === null || error === void 0 ? void 0 : error.msg) || (error === null || error === void 0 ? void 0 : error.message) || error}"`, Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey, error }));
400
+ this.logger.error(`${context} due to unexpected response error: "${(error === null || error === void 0 ? void 0 : error.msg) || (error === null || error === void 0 ? void 0 : error.message) || error}"`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey, error }));
341
401
  this.executeReconnectableClose(wsKey, 'unhandled onWsError');
342
402
  }
343
403
  else {
344
404
  this.logger.info(`${wsKey} socket forcefully closed. Will not reconnect.`);
345
405
  }
406
+ canRetry = true;
346
407
  break;
347
408
  }
348
409
  this.logger.error(`parseWsError(${context}, ${error}, ${wsKey}) `, error);
349
410
  this.emit('response', Object.assign(Object.assign({}, error), { wsKey }));
350
411
  this.emit('exception', Object.assign(Object.assign({}, error), { wsKey }));
412
+ return canRetry;
351
413
  }
352
414
  /** Get a signature, build the auth request and send it */
353
- sendAuthRequest(wsKey) {
415
+ sendAuthRequest(wsKey, eventToAuth) {
354
416
  return __awaiter(this, void 0, void 0, function* () {
355
- var _a;
417
+ var _a, _b;
356
418
  try {
357
- this.logger.trace('Sending auth request...', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
419
+ this.logger.trace('Sending auth request...', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
420
+ eventToAuth }));
358
421
  yield this.assertIsConnected(wsKey);
422
+ // If not required, this won't return anything
423
+ const request = yield this.getWsAuthRequestEvent(wsKey, eventToAuth);
424
+ if (!request) {
425
+ // Short-circuit this for the next time it's called
426
+ const wsState = this.wsStore.get(wsKey, true);
427
+ wsState.isAuthenticated = true;
428
+ return;
429
+ }
359
430
  if (!this.wsStore.getAuthenticationInProgressPromise(wsKey)) {
360
431
  this.wsStore.createAuthenticationInProgressPromise(wsKey, false);
361
432
  }
362
- const request = yield this.getWsAuthRequestEvent(wsKey);
433
+ if (request === 'waitForEvent') {
434
+ return (_a = this.wsStore.getAuthenticationInProgressPromise(wsKey)) === null || _a === void 0 ? void 0 : _a.promise;
435
+ }
363
436
  // console.log('ws auth req', request);
364
- this.tryWsSend(wsKey, JSON.stringify(request));
365
- return (_a = this.wsStore.getAuthenticationInProgressPromise(wsKey)) === null || _a === void 0 ? void 0 : _a.promise;
437
+ this.tryWsSend(wsKey, typeof request === 'string' ? request : JSON.stringify(request));
438
+ return (_b = this.wsStore.getAuthenticationInProgressPromise(wsKey)) === null || _b === void 0 ? void 0 : _b.promise;
366
439
  }
367
440
  catch (e) {
368
- this.logger.trace(e, Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
441
+ this.logger.trace(e, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
369
442
  }
370
443
  });
371
444
  }
@@ -375,14 +448,20 @@ class BaseWebsocketClient extends events_1.default {
375
448
  if (!this.wsStore.isConnectionAttemptInProgress(wsKey)) {
376
449
  this.setWsState(wsKey, websockets_1.WsConnectionStateEnum.RECONNECTING);
377
450
  }
378
- this.logger.info('Reconnecting to websocket with delay...', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
451
+ this.logger.info('Reconnecting to websocket with delay...', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
379
452
  connectionDelayMs }));
380
453
  if ((_a = this.wsStore.get(wsKey)) === null || _a === void 0 ? void 0 : _a.activeReconnectTimer) {
381
454
  this.clearReconnectTimer(wsKey);
382
455
  }
383
456
  this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
384
- this.logger.info('Reconnecting to websocket now', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
385
457
  this.clearReconnectTimer(wsKey);
458
+ // Some streams need a specialist reconnection workflow.
459
+ // E.g. the user data stream can't just be reconnected as is.
460
+ if (this.isCustomReconnectionNeeded(wsKey)) {
461
+ this.wsStore.delete(wsKey);
462
+ return this.triggerCustomReconnectionWorkflow(wsKey);
463
+ }
464
+ this.logger.info('Reconnecting to websocket now', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
386
465
  this.connect(wsKey);
387
466
  }, connectionDelayMs);
388
467
  }
@@ -391,7 +470,7 @@ class BaseWebsocketClient extends events_1.default {
391
470
  return;
392
471
  }
393
472
  this.clearPongTimer(wsKey);
394
- this.logger.trace('Sending ping', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
473
+ this.logger.trace('Sending ping', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
395
474
  const ws = this.wsStore.get(wsKey, true).ws;
396
475
  if (!ws) {
397
476
  this.logger.error(`Unable to send ping for wsKey "${wsKey}" - no connection found`);
@@ -405,7 +484,7 @@ class BaseWebsocketClient extends events_1.default {
405
484
  * If closed, trigger a reconnect immediately
406
485
  */
407
486
  executeReconnectableClose(wsKey, reason) {
408
- this.logger.info(`${reason} - closing socket to reconnect`, Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
487
+ this.logger.info(`${reason} - closing socket to reconnect`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
409
488
  reason }));
410
489
  const wasOpen = this.wsStore.isWsOpen(wsKey);
411
490
  this.clearPingTimer(wsKey);
@@ -416,7 +495,7 @@ class BaseWebsocketClient extends events_1.default {
416
495
  (0, websockets_1.safeTerminateWs)(ws, false);
417
496
  }
418
497
  if (!wasOpen) {
419
- this.logger.info(`${reason} - socket already closed - trigger immediate reconnect`, Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
498
+ this.logger.info(`${reason} - socket already closed - trigger immediate reconnect`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
420
499
  reason }));
421
500
  this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
422
501
  }
@@ -468,6 +547,7 @@ class BaseWebsocketClient extends events_1.default {
468
547
  }
469
548
  // Events that are ready to send (usually stringified JSON)
470
549
  const requestEvents = [];
550
+ // Can be dynamic via this.getWsMarketForWsKey(wsKey) in other SDKs
471
551
  const market = 'all';
472
552
  const maxTopicsPerEvent = this.getMaxTopicsPerSubscribeEvent(wsKey);
473
553
  if (maxTopicsPerEvent &&
@@ -541,7 +621,7 @@ class BaseWebsocketClient extends events_1.default {
541
621
  */
542
622
  tryWsSend(wsKey, wsMessage, throwExceptions) {
543
623
  try {
544
- this.logger.trace('Sending upstream ws message: ', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsMessage,
624
+ this.logger.trace('Sending upstream ws message: ', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsMessage,
545
625
  wsKey }));
546
626
  if (!wsKey) {
547
627
  throw new Error('Cannot send message due to no known websocket for this wsKey');
@@ -553,42 +633,69 @@ class BaseWebsocketClient extends events_1.default {
553
633
  ws.send(wsMessage);
554
634
  }
555
635
  catch (e) {
556
- this.logger.error('Failed to send WS message', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsMessage,
636
+ this.logger.error('Failed to send WS message', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsMessage,
557
637
  wsKey, exception: e }));
558
638
  if (throwExceptions) {
559
639
  throw e;
560
640
  }
561
641
  }
562
642
  }
563
- onWsOpen(event, wsKey) {
643
+ onWsOpen(event, wsKey, url, ws) {
564
644
  return __awaiter(this, void 0, void 0, function* () {
645
+ const isReconnectionAttempt = this.wsStore.isConnectionState(wsKey, websockets_1.WsConnectionStateEnum.RECONNECTING) || this.wsStore.isConnectionState(wsKey, websockets_1.WsConnectionStateEnum.ERROR);
565
646
  const isFreshConnectionAttempt = this.wsStore.isConnectionState(wsKey, websockets_1.WsConnectionStateEnum.CONNECTING);
566
- const isReconnectionAttempt = this.wsStore.isConnectionState(wsKey, websockets_1.WsConnectionStateEnum.RECONNECTING);
567
647
  if (isFreshConnectionAttempt) {
568
- this.logger.info('Websocket connected', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey, testnet: this.options.testnet === true, market: this.options.market }));
569
- this.emit('open', { wsKey, event });
648
+ this.logger.info('Websocket connected', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey, testnet: this.options.testnet === true, market: this.options.market }));
649
+ this.emit('open', { wsKey, event, wsUrl: url, ws });
570
650
  }
571
651
  else if (isReconnectionAttempt) {
572
- this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey, testnet: this.options.testnet === true, market: this.options.market }));
573
- this.emit('reconnected', { wsKey, event });
652
+ this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey, testnet: this.options.testnet === true, market: this.options.market }));
653
+ this.emit('reconnected', { wsKey, event, wsUrl: url, ws });
574
654
  }
575
655
  this.setWsState(wsKey, websockets_1.WsConnectionStateEnum.CONNECTED);
576
- this.logger.trace('Enabled ping timer', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
656
+ this.logger.trace('Enabled ping timer', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
577
657
  this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval);
578
- // Resolve & cleanup deferred "connection attempt in progress" promise
579
- try {
580
- const connectionInProgressPromise = this.wsStore.getConnectionInProgressPromise(wsKey);
581
- if (connectionInProgressPromise === null || connectionInProgressPromise === void 0 ? void 0 : connectionInProgressPromise.resolve) {
582
- connectionInProgressPromise.resolve({
583
- wsKey,
584
- });
585
- }
658
+ if (!this.options.requireConnectionReadyConfirmation) {
659
+ return yield this.onWsReadyForEvents(wsKey);
586
660
  }
587
- catch (e) {
588
- this.logger.error('Exception trying to resolve "connectionInProgress" promise', e);
661
+ else {
662
+ this.resolveConnectionInProgressPromise(wsKey);
589
663
  }
590
664
  // Remove before continuing, in case there's more requests queued
591
665
  this.wsStore.removeConnectingInProgressPromise(wsKey);
666
+ });
667
+ }
668
+ resolveConnectionInProgressPromise(wsKey) {
669
+ try {
670
+ // Resolve & cleanup deferred "connection attempt in progress" promise
671
+ const connectionInProgressPromise = this.wsStore.getConnectionInProgressPromise(wsKey);
672
+ if (connectionInProgressPromise === null || connectionInProgressPromise === void 0 ? void 0 : connectionInProgressPromise.resolve) {
673
+ connectionInProgressPromise.resolve({
674
+ wsKey,
675
+ ws: this.wsStore.getWs(wsKey),
676
+ });
677
+ }
678
+ }
679
+ catch (e) {
680
+ this.logger.error('Exception trying to resolve "connectionInProgress" promise', e);
681
+ }
682
+ }
683
+ /**
684
+ * Called automatically once a connection is ready.
685
+ * - Some exchanges are ready immediately after the connections open.
686
+ * - Some exchanges send an event to confirm the connection is ready for us.
687
+ *
688
+ * This method is called to act when the connection is ready. Use `requireConnectionReadyConfirmation` to control how this is called.
689
+ */
690
+ onWsReadyForEvents(wsKey) {
691
+ return __awaiter(this, void 0, void 0, function* () {
692
+ this.resolveConnectionInProgressPromise(wsKey);
693
+ this.wsStore.removeConnectingInProgressPromise(wsKey);
694
+ // Some websockets require an auth packet to be sent after opening the connection
695
+ if (this.isAuthOnConnectWsKey(wsKey) &&
696
+ this.authPrivateConnectionsOnConnect(wsKey)) {
697
+ yield this.assertIsAuthenticated(wsKey);
698
+ }
592
699
  // Reconnect to topics known before it connected
593
700
  const { privateReqs, publicReqs } = this.sortTopicRequestsIntoPublicPrivate([...this.wsStore.getTopics(wsKey)], wsKey);
594
701
  // Request sub to public topics, if any
@@ -599,8 +706,8 @@ class BaseWebsocketClient extends events_1.default {
599
706
  this.logger.error(`onWsOpen(): exception in public requestSubscribeTopics(${wsKey}): `, publicReqs, e);
600
707
  }
601
708
  // Request sub to private topics, if auth on connect isn't needed
602
- // Else, this is automatic after authentication is successfully confirmed
603
- if (!this.options.authPrivateConnectionsOnConnect) {
709
+ if (!this.authPrivateConnectionsOnConnect(wsKey) ||
710
+ !this.isAuthOnConnectWsKey(wsKey)) {
604
711
  try {
605
712
  this.requestSubscribeTopics(wsKey, privateReqs);
606
713
  }
@@ -608,11 +715,6 @@ class BaseWebsocketClient extends events_1.default {
608
715
  this.logger.error(`onWsOpen(): exception in private requestSubscribeTopics(${wsKey}: `, privateReqs, e);
609
716
  }
610
717
  }
611
- // Some websockets require an auth packet to be sent after opening the connection
612
- if (this.isAuthOnConnectWsKey(wsKey) &&
613
- this.options.authPrivateConnectionsOnConnect) {
614
- yield this.sendAuthRequest(wsKey);
615
- }
616
718
  });
617
719
  }
618
720
  /**
@@ -630,6 +732,7 @@ class BaseWebsocketClient extends events_1.default {
630
732
  inProgressPromise.resolve({
631
733
  wsKey,
632
734
  event,
735
+ ws: wsState.ws,
633
736
  });
634
737
  }
635
738
  }
@@ -638,13 +741,32 @@ class BaseWebsocketClient extends events_1.default {
638
741
  }
639
742
  // Remove before continuing, in case there's more requests queued
640
743
  this.wsStore.removeAuthenticationInProgressPromise(wsKey);
641
- if (this.options.authPrivateConnectionsOnConnect) {
744
+ if (this.authPrivateConnectionsOnConnect(wsKey)) {
642
745
  const topics = [...this.wsStore.getTopics(wsKey)];
643
746
  const privateTopics = topics.filter((topic) => this.isPrivateTopicRequest(topic, wsKey));
644
747
  if (privateTopics.length) {
645
748
  this.subscribeTopicsForWsKey(privateTopics, wsKey);
646
749
  }
647
750
  }
751
+ // Can be passed by the WebsocketClient's integration layer, if necessary, to dynamically track the auth channel
752
+ if (event === null || event === void 0 ? void 0 : event.isWSAPI) {
753
+ wsState.didAuthWSAPI = true;
754
+ if (event === null || event === void 0 ? void 0 : event.WSAPIAuthChannel) {
755
+ wsState.WSAPIAuthChannel = event.WSAPIAuthChannel;
756
+ }
757
+ }
758
+ }
759
+ onWsPing(event, wsKey, ws, source) {
760
+ this.logger.trace(`Received PING ${source}`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
761
+ event,
762
+ source }));
763
+ this.sendPongEvent(wsKey, ws);
764
+ }
765
+ onWsPong(event, wsKey, source) {
766
+ this.logger.trace(`Received PONG ${source}`, Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey, event: event === null || event === void 0 ? void 0 : event.data, source }));
767
+ // Necessary when native heartbeats are used
768
+ this.clearPongTimer(wsKey);
769
+ return;
648
770
  }
649
771
  onWsMessage(event, wsKey, ws) {
650
772
  try {
@@ -652,14 +774,10 @@ class BaseWebsocketClient extends events_1.default {
652
774
  // any message can clear the pong timer - wouldn't get a message if the ws wasn't working
653
775
  this.clearPongTimer(wsKey);
654
776
  if (this.isWsPong(event)) {
655
- this.logger.trace('Received pong', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey, event: event === null || event === void 0 ? void 0 : event.data }));
656
- return;
777
+ return this.onWsPong(event, wsKey, 'event');
657
778
  }
658
779
  if (this.isWsPing(event)) {
659
- this.logger.trace('Received ping', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
660
- event }));
661
- this.sendPongEvent(wsKey, ws);
662
- return;
780
+ return this.onWsPing(event, wsKey, ws, 'event');
663
781
  }
664
782
  if ((0, types_1.isMessageEvent)(event)) {
665
783
  const data = event.data;
@@ -667,20 +785,37 @@ class BaseWebsocketClient extends events_1.default {
667
785
  const emittableEvents = this.resolveEmittableEvents(wsKey, event);
668
786
  if (!emittableEvents.length) {
669
787
  // console.log(`raw event: `, { data, dataType, emittableEvents });
670
- this.logger.error('Unhandled/unrecognised ws event message - returned no emittable data', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { message: data || 'no message', dataType,
788
+ this.logger.error('Unhandled/unrecognised ws event message - returned no emittable data', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { message: data || 'no message', dataType,
671
789
  event,
672
790
  wsKey }));
673
791
  return this.emit('update', Object.assign(Object.assign({}, event), { wsKey }));
674
792
  }
675
793
  for (const emittable of emittableEvents) {
676
- if (this.isWsPong(emittable)) {
677
- this.logger.trace('Received pong2', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
794
+ if (this.isWsPong(emittable) || emittable.eventType === 'pong') {
795
+ this.logger.trace('Received pong2', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
678
796
  data }));
679
797
  continue;
680
798
  }
681
- const emittableFinalEvent = Object.assign(Object.assign({}, emittable.event), { wsKey, isWSAPIResponse: emittable.isWSAPIResponse });
799
+ const emittableFinalEvent = getFinalEmittable(emittable, wsKey, emittable.isWSAPIResponse);
800
+ if (emittable.eventType === 'connectionReady') {
801
+ this.logger.trace('Successfully connected - connection ready for events', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
802
+ emittable }));
803
+ const wsState = this.wsStore.get(wsKey);
804
+ if (wsState && !this.authPrivateConnectionsOnConnect(wsKey)) {
805
+ wsState.isAuthenticated = true;
806
+ }
807
+ this.emit('response', emittableFinalEvent);
808
+ this.onWsReadyForEvents(wsKey);
809
+ continue;
810
+ }
811
+ // Not used for bybit
812
+ if (emittable.eventType === 'connectionReadyForAuth') {
813
+ this.logger.trace('Ready for auth - requesting auth submission...', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
814
+ this.sendAuthRequest(wsKey, emittable.event);
815
+ continue;
816
+ }
682
817
  if (emittable.eventType === 'authenticated') {
683
- this.logger.trace('Successfully authenticated', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey,
818
+ this.logger.trace('Successfully authenticated', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey,
684
819
  emittable }));
685
820
  this.emit(emittable.eventType, emittableFinalEvent);
686
821
  this.onWsAuthenticated(wsKey, emittable.event);
@@ -703,19 +838,21 @@ class BaseWebsocketClient extends events_1.default {
703
838
  }
704
839
  return;
705
840
  }
706
- this.logger.error('Unhandled/unrecognised ws event message - unexpected message format', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { message: event || 'no message', event,
841
+ this.logger.error('Unhandled/unrecognised ws event message - unexpected message format', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { message: event || 'no message', event,
707
842
  wsKey }));
708
843
  }
709
844
  catch (e) {
710
- this.logger.error('Failed to parse ws event message', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { error: e, event,
845
+ this.logger.error('Failed to parse ws event message', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { error: e, event,
711
846
  wsKey }));
712
847
  }
713
848
  }
714
849
  onWsClose(event, wsKey) {
715
- this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, websockets_1.WS_LOGGER_CATEGORY), { wsKey }));
850
+ this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, this.WS_LOGGER_CATEGORY), { wsKey }));
716
851
  const wsState = this.wsStore.get(wsKey, true);
717
852
  wsState.isAuthenticated = false;
718
- if (this.wsStore.getConnectionState(wsKey) !== websockets_1.WsConnectionStateEnum.CLOSING) {
853
+ const wsConnectionState = this.wsStore.getConnectionState(wsKey);
854
+ if (wsConnectionState !== websockets_1.WsConnectionStateEnum.CLOSING) {
855
+ // unintentional close, attempt recovery
719
856
  this.logger.trace(`onWsClose(${wsKey}): rejecting all deferred promises...`);
720
857
  // clean up any pending promises for this connection
721
858
  this.getWsStore().rejectAllDeferredPromises(wsKey, 'connection lost, reconnecting');
@@ -725,6 +862,7 @@ class BaseWebsocketClient extends events_1.default {
725
862
  this.emit('reconnect', { wsKey, event });
726
863
  }
727
864
  else {
865
+ // intentional close - clean up
728
866
  // clean up any pending promises for this connection
729
867
  this.logger.trace(`onWsClose(${wsKey}): rejecting all deferred promises...`);
730
868
  this.getWsStore().rejectAllDeferredPromises(wsKey, 'disconnected');
@@ -746,20 +884,21 @@ class BaseWebsocketClient extends events_1.default {
746
884
  assertIsConnected(wsKey) {
747
885
  return __awaiter(this, void 0, void 0, function* () {
748
886
  const isConnected = this.getWsStore().isConnectionState(wsKey, websockets_1.WsConnectionStateEnum.CONNECTED);
749
- if (!isConnected) {
750
- const inProgressPromise = this.getWsStore().getConnectionInProgressPromise(wsKey);
751
- // Already in progress? Await shared promise and retry
752
- if (inProgressPromise) {
753
- this.logger.trace('assertIsConnected(): awaiting...');
754
- yield inProgressPromise.promise;
755
- this.logger.trace('assertIsConnected(): connected!');
756
- return inProgressPromise.promise;
757
- }
758
- // Start connection, it should automatically store/return a promise.
759
- this.logger.trace('assertIsConnected(): connecting...');
760
- yield this.connect(wsKey);
761
- this.logger.trace('assertIsConnected(): newly connected!');
887
+ if (isConnected) {
888
+ return true;
889
+ }
890
+ const inProgressPromise = this.getWsStore().getConnectionInProgressPromise(wsKey);
891
+ // Already in progress? Await shared promise and retry
892
+ if (inProgressPromise) {
893
+ this.logger.trace('assertIsConnected(): awaiting...');
894
+ yield inProgressPromise.promise;
895
+ this.logger.trace('assertIsConnected(): awaiting...connected!');
896
+ return inProgressPromise.promise;
762
897
  }
898
+ // Start connection, it should automatically store/return a promise.
899
+ this.logger.trace('assertIsConnected(): connecting...');
900
+ yield this.connect(wsKey);
901
+ this.logger.trace('assertIsConnected(): connecting...newly connected!');
763
902
  });
764
903
  }
765
904
  /**