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.
- package/README.md +3 -2
- package/lib/rest-client-v5.d.ts +82 -1
- package/lib/rest-client-v5.js +82 -0
- package/lib/rest-client-v5.js.map +1 -1
- package/lib/types/request/v5-asset.d.ts +11 -0
- package/lib/types/request/v5-broker.d.ts +14 -0
- package/lib/types/request/v5-user.d.ts +11 -1
- package/lib/types/response/v5-asset.d.ts +33 -0
- package/lib/types/response/v5-broker.d.ts +18 -0
- package/lib/types/response/v5-user.d.ts +27 -0
- package/lib/types/websockets/ws-general.d.ts +12 -1
- package/lib/util/BaseWSClient.d.ts +37 -13
- package/lib/util/BaseWSClient.js +225 -86
- package/lib/util/BaseWSClient.js.map +1 -1
- package/lib/util/websockets/WsStore.types.d.ts +1 -0
- package/lib/util/websockets/websocket-util.d.ts +2 -5
- package/lib/util/websockets/websocket-util.js +1 -2
- package/lib/util/websockets/websocket-util.js.map +1 -1
- package/lib/websocket-client.d.ts +19 -8
- package/lib/websocket-client.js +33 -8
- package/lib/websocket-client.js.map +1 -1
- package/package.json +1 -1
package/lib/util/BaseWSClient.js
CHANGED
|
@@ -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
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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
|
-
|
|
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 (
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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
|
-
|
|
579
|
-
|
|
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
|
-
|
|
588
|
-
this.
|
|
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
|
-
|
|
603
|
-
|
|
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.
|
|
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.
|
|
656
|
-
return;
|
|
777
|
+
return this.onWsPong(event, wsKey, 'event');
|
|
657
778
|
}
|
|
658
779
|
if (this.isWsPing(event)) {
|
|
659
|
-
this.
|
|
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({},
|
|
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({},
|
|
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 =
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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({},
|
|
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
|
-
|
|
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 (
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
/**
|