binance 3.0.0-beta.0 → 3.0.0-beta.10

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 (80) hide show
  1. package/README.md +305 -75
  2. package/index.js +1 -1
  3. package/lib/coinm-client.d.ts +40 -22
  4. package/lib/coinm-client.js +4 -16
  5. package/lib/coinm-client.js.map +1 -1
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +2 -0
  8. package/lib/index.js.map +1 -1
  9. package/lib/main-client.d.ts +94 -30
  10. package/lib/main-client.js +68 -36
  11. package/lib/main-client.js.map +1 -1
  12. package/lib/portfolio-client.js.map +1 -1
  13. package/lib/types/coin.d.ts +1 -0
  14. package/lib/types/futures.d.ts +15 -4
  15. package/lib/types/futures.js.map +1 -1
  16. package/lib/types/portfolio-margin.d.ts +0 -9
  17. package/lib/types/shared.d.ts +14 -13
  18. package/lib/types/spot.d.ts +174 -11
  19. package/lib/types/spot.js.map +1 -1
  20. package/lib/types/websockets/ws-api-requests.d.ts +480 -2
  21. package/lib/types/websockets/ws-api-responses.d.ts +565 -1
  22. package/lib/types/websockets/ws-api.d.ts +157 -60
  23. package/lib/types/websockets/ws-api.js +47 -3
  24. package/lib/types/websockets/ws-api.js.map +1 -1
  25. package/lib/types/websockets/ws-events-formatted.d.ts +18 -14
  26. package/lib/types/websockets/ws-events-raw.d.ts +4 -2
  27. package/lib/types/websockets/ws-general.d.ts +8 -18
  28. package/lib/usdm-client.d.ts +45 -27
  29. package/lib/usdm-client.js +7 -19
  30. package/lib/usdm-client.js.map +1 -1
  31. package/lib/util/BaseRestClient.js +20 -6
  32. package/lib/util/BaseRestClient.js.map +1 -1
  33. package/lib/util/BaseWSClient.d.ts +14 -3
  34. package/lib/util/BaseWSClient.js +87 -30
  35. package/lib/util/BaseWSClient.js.map +1 -1
  36. package/lib/util/beautifier-maps.d.ts +32 -0
  37. package/lib/util/beautifier-maps.js +41 -0
  38. package/lib/util/beautifier-maps.js.map +1 -1
  39. package/lib/util/beautifier.d.ts +1 -0
  40. package/lib/util/beautifier.js +44 -9
  41. package/lib/util/beautifier.js.map +1 -1
  42. package/lib/util/browser-support.d.ts +1 -1
  43. package/lib/util/browser-support.js +1 -41
  44. package/lib/util/browser-support.js.map +1 -1
  45. package/lib/util/node-support.js +7 -5
  46. package/lib/util/node-support.js.map +1 -1
  47. package/lib/util/requestUtils.d.ts +22 -4
  48. package/lib/util/requestUtils.js +76 -25
  49. package/lib/util/requestUtils.js.map +1 -1
  50. package/lib/util/rounding.d.ts +8 -0
  51. package/lib/util/rounding.js +41 -0
  52. package/lib/util/rounding.js.map +1 -0
  53. package/lib/util/typeGuards.d.ts +31 -3
  54. package/lib/util/typeGuards.js +105 -8
  55. package/lib/util/typeGuards.js.map +1 -1
  56. package/lib/util/webCryptoAPI.js +20 -45
  57. package/lib/util/webCryptoAPI.js.map +1 -1
  58. package/lib/util/websockets/WsStore.js +7 -2
  59. package/lib/util/websockets/WsStore.js.map +1 -1
  60. package/lib/util/websockets/enum.d.ts +11 -0
  61. package/lib/util/websockets/enum.js +24 -0
  62. package/lib/util/websockets/enum.js.map +1 -0
  63. package/lib/util/websockets/rest-client-cache.d.ts +3 -3
  64. package/lib/util/websockets/rest-client-cache.js +9 -9
  65. package/lib/util/websockets/rest-client-cache.js.map +1 -1
  66. package/lib/util/websockets/user-data-stream-manager.js +7 -8
  67. package/lib/util/websockets/user-data-stream-manager.js.map +1 -1
  68. package/lib/util/websockets/websocket-util.d.ts +27 -6
  69. package/lib/util/websockets/websocket-util.js +147 -52
  70. package/lib/util/websockets/websocket-util.js.map +1 -1
  71. package/lib/websocket-api-client.d.ts +401 -0
  72. package/lib/websocket-api-client.js +647 -0
  73. package/lib/websocket-api-client.js.map +1 -0
  74. package/lib/websocket-client-legacy.d.ts +5 -3
  75. package/lib/websocket-client-legacy.js +11 -10
  76. package/lib/websocket-client-legacy.js.map +1 -1
  77. package/lib/websocket-client.d.ts +26 -11
  78. package/lib/websocket-client.js +185 -139
  79. package/lib/websocket-client.js.map +1 -1
  80. package/package.json +3 -3
