binance 3.0.0-beta.7 → 3.0.0

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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/lib/main-client.d.ts +56 -2
  3. package/lib/main-client.js +44 -1
  4. package/lib/main-client.js.map +1 -1
  5. package/lib/types/futures.d.ts +10 -0
  6. package/lib/types/shared.d.ts +8 -1
  7. package/lib/types/spot.d.ts +48 -3
  8. package/lib/types/websockets/ws-api-requests.d.ts +37 -29
  9. package/lib/types/websockets/ws-api-responses.d.ts +35 -1
  10. package/lib/types/websockets/ws-api.d.ts +11 -9
  11. package/lib/types/websockets/ws-api.js +1 -0
  12. package/lib/types/websockets/ws-api.js.map +1 -1
  13. package/lib/types/websockets/ws-events-formatted.d.ts +14 -12
  14. package/lib/types/websockets/ws-events-raw.d.ts +10 -0
  15. package/lib/types/websockets/ws-general.d.ts +5 -0
  16. package/lib/usdm-client.d.ts +4 -1
  17. package/lib/usdm-client.js +3 -0
  18. package/lib/usdm-client.js.map +1 -1
  19. package/lib/util/BaseRestClient.d.ts +2 -1
  20. package/lib/util/BaseRestClient.js +6 -5
  21. package/lib/util/BaseRestClient.js.map +1 -1
  22. package/lib/util/BaseWSClient.js +28 -5
  23. package/lib/util/BaseWSClient.js.map +1 -1
  24. package/lib/util/beautifier-maps.d.ts +34 -0
  25. package/lib/util/beautifier-maps.js +34 -0
  26. package/lib/util/beautifier-maps.js.map +1 -1
  27. package/lib/util/beautifier.d.ts +1 -0
  28. package/lib/util/beautifier.js +46 -7
  29. package/lib/util/beautifier.js.map +1 -1
  30. package/lib/util/node-support.js +3 -1
  31. package/lib/util/node-support.js.map +1 -1
  32. package/lib/util/requestUtils.d.ts +2 -2
  33. package/lib/util/requestUtils.js +71 -18
  34. package/lib/util/requestUtils.js.map +1 -1
  35. package/lib/util/typeGuards.d.ts +27 -4
  36. package/lib/util/typeGuards.js +103 -41
  37. package/lib/util/typeGuards.js.map +1 -1
  38. package/lib/util/webCryptoAPI.js +0 -2
  39. package/lib/util/webCryptoAPI.js.map +1 -1
  40. package/lib/util/websockets/WsStore.js.map +1 -1
  41. package/lib/util/websockets/user-data-stream-manager.js +2 -2
  42. package/lib/util/websockets/user-data-stream-manager.js.map +1 -1
  43. package/lib/util/websockets/websocket-util.d.ts +16 -2
  44. package/lib/util/websockets/websocket-util.js +92 -42
  45. package/lib/util/websockets/websocket-util.js.map +1 -1
  46. package/lib/websocket-api-client.d.ts +14 -8
  47. package/lib/websocket-api-client.js +39 -24
  48. package/lib/websocket-api-client.js.map +1 -1
  49. package/lib/websocket-client-legacy.d.ts +3 -1
  50. package/lib/websocket-client-legacy.js +6 -4
  51. package/lib/websocket-client-legacy.js.map +1 -1
  52. package/lib/websocket-client.d.ts +48 -14
  53. package/lib/websocket-client.js +312 -154
  54. package/lib/websocket-client.js.map +1 -1
  55. package/package.json +1 -1
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
11
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
24
  };
@@ -44,9 +55,12 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
44
55
  super(options, logger);
45
56
  this.restClientCache = new rest_client_cache_1.RestClientCache();
46
57
  this.beautifier = new beautifier_1.default({
47
- warnKeyMissingInMap: true,
58
+ warnKeyMissingInMap: false,
48
59
  });
49
60
  this.respawnTimeoutCache = {};
61
+ if (options === null || options === void 0 ? void 0 : options.beautifyWarnIfMissing) {
62
+ this.beautifier.setWarnIfMissing(options.beautifyWarnIfMissing);
63
+ }
50
64
  /**
51
65
  * Binance uses native WebSocket ping/pong frames, which cannot be directly used in
52
66
  * some environents (e.g. most browsers do not support sending raw ping/pong frames).
@@ -57,7 +71,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
57
71
  * be found here: https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser
58
72
  */
59
73
  if (!(0, websocket_util_1.isWSPingFrameAvailable)()) {
60
- this.logger.trace('Disabled WS heartbeats. WS.ping() is not available in your environment.');
74
+ this.logger.info('Disabled WS heartbeats. WS.ping() is not available in your environment.');
61
75
  this.options.disableHeartbeat = true;
62
76
  }
63
77
  this.userDataStreamManager = new user_data_stream_manager_1.UserDataStreamManager({
@@ -65,8 +79,12 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
65
79
  wsStore: this.getWsStore(),
66
80
  restClientCache: this.restClientCache,
67
81
  // fn pointers:
68
- respawnUserDataFn: (wsKey, market, context = {}) => this.respawnUserDataStream(wsKey, market, context),
69
- getWsUrlFn: (wsKey, connectionType) => this.getWsUrl(wsKey, connectionType),
82
+ respawnUserDataFn: (wsKey, market, context = {}) => {
83
+ return this.respawnUserDataStream(wsKey, market, context);
84
+ },
85
+ getWsUrlFn: (wsKey, connectionType) => {
86
+ return this.getWsUrl(wsKey, connectionType);
87
+ },
70
88
  getRestClientOptionsFn: () => this.getRestClientOptions(),
71
89
  getWsClientOptionsfn: () => this.options,
72
90
  closeWsFn: (wsKey, force) => this.close(wsKey, force),
@@ -114,7 +132,10 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
114
132
  * You do not need to call this, but if you call this before making any WS API requests,
115
133
  * it can accelerate the first request (by preparing the connection in advance).
116
134
  */
117
- connectWSAPI(wsKey) {
135
+ connectWSAPI(wsKey, skipAuth) {
136
+ if (skipAuth) {
137
+ return this.assertIsConnected(wsKey);
138
+ }
118
139
  /** This call automatically ensures the connection is active AND authenticated before resolving */
119
140
  return this.assertIsAuthenticated(wsKey);
120
141
  }
@@ -146,27 +167,48 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
146
167
  const normalisedTopicRequests = (0, websocket_util_1.getNormalisedTopicRequests)(topicRequests);
147
168
  return this.unsubscribeTopicsForWsKey(normalisedTopicRequests, wsKey);
148
169
  }
149
- sendWSAPIRequest(wsKey, operation, params) {
170
+ sendWSAPIRequest(wsKey, operation, params, requestFlags) {
150
171
  return __awaiter(this, void 0, void 0, function* () {
151
172
  /**
152
- * Spot: https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/general-api-information
173
+ * Spot:
174
+ * -> https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/general-api-information
175
+ * -> https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-api.md#public-api-requests
153
176
  * USDM Futures: https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-api-general-info
154
177
  * COINM Futures: https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-api-general-info
155
178
  */
156
- var _a;
179
+ var _a, _b;
157
180
  // If testnet, enforce testnet wskey for WS API calls
158
181
  const resolvedWsKey = this.options.testnet ? (0, websocket_util_1.getTestnetWsKey)(wsKey) : wsKey;
159
182
  // this.logger.trace(`sendWSAPIRequest(): assertIsConnected("${wsKey}")...`);
183
+ const timestampBeforeAuth = Date.now();
160
184
  yield this.assertIsConnected(resolvedWsKey);
161
185
  // this.logger.trace('sendWSAPIRequest(): assertIsConnected(${wsKey}) ok');
162
- // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey})...');
163
- yield this.assertIsAuthenticated(resolvedWsKey);
164
- // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey}) ok');
186
+ // Some commands don't require authentication.
187
+ if ((requestFlags === null || requestFlags === void 0 ? void 0 : requestFlags.authIsOptional) !== true) {
188
+ // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey})...');
189
+ yield this.assertIsAuthenticated(resolvedWsKey);
190
+ // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey}) ok');
191
+ }
192
+ const timestampAfterAuth = Date.now();
165
193
  const request = {
166
194
  id: this.getNewRequestId(),
167
195
  method: operation,
168
196
  params: Object.assign({}, params),
169
197
  };
198
+ /**
199
+ * Some WS API requests require a timestamp to be included. assertIsConnected and assertIsAuthenticated
200
+ * can introduce a small delay before the actual request is sent, if not connected before that request is
201
+ * made. This can lead to a curious race condition, where the request timestamp is before
202
+ * the "authorizedSince" timestamp - as such, binance does not recognise the session as already authenticated.
203
+ *
204
+ * The below mechanism measures any delay introduced from the assert calls, and if the request includes a timestamp,
205
+ * it offsets that timestamp by the delay.
206
+ */
207
+ const delayFromAuthAssert = timestampAfterAuth - timestampBeforeAuth;
208
+ if (delayFromAuthAssert && ((_a = request.params) === null || _a === void 0 ? void 0 : _a.timestamp)) {
209
+ request.params.timestamp += delayFromAuthAssert;
210
+ this.logger.trace(`sendWSAPIRequest(): adjust timestamp - delay seen by connect/auth assert and delayed request includes timestamp, adjusting timestamp by ${delayFromAuthAssert}ms`);
211
+ }
170
212
  if ((0, requestUtils_1.requiresWSAPINewClientOID)(request, resolvedWsKey)) {
171
213
  (0, requestUtils_1.validateWSAPINewClientOID)(request, resolvedWsKey);
172
214
  }
@@ -175,17 +217,24 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
175
217
  // Store deferred promise, resolved within the "resolveEmittableEvents" method while parsing incoming events
176
218
  const promiseRef = (0, websocket_util_1.getPromiseRefForWSAPIRequest)(resolvedWsKey, signedEvent);
177
219
  const deferredPromise = this.getWsStore().createDeferredPromise(resolvedWsKey, promiseRef, false);
178
- (_a = deferredPromise.promise) === null || _a === void 0 ? void 0 : _a.catch((e) => {
220
+ // Enrich returned promise with request context for easier debugging
221
+ (_b = deferredPromise.promise) === null || _b === void 0 ? void 0 : _b.then((res) => {
222
+ if (!Array.isArray(res)) {
223
+ res.request = Object.assign({ wsKey: resolvedWsKey }, signedEvent);
224
+ }
225
+ return res;
226
+ }).catch((e) => {
179
227
  if (typeof e === 'string') {
180
228
  this.logger.error('unexpcted string', { e });
181
- throw e;
229
+ return e;
182
230
  }
183
231
  e.request = {
184
232
  wsKey: resolvedWsKey,
185
233
  operation,
186
- params,
234
+ params: signedEvent.params,
187
235
  };
188
- throw e;
236
+ // throw e;
237
+ return e;
189
238
  });
190
239
  // this.logger.trace(
191
240
  // `sendWSAPIRequest(): sending raw request: ${JSON.stringify(signedEvent)} with promiseRef(${promiseRef})`,
@@ -226,8 +275,26 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
226
275
  }
227
276
  signWSAPIRequest(requestEvent) {
228
277
  return __awaiter(this, void 0, void 0, function* () {
229
- // Not needed for Binance. Auth happens only on connection open, automatically.
230
- // Faster than performing auth for every request
278
+ if (!requestEvent.params) {
279
+ return requestEvent;
280
+ }
281
+ /**
282
+ * Not really needed for most commands on Binance. Their WS API supports
283
+ * sending signed WS API commands, but that adds latency to every request.
284
+ *
285
+ * Instead, this SDK will authenticate once after connecting. All requests
286
+ * after authentication can then be sent without sign for maximum speed.
287
+ */
288
+ const _a = requestEvent.params, { signRequest } = _a, otherParams = __rest(_a, ["signRequest"]);
289
+ if (signRequest) {
290
+ const strictParamValidation = true;
291
+ const encodeValues = true;
292
+ const filterUndefinedParams = true;
293
+ const semiFinalRequestParams = Object.assign({ apiKey: this.options.api_key }, otherParams);
294
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(semiFinalRequestParams, strictParamValidation, encodeValues, filterUndefinedParams);
295
+ const signature = yield this.signMessage(serialisedParams, this.options.api_secret, 'base64', 'SHA-256');
296
+ return Object.assign(Object.assign({}, requestEvent), { params: Object.assign(Object.assign({}, semiFinalRequestParams), { signature }) });
297
+ }
231
298
  return requestEvent;
232
299
  });
233
300
  }
@@ -238,21 +305,23 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
238
305
  // If you do not want to specify apiKey and signature in each individual request, you can authenticate your API key for the active WebSocket session.
239
306
  // Once authenticated, you no longer have to specify apiKey and signature for those requests that need them. Requests will be performed on behalf of the account owning the authenticated API key.
240
307
  // Note: You still have to specify the timestamp parameter for SIGNED requests.
241
- const recvWindow = this.options.recvWindow || 0;
242
- const timestamp = Date.now() + (this.getTimeOffsetMs() || 0) + recvWindow;
308
+ const timestamp = Date.now() + (this.getTimeOffsetMs() || 0);
243
309
  const strictParamValidation = true;
244
310
  const encodeValues = true;
245
311
  const filterUndefinedParams = true;
246
- const authParams = {
312
+ if (!this.options.api_key || !this.options.api_secret) {
313
+ throw new Error('API key and/or secret missing, unable to prepare WS auth event without valid API keys.');
314
+ }
315
+ const params = {
247
316
  apiKey: this.options.api_key,
248
317
  timestamp,
249
318
  };
250
- const serialisedParams = (0, requestUtils_1.serialiseParams)(authParams, strictParamValidation, encodeValues, filterUndefinedParams);
319
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(params, strictParamValidation, encodeValues, filterUndefinedParams);
251
320
  const signature = yield this.signMessage(serialisedParams, this.options.api_secret, 'base64', 'SHA-256');
252
321
  const request = {
253
322
  id: this.getNewRequestId(),
254
323
  method: 'session.logon',
255
- params: Object.assign(Object.assign({}, authParams), { signature }),
324
+ params: Object.assign(Object.assign({}, params), { signature }),
256
325
  };
257
326
  return request;
258
327
  }
@@ -390,26 +459,71 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
390
459
  * Abstraction called to sort ws events into emittable event types (response to a request, data update, etc)
391
460
  */
392
461
  resolveEmittableEvents(wsKey, event) {
393
- var _a, _b, _c;
462
+ var _a, _b;
463
+ // this.logger.trace(`resolveEmittableEvents(${wsKey}): `, event?.data);
394
464
  const results = [];
395
465
  try {
396
- // const parsed = JSON.parse(event.data);
397
- const parsed = (0, websocket_util_1.parseRawWsMessage)(event);
398
- const eventType = (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, parsed);
466
+ /**
467
+ *
468
+ * Extract event from JSON
469
+ *
470
+ */
471
+ const parsedEvent = (0, websocket_util_1.parseRawWsMessage)(event);
472
+ /**
473
+ *
474
+ * Minor data normalisation & preparation
475
+ *
476
+ */
477
+ // ws consumers: { data: ... }
478
+ // ws api consumers (user data): { event: ... }
479
+ // other responses: { ... }
480
+ const eventData = (parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.data) || (parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.event) || parsedEvent;
481
+ const parsedEventId = parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.id;
482
+ const parsedEventErrorCode = (_a = parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.error) === null || _a === void 0 ? void 0 : _a.code;
483
+ const streamName = parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.stream;
484
+ const eventType =
485
+ // First try, the child node
486
+ (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, eventData) ||
487
+ // Second try, the parent
488
+ (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, parsedEvent);
399
489
  // Some events don't include the topic (event name)
400
490
  // This tries to extract and append it, using available context
401
- (0, requestUtils_1.appendEventIfMissing)(parsed, wsKey);
491
+ (0, requestUtils_1.appendEventIfMissing)(eventData, wsKey, eventType);
402
492
  const legacyContext = (0, websocket_util_1.getLegacyWsKeyContext)(wsKey);
403
- if (legacyContext) {
404
- parsed.wsMarket = legacyContext.market;
493
+ const wsMarket = legacyContext
494
+ ? legacyContext.market
495
+ : (0, websocket_util_1.resolveUserDataMarketForWsKey)(wsKey);
496
+ // This attaches `wsMarket` and `streamName` to incoming events
497
+ // If the event is an array, it's attached to each element in the array
498
+ if (Array.isArray(eventData)) {
499
+ for (const row of eventData) {
500
+ row.wsMarket = wsMarket;
501
+ if (streamName && !row.streamName) {
502
+ row.streamName = streamName;
503
+ }
504
+ }
505
+ }
506
+ else {
507
+ eventData.wsMarket = wsMarket;
508
+ if (streamName && !eventData.streamName) {
509
+ eventData.streamName = streamName;
510
+ }
405
511
  }
512
+ /**
513
+ *
514
+ *
515
+ * Main parsing logic below:
516
+ *
517
+ *
518
+ */
406
519
  const traceEmittable = false;
407
520
  if (traceEmittable) {
408
- this.logger.trace('resolveEmittableEvents', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey,
409
- eventType, parsed: JSON.stringify(parsed) }));
521
+ this.logger.trace('resolveEmittableEvents', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, parsedEvent: JSON.stringify(parsedEvent), parsedEventData: JSON.stringify(eventData), eventType, properties: {
522
+ parsedEventId,
523
+ parsedEventErrorCode,
524
+ } }));
410
525
  }
411
- const reqId = parsed.id;
412
- const isWSAPIResponse = typeof parsed.id === 'number';
526
+ const isWSAPIResponse = typeof parsedEventId === 'number';
413
527
  const EVENTS_AUTHENTICATED = ['auth'];
414
528
  const EVENTS_RESPONSES = [
415
529
  'subscribe',
@@ -454,65 +568,66 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
454
568
  isWSAPIResponse: true
455
569
  }
456
570
  */
457
- const isError = typeof ((_a = parsed.error) === null || _a === void 0 ? void 0 : _a.code) === 'number' && ((_b = parsed.error) === null || _b === void 0 ? void 0 : _b.code) !== 0;
571
+ const isError = typeof parsedEventErrorCode === 'number' &&
572
+ parsedEventErrorCode !== 0;
458
573
  // This is the counterpart to getPromiseRefForWSAPIRequest
459
- const promiseRef = [wsKey, reqId].join('_');
460
- if (!reqId) {
574
+ const promiseRef = [wsKey, parsedEventId].join('_');
575
+ if (!parsedEventId) {
461
576
  this.logger.error('WS API response is missing reqId - promisified workflow could get stuck. If this happens, please get in touch with steps to reproduce. Trace:', {
462
577
  wsKey,
463
578
  promiseRef,
464
- parsedEvent: parsed,
579
+ parsedEvent: eventData,
465
580
  });
466
581
  }
467
582
  // WS API Exception
468
583
  if (isError) {
469
584
  try {
470
- this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
585
+ this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, eventData), true);
471
586
  }
472
587
  catch (e) {
473
588
  this.logger.error('Exception trying to reject WSAPI promise', {
474
589
  wsKey,
475
590
  promiseRef,
476
- parsedEvent: parsed,
591
+ parsedEvent: eventData,
477
592
  e,
478
593
  });
479
594
  }
480
595
  results.push({
481
596
  eventType: 'exception',
482
- event: parsed,
597
+ event: eventData,
483
598
  isWSAPIResponse: isWSAPIResponse,
484
599
  });
485
600
  return results;
486
601
  }
487
602
  // authenticated
488
- if ((_c = parsed.result) === null || _c === void 0 ? void 0 : _c.apiKey) {
603
+ if ((_b = eventData.result) === null || _b === void 0 ? void 0 : _b.apiKey) {
489
604
  // Note: Without this check, this will also trigger "onWsAuthenticated()" for session.status requests
490
605
  if (this.getWsStore().getAuthenticationInProgressPromise(wsKey)) {
491
606
  results.push({
492
607
  eventType: 'authenticated',
493
- event: parsed,
608
+ event: eventData,
494
609
  isWSAPIResponse: isWSAPIResponse,
495
610
  });
496
611
  }
497
612
  }
498
613
  // WS API Success
499
614
  try {
500
- this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
615
+ this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, eventData), true);
501
616
  }
502
617
  catch (e) {
503
618
  this.logger.error('Exception trying to resolve WSAPI promise', {
504
619
  wsKey,
505
620
  promiseRef,
506
- parsedEvent: parsed,
621
+ parsedEvent: eventData,
507
622
  e,
508
623
  });
509
624
  }
510
625
  results.push({
511
626
  eventType: 'response',
512
- event: Object.assign(Object.assign({}, parsed), { request: this.getCachedMidFlightRequest(wsKey, reqId) }),
627
+ event: Object.assign(Object.assign({}, eventData), { request: this.getCachedMidFlightRequest(wsKey, `${parsedEventId}`) }),
513
628
  isWSAPIResponse: isWSAPIResponse,
514
629
  });
515
- this.removeCachedMidFlightRequest(wsKey, reqId);
630
+ this.removeCachedMidFlightRequest(wsKey, `${parsedEventId}`);
516
631
  return results;
517
632
  }
518
633
  // Handle incoming event that listen key expired
@@ -522,14 +637,14 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
522
637
  // handle this how?
523
638
  throw new Error('No context found within wsKey - fatal error with expired listen key');
524
639
  }
525
- this.logger.info(`${legacyContext.market} listenKey EXPIRED - attempting to respawn user data stream: ${wsKey}`, parsed);
640
+ this.logger.info(`${legacyContext.market} listenKey EXPIRED - attempting to respawn user data stream: ${wsKey}`, eventData);
526
641
  // Just closing the connection (with the last parameter as true) will handle cleanup and respawn
527
642
  // Automatically leads to triggerCustomReconnectionWorkflow() to handle fresh user data respawn
528
643
  this.getUserDataStreamManager().teardownUserDataListenKey(legacyContext.listenKey, this.getWsStore().getWs(wsKey));
529
644
  this.executeReconnectableClose(wsKey, 'listenKeyExpired');
530
645
  }
531
646
  if (this.options.beautify) {
532
- const beautifiedMessage = this.beautifier.beautifyWsMessage(parsed, eventType, false,
647
+ const beautifiedMessage = this.beautifier.beautifyWsMessage(eventData, eventType, false,
533
648
  // Suffix all events for the beautifier, if market is options
534
649
  // Options has some conflicting keys with different intentions, so will be suffixed
535
650
  wsKey === 'eoptions' ? 'Options' : '');
@@ -540,19 +655,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
540
655
  });
541
656
  // emit an additional event for user data messages
542
657
  if (!Array.isArray(beautifiedMessage) && eventType) {
543
- if ([
544
- 'balanceUpdate',
545
- 'executionReport',
546
- 'listStatus',
547
- 'listenKeyExpired',
548
- 'outboundAccountPosition',
549
- 'ACCOUNT_CONFIG_UPDATE',
550
- 'ACCOUNT_UPDATE',
551
- 'MARGIN_CALL',
552
- 'ORDER_TRADE_UPDATE',
553
- 'TRADE_LITE',
554
- 'CONDITIONAL_ORDER_TRIGGER_REJECT',
555
- ].includes(eventType)) {
658
+ if (websocket_util_1.EVENT_TYPES_USER_DATA.includes(eventType)) {
556
659
  results.push({
557
660
  eventType: 'formattedUserDataMessage',
558
661
  event: beautifiedMessage,
@@ -565,25 +668,25 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
565
668
  if (typeof eventType === 'string') {
566
669
  results.push({
567
670
  eventType: 'message',
568
- event: (parsed === null || parsed === void 0 ? void 0 : parsed.data) ? parsed.data : parsed,
671
+ event: (eventData === null || eventData === void 0 ? void 0 : eventData.data) ? eventData.data : eventData,
569
672
  });
570
673
  return results;
571
674
  }
572
675
  // Messages that are a "reply" to a request/command (e.g. subscribe to these topics) typically include the "op" property
573
676
  if (typeof eventType === 'string') {
574
677
  // Failed request
575
- if (parsed.success === false) {
678
+ if (eventData.success === false) {
576
679
  results.push({
577
680
  eventType: 'exception',
578
- event: parsed,
681
+ event: eventData,
579
682
  });
580
683
  return results;
581
684
  }
582
- // These are r equest/reply pattern events (e.g. after subscribing to topics or authenticating)
685
+ // These are request/reply pattern events (e.g. after subscribing to topics or authenticating)
583
686
  if (EVENTS_RESPONSES.includes(eventType)) {
584
687
  results.push({
585
688
  eventType: 'response',
586
- event: parsed,
689
+ event: eventData,
587
690
  });
588
691
  return results;
589
692
  }
@@ -591,19 +694,19 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
591
694
  if (EVENTS_AUTHENTICATED.includes(eventType)) {
592
695
  results.push({
593
696
  eventType: 'authenticated',
594
- event: parsed,
697
+ event: eventData,
595
698
  });
596
699
  return results;
597
700
  }
598
- this.logger.error(`!! Unhandled string operation type "${eventType}". Defaulting to "update" channel...`, parsed);
701
+ this.logger.error(`!! Unhandled string operation type "${eventType}". Defaulting to "update" channel... raw event:`, JSON.stringify(parsedEvent));
599
702
  }
600
703
  else {
601
- this.logger.error(`!!!! Unhandled non-string event type "${eventType}". Defaulting to "update" channel...`, parsed);
704
+ this.logger.error(`!!!! Unhandled non-string event type "${eventType}". Defaulting to "update" channel... raw event:`, JSON.stringify(parsedEvent));
602
705
  }
603
706
  // In case of catastrophic failure, fallback to noisy emit update
604
707
  results.push({
605
708
  eventType: 'message',
606
- event: parsed,
709
+ event: eventData,
607
710
  });
608
711
  }
609
712
  catch (e) {
@@ -650,7 +753,9 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
650
753
  });
651
754
  }
652
755
  /**
653
- * Subscribe to spot user data stream - listen key is automaticallyr generated. Calling multiple times only opens one connection.
756
+ * Subscribe to spot user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
757
+ *
758
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group (e.g. port 9443 (main) vs 443 (main2))
654
759
  */
655
760
  subscribeSpotUserDataStream() {
656
761
  return __awaiter(this, arguments, void 0, function* (wsKey = 'main', forceNewConnection, miscState) {
@@ -672,8 +777,13 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
672
777
  }
673
778
  });
674
779
  }
780
+ unsubscribeSpotUserDataStream(wsKey = 'main') {
781
+ return this.closeUserDataStream(wsKey, 'spot');
782
+ }
675
783
  /**
676
- * Subscribe to margin user data stream - listen key is automatically generated.
784
+ * Subscribe to margin user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
785
+ *
786
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group (e.g. port 9443 (main) vs 443 (main2))
677
787
  */
678
788
  subscribeCrossMarginUserDataStream() {
679
789
  return __awaiter(this, arguments, void 0, function* (wsKey = 'main', forceNewConnection, miscState) {
@@ -691,8 +801,13 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
691
801
  }
692
802
  });
693
803
  }
804
+ unsubscribeCrossMarginUserDataStream(wsKey = 'main') {
805
+ return this.closeUserDataStream(wsKey, 'crossMargin');
806
+ }
694
807
  /**
695
- * Subscribe to isolated margin user data stream - listen key is automatically generated.
808
+ * Subscribe to isolated margin user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
809
+ *
810
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group (e.g. port 9443 (main) vs 443 (main2))
696
811
  */
697
812
  subscribeIsolatedMarginUserDataStream(symbol_1) {
698
813
  return __awaiter(this, arguments, void 0, function* (symbol, wsKey = 'main', forceNewConnection, miscState) {
@@ -704,17 +819,27 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
704
819
  symbol: lowerCaseSymbol,
705
820
  });
706
821
  const market = 'isolatedMargin';
707
- return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
822
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, Object.assign(Object.assign({}, miscState), { symbol }));
708
823
  }
709
824
  catch (e) {
710
825
  this.logger.error('Failed to connect to isolated margin user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e, symbol }));
711
- this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeIsolatedMarginUserDataStream()', wsKey,
712
- forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
826
+ this.emit('exception', {
827
+ functionRef: 'subscribeIsolatedMarginUserDataStream()',
828
+ wsKey,
829
+ forceNewConnection,
830
+ miscState: Object.assign(Object.assign({}, miscState), { symbol }),
831
+ error: (e === null || e === void 0 ? void 0 : e.stack) || e,
832
+ });
713
833
  }
714
834
  });