@@ -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
  };
@@ -15,9 +26,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
26
  exports.WebsocketClient = void 0;
16
27
  const BaseWSClient_1 = require("./util/BaseWSClient");
17
28
  const beautifier_1 = __importDefault(require("./util/beautifier"));
29
+ const node_support_1 = require("./util/node-support");
18
30
  const requestUtils_1 = require("./util/requestUtils");
19
31
  const typeGuards_1 = require("./util/typeGuards");
20
- const webCryptoAPI_1 = require("./util/webCryptoAPI");
21
32
  const rest_client_cache_1 = require("./util/websockets/rest-client-cache");
22
33
  const user_data_stream_manager_1 = require("./util/websockets/user-data-stream-manager");
23
34
  const websocket_util_1 = require("./util/websockets/websocket-util");
@@ -47,6 +58,22 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
47
58
  warnKeyMissingInMap: true,
48
59
  });
49
60
  this.respawnTimeoutCache = {};
61
+ if (options === null || options === void 0 ? void 0 : options.beautifyWarnIfMissing) {
62
+ this.beautifier.setWarnIfMissing(options.beautifyWarnIfMissing);
63
+ }
64
+ /**
65
+ * Binance uses native WebSocket ping/pong frames, which cannot be directly used in
66
+ * some environents (e.g. most browsers do not support sending raw ping/pong frames).
67
+ *
68
+ * This disables heartbeats in those environments, if ping/pong frames are unavailable.
69
+ *
70
+ * Some browsers may still handle these automatically. Some discussion around this can
71
+ * be found here: https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser
72
+ */
73
+ if (!(0, websocket_util_1.isWSPingFrameAvailable)()) {
74
+ this.logger.trace('Disabled WS heartbeats. WS.ping() is not available in your environment.');
75
+ this.options.disableHeartbeat = true;
76
+ }
50
77
  this.userDataStreamManager = new user_data_stream_manager_1.UserDataStreamManager({
51
78
  logger: this.logger,
52
79
  wsStore: this.getWsStore(),
@@ -64,25 +91,17 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
64
91
  return this.userDataStreamManager;
65
92
  }
66
93
  getRestClientOptions() {
67
- return Object.assign(Object.assign(Object.assign({}, this.options), this.options.restOptions), { api_key: this.options.api_key, api_secret: this.options.api_secret });
94
+ return Object.assign(Object.assign(Object.assign({}, this.options), this.options.restOptions), { testnet: this.options.testnet, api_key: this.options.api_key, api_secret: this.options.api_secret });
68
95
  }
69
96
  /**
70
97
  * Request connection of all dependent (public & WS API) websockets in prod, instead of waiting
71
- * for automatic connection by SDK. TODO: also do the user data streams?
98
+ * for automatic connection by SDK.
99
+ *
100
+ * For the Binance SDK, this will only open public connections (without auth), but is almost definitely overkill if you're only working with one product group.
72
101
  */
73
102
  connectAll() {
74
103
  return this.connectPublic();
75
104
  }
76
- /**
77
- * Ensures the WS API connection is active and ready.
78
- *
79
- * You do not need to call this, but if you call this before making any WS API requests,
80
- * it can accelerate the first request (by preparing the connection in advance).
81
- */
82
- connectWSAPI(wsKey) {
83
- /** This call automatically ensures the connection is active AND authenticated before resolving */
84
- return this.assertIsAuthenticated(wsKey);
85
- }
86
105
  /**
87
106
  * Request connection to all public websockets in prod (spot, margin, futures, options). Overkill if
88
107
  * you're only working with one product group.
@@ -95,18 +114,24 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
95
114
  this.connect(websocket_util_1.WS_KEY_MAP.eoptions),
96
115
  ];
97
116
  }
117
+ /**
118
+ * This function serves no purpose in the Binance SDK
119
+ */
98
120
  connectPrivate() {
99
121
  return __awaiter(this, void 0, void 0, function* () {
100
- // switch (this.options.market) {
101
- // case 'v5':
102
- // default: {
103
- // return this.connect(WS_KEY_MAP.v5Private);
104
- // }
105
- // }
106
- // TODO:
107
122
  return;
108
123
  });
109
124
  }
125
+ /**
126
+ * Ensures the WS API connection is active and ready.
127
+ *
128
+ * You do not need to call this, but if you call this before making any WS API requests,
129
+ * it can accelerate the first request (by preparing the connection in advance).
130
+ */
131
+ connectWSAPI(wsKey) {
132
+ /** This call automatically ensures the connection is active AND authenticated before resolving */
133
+ return this.assertIsAuthenticated(wsKey);
134
+ }
110
135
  /**
111
136
  * Request subscription to one or more topics. Pass topics as either an array of strings,
112
137
  * or array of objects (if the topic has parameters).
@@ -135,54 +160,75 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
135
160
  const normalisedTopicRequests = (0, websocket_util_1.getNormalisedTopicRequests)(topicRequests);
136
161
  return this.unsubscribeTopicsForWsKey(normalisedTopicRequests, wsKey);
137
162
  }
138
- // These overloads give stricter types than mapped generics, since generic constraints
139
- // do not trigger excess property checks
140
- // Without these overloads, TypeScript won't complain if you include an
141
- // unexpected property with your request (if it doesn't clash with an existing property)
142
- // sendWSAPIRequest<TWSOpreation extends WSAPIOperation = 'order.create'>(
143
- // wsKey: typeof WS_KEY_MAP.v5PrivateTrade,
144
- // operation: TWSOpreation,
145
- // params: WsAPITopicRequestParamMap[TWSOpreation],
146
- // ): Promise<WsAPIOperationResponseMap[TWSOpreation]>;
147
- // sendWSAPIRequest<TWSOpreation extends WSAPIOperation = 'order.amend'>(
148
- // wsKey: typeof WS_KEY_MAP.v5PrivateTrade,
149
- // operation: TWSOpreation,
150
- // params: WsAPITopicRequestParamMap[TWSOpreation],
151
- // ): Promise<WsAPIOperationResponseMap[TWSOpreation]>;
152
- // sendWSAPIRequest<TWSOpreation extends WSAPIOperation = 'order.cancel'>(
153
- // wsKey: typeof WS_KEY_MAP.v5PrivateTrade,
154
- // operation: TWSOpreation,
155
- // params: WsAPITopicRequestParamMap[TWSOpreation],
156
- // ): Promise<WsAPIOperationResponseMap[TWSOpreation]>;
157
163
  sendWSAPIRequest(wsKey, operation, params) {
158
164
  return __awaiter(this, void 0, void 0, function* () {
159
- //WsAPIOperationResponseMap[TWSOperation]> {
160
165
  /**
161
- * Spot: https://developers.binance.com/docs/binance-spot-api-docs/web-socket-api/general-api-information
166
+ * Spot: https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/general-api-information
167
+ * USDM Futures: https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-api-general-info
168
+ * COINM Futures: https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-api-general-info
162
169
  */
163
- // this.logger.trace(`sendWSAPIRequest(): assert "${wsKey}" is connected`);
164
- yield this.assertIsConnected(wsKey);
165
- // this.logger.trace('sendWSAPIRequest()->assertIsConnected() ok');
166
- yield this.assertIsAuthenticated(wsKey);
167
- // this.logger.trace('sendWSAPIRequest()->assertIsAuthenticated() ok');
170
+ var _a, _b;
171
+ // If testnet, enforce testnet wskey for WS API calls
172
+ const resolvedWsKey = this.options.testnet ? (0, websocket_util_1.getTestnetWsKey)(wsKey) : wsKey;
173
+ // this.logger.trace(`sendWSAPIRequest(): assertIsConnected("${wsKey}")...`);
174
+ const timestampBeforeAuth = Date.now();
175
+ yield this.assertIsConnected(resolvedWsKey);
176
+ // this.logger.trace('sendWSAPIRequest(): assertIsConnected(${wsKey}) ok');
177
+ // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey})...');
178
+ yield this.assertIsAuthenticated(resolvedWsKey);
179
+ const timestampAfterAuth = Date.now();
180
+ // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey}) ok');
168
181
  const request = {
169
182
  id: this.getNewRequestId(),
170
183
  method: operation,
171
- params: Object.assign({ timestamp: Date.now(), recvWindow: this.options.recvWindow }, params),
184
+ params: Object.assign({}, params),
172
185
  };
173
- if ((0, requestUtils_1.requiresWSAPINewClientOID)(request, wsKey)) {
174
- (0, requestUtils_1.validateWSAPINewClientOID)(request, wsKey);
186
+ /**
187
+ * Some WS API requests require a timestamp to be included. assertIsConnected and assertIsAuthenticated
188
+ * can introduce a small delay before the actual request is sent, if not connected before that request is
189
+ * made. This can lead to a curious race condition, where the request timestamp is before
190
+ * the "authorizedSince" timestamp - as such, binance does not recognise the session as already authenticated.
191
+ *
192
+ * The below mechanism measures any delay introduced from the assert calls, and if the request includes a timestamp,
193
+ * it offsets that timestamp by the delay.
194
+ */
195
+ const delayFromAuthAssert = timestampAfterAuth - timestampBeforeAuth;
196
+ if (delayFromAuthAssert && ((_a = request.params) === null || _a === void 0 ? void 0 : _a.timestamp)) {
197
+ request.params.timestamp += delayFromAuthAssert;
198
+ this.logger.trace(`sendWSAPIRequest(): adjust timestamp - delay seen by connect/auth assert and delayed request includes timestamp, adjusting timestamp by ${delayFromAuthAssert}ms`);
199
+ }
200
+ if ((0, requestUtils_1.requiresWSAPINewClientOID)(request, resolvedWsKey)) {
201
+ (0, requestUtils_1.validateWSAPINewClientOID)(request, resolvedWsKey);
175
202
  }
176
203
  // Sign, if needed
177
204
  const signedEvent = yield this.signWSAPIRequest(request);
178
205
  // Store deferred promise, resolved within the "resolveEmittableEvents" method while parsing incoming events
179
- const promiseRef = (0, websocket_util_1.getPromiseRefForWSAPIRequest)(wsKey, signedEvent);
180
- const deferredPromise = this.getWsStore().createDeferredPromise(wsKey, promiseRef, false);
206
+ const promiseRef = (0, websocket_util_1.getPromiseRefForWSAPIRequest)(resolvedWsKey, signedEvent);
207
+ const deferredPromise = this.getWsStore().createDeferredPromise(resolvedWsKey, promiseRef, false);
208
+ (_b = deferredPromise.promise) === null || _b === void 0 ? void 0 : _b.then((res) => {
209
+ if (!Array.isArray(res)) {
210
+ res.request = Object.assign({ wsKey: resolvedWsKey }, signedEvent);
211
+ }
212
+ return res;
213
+ }).catch((e) => {
214
+ if (typeof e === 'string') {
215
+ this.logger.error('unexpcted string', { e });
216
+ return e;
217
+ }
218
+ e.request = {
219
+ wsKey: resolvedWsKey,
220
+ operation,
221
+ params: signedEvent.params,
222
+ };
223
+ // throw e;
224
+ return e;
225
+ });
181
226
  // this.logger.trace(
182
227
  // `sendWSAPIRequest(): sending raw request: ${JSON.stringify(signedEvent)} with promiseRef(${promiseRef})`,
183
228
  // );
184
- // Send event
185
- this.tryWsSend(wsKey, JSON.stringify(signedEvent));
229
+ // Send event.
230
+ const throwExceptions = true;
231
+ this.tryWsSend(resolvedWsKey, JSON.stringify(signedEvent), throwExceptions);
186
232
  this.logger.trace(`sendWSAPIRequest(): sent "${operation}" event with promiseRef(${promiseRef})`);
187
233
  // Return deferred promise, so caller can await this call
188
234
  return deferredPromise.promise;
@@ -210,13 +256,35 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
210
256
  if (typeof this.options.customSignMessageFn === 'function') {
211
257
  return this.options.customSignMessageFn(paramsStr, secret);
212
258
  }
213
- return yield (0, webCryptoAPI_1.signMessage)(paramsStr, secret, method, algorithm);
259
+ return yield (0, node_support_1.signMessage)(paramsStr, secret, method, algorithm);
260
+ // return await signMessageWebCryptoAPI(paramsStr, secret, method, algorithm);
214
261
  });
215
262
  }
216
263
  signWSAPIRequest(requestEvent) {
217
264
  return __awaiter(this, void 0, void 0, function* () {
218
- // Not needed for Binance. Auth happens only on connection open, automatically.
265
+ if (!requestEvent.params) {
266
+ return requestEvent;
267
+ }
268
+ /**
269
+ *
270
+ */
271
+ // Not needed for most commands on binance Binance. Auth happens only on connection open, automatically.
219
272
  // Faster than performing auth for every request
273
+ // However, some commands don't work without this for some reason...
274
+ const _a = requestEvent.params, { signRequest } = _a, otherParams = __rest(_a, ["signRequest"]);
275
+ if (signRequest) {
276
+ const strictParamValidation = true;
277
+ const encodeValues = true;
278
+ const filterUndefinedParams = true;
279
+ const semiFinalRequestParams = Object.assign({ apiKey: this.options.api_key }, otherParams);
280
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(semiFinalRequestParams, strictParamValidation, encodeValues, filterUndefinedParams);
281
+ const signature = yield this.signMessage(serialisedParams, this.options.api_secret, 'base64', 'SHA-256');
282
+ console.log('signWSAPIRequest()', {
283
+ semiFinalRequestParams,
284
+ serialisedParams,
285
+ });
286
+ return Object.assign(Object.assign({}, requestEvent), { params: Object.assign(Object.assign({}, semiFinalRequestParams), { signature }) });
287
+ }
220
288
  return requestEvent;
221
289
  });
222
290
  }