715
835
  }
836
+ unsubscribeIsolatedMarginUserDataStream(symbol, wsKey = 'main') {
837
+ return this.closeUserDataStream(wsKey, 'isolatedMargin', symbol);
838
+ }
716
839
  /**
717
- * Subscribe to margin risk user data stream - listen key is automatically generated.
840
+ * Subscribe to margin risk user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
841
+ *
842
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group (e.g. port 9443 (main) vs 443 (main2))
718
843
  */
719
844
  subscribeMarginRiskUserDataStream() {
720
845
  return __awaiter(this, arguments, void 0, function* (wsKey = 'main', forceNewConnection, miscState) {
@@ -732,6 +857,116 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
732
857
  }
733
858
  });
734
859
  }
860
+ unsubscribeMarginRiskUserDataStream(wsKey = 'main') {
861
+ return this.closeUserDataStream(wsKey, 'riskDataMargin');
862
+ }
863
+ /**
864
+ * --------------------------
865
+ * End of SPOT market websocket streams
866
+ * --------------------------
867
+ **/
868
+ /**
869
+ * Subscribe to USD-M Futures user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
870
+ *
871
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group.
872
+ */
873
+ subscribeUsdFuturesUserDataStream() {
874
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'usdm', // usdm | usdmTestnet
875
+ forceNewConnection, miscState) {
876
+ try {
877
+ const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.usdmTestnet;
878
+ const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions);
879
+ const { listenKey } = yield restClient.getFuturesUserDataListenKey();
880
+ const market = isTestnet ? 'usdmTestnet' : 'usdm';
881
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
882
+ }
883
+ catch (e) {
884
+ this.logger.error('Failed to connect to USD Futures user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
885
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeUsdFuturesUserDataStream()', wsKey,
886
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
887
+ }
888
+ });
889
+ }
890
+ unsubscribeUsdFuturesUserDataStream(wsKey = 'usdm') {
891
+ return this.closeUserDataStream(wsKey, 'usdm');
892
+ }
893
+ /**
894
+ * Subscribe to COIN-M Futures user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
895
+ *
896
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group.
897
+ */
898
+ subscribeCoinFuturesUserDataStream() {
899
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'coinm', // coinm | coinmTestnet
900
+ forceNewConnection, miscState) {
901
+ try {
902
+ const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.coinmTestnet;
903
+ const { listenKey } = yield this.restClientCache
904
+ .getCOINMRestClient(this.getRestClientOptions(), this.options.requestOptions)
905
+ .getFuturesUserDataListenKey();
906
+ const market = isTestnet ? 'coinmTestnet' : 'coinm';
907
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
908
+ }
909
+ catch (e) {
910
+ this.logger.error('Failed to connect to COIN Futures user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
911
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeCoinFuturesUserDataStream()', wsKey,
912
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
913
+ }
914
+ });
915
+ }
916
+ unsubscribeCoinFuturesUserDataStream(wsKey = 'coinm') {
917
+ return this.closeUserDataStream(wsKey, 'coinm');
918
+ }
919
+ /**
920
+ * Subscribe to Portfolio Margin user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
921
+ *
922
+ * Note: the wsKey parameter is optional, but can be used to connect to other environments for this product group.
923
+ */
924
+ subscribePortfolioMarginUserDataStream() {
925
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'portfolioMarginUserData', forceNewConnection, miscState) {
926
+ try {
927
+ const { listenKey } = yield this.restClientCache
928
+ .getPortfolioClient(this.getRestClientOptions(), this.options.requestOptions)
929
+ .getPMUserDataListenKey();
930
+ const market = 'portfoliom';
931
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
932
+ }
933
+ catch (e) {
934
+ this.logger.error('Failed to connect to Portfolio Margin user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
935
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribePortfolioMarginUserDataStream()', wsKey,
936
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
937
+ }
938
+ });
939
+ }
940
+ unsubscribePortfolioMarginUserDataStream(wsKey = 'portfolioMarginUserData') {
941
+ return this.closeUserDataStream(wsKey, 'portfoliom');
942
+ }
943
+ /**
944
+ * Close an active, dedicated, user data stream connection.
945
+ *
946
+ * @param wsKey - the connection key used to open the connection (excluding any automatic parameters such as the listen key). E.g. 'main' for spot/margin, 'usdm' for futures.
947
+ * @param wsMarket - the product group, recommended if you're subscribed to both spot and margin (since they're on the same wsKey (main)).
948
+ */
949
+ closeUserDataStream(wsKey, wsMarket, symbol) {
950
+ return __awaiter(this, void 0, void 0, function* () {
951
+ const wsKeys = this.getWsStore().getKeys();
952
+ const userDataWsKey = wsKeys.find((key) => {
953
+ if (key === wsKey) {
954
+ return true;
955
+ }
956
+ // built around the assumption in how per-connection listen key wskeys are created
957
+ // isolatedMargin_userData_BTCUSDC_6RszN123x213x1233x213x1233x213xx123x1uzkTV_main
958
+ // coinm_userData__WRAVTxGaQa1Nhd1243312kjn13kj12n3m5wRFv6JoFQgwUR5AEFofZtlk_coinm
959
+ const symbolSuffix = symbol ? '_' + symbol : '';
960
+ const prefixMatch = wsMarket + '_userData' + symbolSuffix;
961
+ return key.startsWith(prefixMatch) && key.endsWith(wsKey);
962
+ });
963
+ if (!userDataWsKey) {
964
+ throw new Error(`No matching connection found with wsKey "${wsKey}". Active connections: ${JSON.stringify(wsKeys)}`);
965
+ }
966
+ // todo: close?
967
+ this.close(userDataWsKey);
968
+ });
969
+ }
735
970
  isCustomReconnectionNeeded(wsKey) {
736
971
  return wsKey.includes('userData');
737
972
  }