@@ -227,22 +295,21 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
227
295
  // 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.
228
296
  // 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.
229
297
  // Note: You still have to specify the timestamp parameter for SIGNED requests.
230
- const recvWindow = this.options.recvWindow || 0;
231
- const timestamp = Date.now() + (this.getTimeOffsetMs() || 0) + recvWindow;
298
+ // const recvWindow = this.options.recvWindow || 0;
299
+ const timestamp = Date.now() + (this.getTimeOffsetMs() || 0); // + recvWindow;
232
300
  const strictParamValidation = true;
233
301
  const encodeValues = true;
234
302
  const filterUndefinedParams = true;
235
- const authParams = {
303
+ const params = {
236
304
  apiKey: this.options.api_key,
237
305
  timestamp,
238
306
  };
239
- const serialisedParams = (0, requestUtils_1.serialiseParams)(authParams, strictParamValidation, encodeValues, filterUndefinedParams);
307
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(params, strictParamValidation, encodeValues, filterUndefinedParams);
240
308
  const signature = yield this.signMessage(serialisedParams, this.options.api_secret, 'base64', 'SHA-256');
241
- // https://developers.binance.com/docs/binance-spot-api-docs/web-socket-api/request-security#signed-request-example-ed25519
242
309
  const request = {
243
310
  id: this.getNewRequestId(),
244
311
  method: 'session.logon',
245
- params: Object.assign(Object.assign({}, authParams), { signature }),
312
+ params: Object.assign(Object.assign({}, params), { signature }),
246
313
  };
247
314
  return request;
248
315
  }