@@ -824,83 +1059,6 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
824
1059
  }
825
1060
  });
826
1061
  }
827
- /**
828
- * --------------------------
829
- * End of SPOT market websocket streams
830
- * --------------------------
831
- **/
832
- /**
833
- * Subscribe to USD-M Futures user data stream - listen key is automatically generated.
834
- */
835
- subscribeUsdFuturesUserDataStream() {
836
- return __awaiter(this, arguments, void 0, function* (wsKey = 'usdm', // usdm | usdmTestnet
837
- forceNewConnection, miscState) {
838
- try {
839
- const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.usdmTestnet;
840
- const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions);
841
- const { listenKey } = yield restClient.getFuturesUserDataListenKey();
842
- const market = isTestnet ? 'usdmTestnet' : 'usdm';
843
- return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
844
- }
845
- catch (e) {
846
- this.logger.error('Failed to connect to USD Futures user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
847
- this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeUsdFuturesUserDataStream()', wsKey,
848
- forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
849
- }
850
- });
851
- }
852
- closeUserDataStream() {
853
- return __awaiter(this, arguments, void 0, function* (wsKey = 'usdm') {
854
- const wsKeys = this.getWsStore().getKeys();
855
- const userDataWsKey = wsKeys.find((key) => {
856
- return key === wsKey || key.startsWith(wsKey + '_userData');
857
- });
858
- if (!userDataWsKey) {
859
- throw new Error(`No matching connection found with wsKey "${wsKey}". Active connections: [${JSON.stringify(wsKey)}]`);
860
- }
861
- this.close(userDataWsKey);
862
- });
863
- }
864
- /**
865
- * Subscribe to COIN-M Futures user data stream - listen key is automatically generated.
866
- */
867
- subscribeCoinFuturesUserDataStream() {
868
- return __awaiter(this, arguments, void 0, function* (wsKey = 'coinm', // coinm | coinmTestnet
869
- forceNewConnection, miscState) {
870
- try {
871
- const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.coinmTestnet;
872
- const { listenKey } = yield this.restClientCache
873
- .getCOINMRestClient(this.getRestClientOptions(), this.options.requestOptions)
874
- .getFuturesUserDataListenKey();
875
- const market = isTestnet ? 'coinmTestnet' : 'coinm';
876
- return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
877
- }
878
- catch (e) {
879
- this.logger.error('Failed to connect to COIN Futures user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
880
- this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeCoinFuturesUserDataStream()', wsKey,
881
- forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
882
- }
883
- });
884
- }
885
- /**
886
- * Subscribe to Portfolio Margin user data stream - listen key is automatically generated.
887
- */
888
- subscribePortfolioMarginUserDataStream() {
889
- return __awaiter(this, arguments, void 0, function* (wsKey = 'portfolioMarginUserData', forceNewConnection, miscState) {
890
- try {
891
- const { listenKey } = yield this.restClientCache
892
- .getPortfolioClient(this.getRestClientOptions(), this.options.requestOptions)
893
- .getPMUserDataListenKey();
894
- const market = 'portfoliom';
895
- return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
896
- }
897
- catch (e) {
898
- this.logger.error('Failed to connect to Portfolio Margin user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
899
- this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribePortfolioMarginUserDataStream()', wsKey,
900
- forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
901
- }
902
- });
903
- }
904
1062
  /**
905
1063
  * Subscribe to the European Options user data stream - listen key is automatically generated.
906
1064
  *