@@ -252,36 +319,12 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
252
319
  }
253
320
  });
254
321
  }
255
- // private async getWsAuthSignature(
256
- // wsKey: WsKey,
257
- // ): Promise<{ expiresAt: number; signature: string }> {
258
- // const { api_key, api_secret } = this.options;
259
- // if (!api_key || !api_secret) {
260
- // this.logger.error(
261
- // 'Cannot authenticate websocket, either api or private keys missing.',
262
- // { ...WS_LOGGER_CATEGORY, wsKey },
263
- // );
264
- // throw new Error('Cannot auth - missing api or secret in config');
265
- // }
266
- // this.logger.trace("Getting auth'd request params", {
267
- // ...WS_LOGGER_CATEGORY,
268
- // wsKey,
269
- // });
270
- // const recvWindow = this.options.recvWindow || 5000;
271
- // const signatureExpiresAt = Date.now() + this.getTimeOffsetMs() + recvWindow;
272
- // // const signature = await this.signMessage(
273
- // // 'GET/realtime' + signatureExpiresAt,
274
- // // api_secret,
275
- // // 'hex',
276
- // // 'SHA-256',
277
- // // );
278
- // return {
279
- // expiresAt: signatureExpiresAt,
280
- // signature: '',
281
- // };
282
- // }
283
322
  sendPingEvent(wsKey) {
284
323
  try {
324
+ if (!(0, websocket_util_1.isWSPingFrameAvailable)()) {
325
+ this.logger.trace('Unable to send WS ping frame. Not available in this environment.', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
326
+ return;
327
+ }
285
328
  // this.logger.trace(`Sending upstream ping: `, { ...loggerCategory, wsKey });
286
329
  if (!wsKey) {
287
330
  throw new Error('No wsKey provided');
@@ -304,8 +347,11 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
304
347
  }
305
348
  }
306
349
  sendPongEvent(wsKey) {
307
- // ws.pong();
308
350
  try {
351
+ if (!(0, websocket_util_1.isWSPongFrameAvailable)()) {
352
+ this.logger.trace('Unable to send WS pong frame. Not available in this environment.', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
353
+ return;
354
+ }
309
355
  // this.logger.trace(`Sending upstream ping: `, { ...loggerCategory, wsKey });
310
356
  if (!wsKey) {
311
357
  throw new Error('No wsKey provided');
@@ -347,6 +393,8 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
347
393
  params: topics,
348
394
  id: req_id,
349
395
  };
396
+ // Cache midflight subs on the req ID
397
+ // Enrich response with subs for that req ID
350
398
  const midflightWsEvent = {
351
399
  requestKey: wsEvent.id,
352
400
  requestEvent: wsEvent,
@@ -354,9 +402,6 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
354
402
  wsRequestEvents.push(Object.assign({}, midflightWsEvent));
355
403
  break;
356
404
  }
357
- // default: {
358
- // throw neverGuard(wsKey, `Unhandled wsKey "${wsKey}"`);
359
- // }
360
405
  }
361
406
  if (wsRequestBuildingErrors.length) {
362
407
  const label = wsRequestBuildingErrors.length === requests.length ? 'all' : 'some';
@@ -403,6 +448,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
403
448
  */
404
449
  resolveEmittableEvents(wsKey, event) {
405
450
  var _a, _b, _c;
451
+ this.logger.trace(`resolveEmittableEvents(${wsKey}): `, event);
406
452
  const results = [];
407
453
  try {
408
454
  // const parsed = JSON.parse(event.data);
@@ -415,8 +461,6 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
415
461
  if (legacyContext) {
416
462
  parsed.wsMarket = legacyContext.market;
417
463
  }
418
- // const eventType = eventType; //parsed?.stream;
419
- // const eventOperation = parsed?.op;
420
464
  const traceEmittable = false;
421
465
  if (traceEmittable) {
422
466
  this.logger.trace('resolveEmittableEvents', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey,
@@ -433,9 +477,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
433
477
  'pong',
434
478
  ];
435
479
  // WS API response
436
- // TODO: after
437
480
  if (isWSAPIResponse) {
438
- const retCode = parsed.status;
439
481
  /**
440
482
  * Responses to "subscribe" are quite basic, with no indication of errors (e.g. bad topic):
441
483
  *
@@ -444,29 +486,31 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
444
486
  * id: 1,
445
487
  * };
446
488
  *
447
- * Will need some way to tell this apart from an actual WS API response with error. Maybe to send it via a diff check than isWSAPIResponse=true
489
+ * Currently there's no simple way to tell this apart from an actual WS API response with
490
+ * error. So subscribe/unsubscribe requests will simply look like a WS API response internally,
491
+ * but that will not affect usage.
448
492
  *
449
- * Example wsapi error:
493
+ * Unrelated, example wsapi error for reference:
450
494
  *
451
- {
452
- id: 1,
453
- status: 400,
454
- error: {
455
- code: -1021,
456
- msg: "Timestamp for this request was 1000ms ahead of the server's time."
457
- },
458
- rateLimits: [
459
- {
460
- rateLimitType: 'REQUEST_WEIGHT',
461
- interval: 'MINUTE',
462
- intervalNum: 1,
463
- limit: 6000,
464
- count: 4
465
- }
466
- ],
467
- wsKey: 'mainWSAPI',
468
- isWSAPIResponse: true
469
- }
495
+ {
496
+ id: 1,
497
+ status: 400,
498
+ error: {
499
+ code: -1021,
500
+ msg: "Timestamp for this request was 1000ms ahead of the server's time."
501
+ },
502
+ rateLimits: [
503
+ {
504
+ rateLimitType: 'REQUEST_WEIGHT',
505
+ interval: 'MINUTE',
506
+ intervalNum: 1,
507
+ limit: 6000,
508
+ count: 4
509
+ }
510
+ ],
511
+ wsKey: 'mainWSAPI',
512
+ isWSAPIResponse: true
513
+ }
470
514
  */
471
515
  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;
472
516
  // This is the counterpart to getPromiseRefForWSAPIRequest
@@ -488,6 +532,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
488
532
  wsKey,
489
533
  promiseRef,
490
534
  parsedEvent: parsed,
535
+ e,
491
536
  });
492
537
  }
493
538
  results.push({
@@ -499,7 +544,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
499
544
  }
500
545
  // authenticated
501
546
  if ((_c = parsed.result) === null || _c === void 0 ? void 0 : _c.apiKey) {
502
- // Note: Without this check, this will also trigger "onWsAuthenticaated()" for session.status requests
547
+ // Note: Without this check, this will also trigger "onWsAuthenticated()" for session.status requests
503
548
  if (this.getWsStore().getAuthenticationInProgressPromise(wsKey)) {
504
549
  results.push({
505
550
  eventType: 'authenticated',
@@ -517,13 +562,15 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
517
562
  wsKey,
518
563
  promiseRef,
519
564
  parsedEvent: parsed,
565
+ e,
520
566
  });
521
567
  }
522
568
  results.push({
523
569
  eventType: 'response',
524
- event: parsed,
570
+ event: Object.assign(Object.assign({}, parsed), { request: this.getCachedMidFlightRequest(wsKey, reqId) }),
525
571
  isWSAPIResponse: isWSAPIResponse,
526
572
  });
573
+ this.removeCachedMidFlightRequest(wsKey, reqId);
527
574
  return results;
528
575
  }
529
576
  // Handle incoming event that listen key expired
@@ -542,7 +589,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
542
589
  if (this.options.beautify) {
543
590
  const beautifiedMessage = this.beautifier.beautifyWsMessage(parsed, eventType, false,
544
591
  // Suffix all events for the beautifier, if market is options
545
- // Options has some conflicting keys with different intentions
592
+ // Options has some conflicting keys with different intentions, so will be suffixed
546
593
  wsKey === 'eoptions' ? 'Options' : '');
547
594
  results.push({
548
595
  eventType: 'formattedMessage',
@@ -551,19 +598,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
551
598
  });
552
599
  // emit an additional event for user data messages
553
600
  if (!Array.isArray(beautifiedMessage) && eventType) {
554
- if ([
555
- 'balanceUpdate',
556
- 'executionReport',
557
- 'listStatus',
558
- 'listenKeyExpired',
559
- 'outboundAccountPosition',
560
- 'ACCOUNT_CONFIG_UPDATE',
561
- 'ACCOUNT_UPDATE',
562
- 'MARGIN_CALL',
563
- 'ORDER_TRADE_UPDATE',
564
- 'TRADE_LITE',
565
- 'CONDITIONAL_ORDER_TRIGGER_REJECT',
566
- ].includes(eventType)) {
601
+ if (websocket_util_1.EVENT_TYPES_USER_DATA.includes(eventType)) {
567
602
  results.push({
568
603
  eventType: 'formattedUserDataMessage',
569
604
  event: beautifiedMessage,
@@ -748,7 +783,6 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
748
783
  }
749
784
  triggerCustomReconnectionWorkflow(legacyWsKey) {
750
785
  return __awaiter(this, void 0, void 0, function* () {
751
- console.log(`triggerCustomReconnectionWorkflow(${legacyWsKey})`);
752
786
  if (legacyWsKey.includes('userData')) {
753
787
  return this.getUserDataStreamManager().triggerUserDataReconnectionWorkflow(legacyWsKey);
754
788
  }
@@ -790,13 +824,13 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
790
824
  ws = yield this.subscribeUsdFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
791
825
  break;
792
826
  case 'usdmTestnet':
793
- ws = yield this.subscribeUsdFuturesUserDataStream('usdmTestnet', forceNewConnection, miscConnectionState);
827
+ ws = yield this.subscribeUsdFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
794
828
  break;
795
829
  case 'coinm':
796
830
  ws = yield this.subscribeCoinFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
797
831
  break;
798
832
  case 'coinmTestnet':
799
- ws = yield this.subscribeCoinFuturesUserDataStream('coinmTestnet', forceNewConnection, miscConnectionState);
833
+ ws = yield this.subscribeCoinFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
800
834
  break;
801
835
  case 'portfoliom':
802
836
  ws = yield this.subscribePortfolioMarginUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
@@ -849,7 +883,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
849
883
  forceNewConnection, miscState) {
850
884
  try {
851
885
  const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.usdmTestnet;
852
- const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet);
886
+ const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions);
853
887
  const { listenKey } = yield restClient.getFuturesUserDataListenKey();
854
888
  const market = isTestnet ? 'usdmTestnet' : 'usdm';
855
889
  return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
@@ -861,6 +895,18 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
861
895
  }
862
896
  });
863
897
  }
898
+ closeUserDataStream() {
899
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'usdm') {
900
+ const wsKeys = this.getWsStore().getKeys();
901
+ const userDataWsKey = wsKeys.find((key) => {
902
+ return key === wsKey || key.startsWith(wsKey + '_userData');
903
+ });
904
+ if (!userDataWsKey) {
905
+ throw new Error(`No matching connection found with wsKey "${wsKey}". Active connections: [${JSON.stringify(wsKey)}]`);
906
+ }
907
+ this.close(userDataWsKey);
908
+ });
909
+ }
864
910
  /**
865
911
  * Subscribe to COIN-M Futures user data stream - listen key is automatically generated.
866
912
  */
@@ -870,7 +916,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
870
916
  try {
871
917
  const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.coinmTestnet;
872
918
  const { listenKey } = yield this.restClientCache
873
- .getCOINMRestClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet)
919
+ .getCOINMRestClient(this.getRestClientOptions(), this.options.requestOptions)
874
920
  .getFuturesUserDataListenKey();
875
921
  const market = isTestnet ? 'coinmTestnet' : 'coinm';
876
922
  return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);