binance 3.0.0-beta.1 → 3.0.0-beta.11

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 -5
  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 +23 -5
  48. package/lib/util/requestUtils.js +130 -29
  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 +36 -8
  69. package/lib/util/websockets/websocket-util.js +176 -74
  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 +14 -13
  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 +269 -174
  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");
@@ -44,16 +55,36 @@ 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
+ }
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.info('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(),
53
80
  restClientCache: this.restClientCache,
54
81
  // fn pointers:
55
- respawnUserDataFn: (wsKey, market, context = {}) => this.respawnUserDataStream(wsKey, market, context),
56
- 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
+ },
57
88
  getRestClientOptionsFn: () => this.getRestClientOptions(),
58
89
  getWsClientOptionsfn: () => this.options,
59
90
  closeWsFn: (wsKey, force) => this.close(wsKey, force),
@@ -64,25 +95,17 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
64
95
  return this.userDataStreamManager;
65
96
  }
66
97
  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 });
98
+ 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
99
  }
69
100
  /**
70
101
  * 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?
102
+ * for automatic connection by SDK.
103
+ *
104
+ * 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
105
  */
73
106
  connectAll() {
74
107
  return this.connectPublic();
75
108
  }
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
109
  /**
87
110
  * Request connection to all public websockets in prod (spot, margin, futures, options). Overkill if
88
111
  * you're only working with one product group.
@@ -95,18 +118,24 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
95
118
  this.connect(websocket_util_1.WS_KEY_MAP.eoptions),
96
119
  ];
97
120
  }
121
+ /**
122
+ * This function serves no purpose in the Binance SDK
123
+ */
98
124
  connectPrivate() {
99
125
  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
126
  return;
108
127
  });
109
128
  }
129
+ /**
130
+ * Ensures the WS API connection is active and ready.
131
+ *
132
+ * You do not need to call this, but if you call this before making any WS API requests,
133
+ * it can accelerate the first request (by preparing the connection in advance).
134
+ */
135
+ connectWSAPI(wsKey) {
136
+ /** This call automatically ensures the connection is active AND authenticated before resolving */
137
+ return this.assertIsAuthenticated(wsKey);
138
+ }
110
139
  /**
111
140
  * Request subscription to one or more topics. Pass topics as either an array of strings,
112
141
  * or array of objects (if the topic has parameters).
@@ -135,54 +164,75 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
135
164
  const normalisedTopicRequests = (0, websocket_util_1.getNormalisedTopicRequests)(topicRequests);
136
165
  return this.unsubscribeTopicsForWsKey(normalisedTopicRequests, wsKey);
137
166
  }
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
167
  sendWSAPIRequest(wsKey, operation, params) {
158
168
  return __awaiter(this, void 0, void 0, function* () {
159
- //WsAPIOperationResponseMap[TWSOperation]> {
160
169
  /**
161
- * Spot: https://developers.binance.com/docs/binance-spot-api-docs/web-socket-api/general-api-information
170
+ * Spot: https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/general-api-information
171
+ * USDM Futures: https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-api-general-info
172
+ * COINM Futures: https://developers.binance.com/docs/derivatives/coin-margined-futures/websocket-api-general-info
162
173
  */
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');
174
+ var _a, _b;
175
+ // If testnet, enforce testnet wskey for WS API calls
176
+ const resolvedWsKey = this.options.testnet ? (0, websocket_util_1.getTestnetWsKey)(wsKey) : wsKey;
177
+ // this.logger.trace(`sendWSAPIRequest(): assertIsConnected("${wsKey}")...`);
178
+ const timestampBeforeAuth = Date.now();
179
+ yield this.assertIsConnected(resolvedWsKey);
180
+ // this.logger.trace('sendWSAPIRequest(): assertIsConnected(${wsKey}) ok');
181
+ // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey})...');
182
+ yield this.assertIsAuthenticated(resolvedWsKey);
183
+ const timestampAfterAuth = Date.now();
184
+ // this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey}) ok');
168
185
  const request = {
169
186
  id: this.getNewRequestId(),
170
187
  method: operation,
171
- params: Object.assign({ timestamp: Date.now(), recvWindow: this.options.recvWindow }, params),
188
+ params: Object.assign({}, params),
172
189
  };
173
- if ((0, requestUtils_1.requiresWSAPINewClientOID)(request, wsKey)) {
174
- (0, requestUtils_1.validateWSAPINewClientOID)(request, wsKey);
190
+ /**
191
+ * Some WS API requests require a timestamp to be included. assertIsConnected and assertIsAuthenticated
192
+ * can introduce a small delay before the actual request is sent, if not connected before that request is
193
+ * made. This can lead to a curious race condition, where the request timestamp is before
194
+ * the "authorizedSince" timestamp - as such, binance does not recognise the session as already authenticated.
195
+ *
196
+ * The below mechanism measures any delay introduced from the assert calls, and if the request includes a timestamp,
197
+ * it offsets that timestamp by the delay.
198
+ */
199
+ const delayFromAuthAssert = timestampAfterAuth - timestampBeforeAuth;
200
+ if (delayFromAuthAssert && ((_a = request.params) === null || _a === void 0 ? void 0 : _a.timestamp)) {
201
+ request.params.timestamp += delayFromAuthAssert;
202
+ this.logger.trace(`sendWSAPIRequest(): adjust timestamp - delay seen by connect/auth assert and delayed request includes timestamp, adjusting timestamp by ${delayFromAuthAssert}ms`);
203
+ }
204
+ if ((0, requestUtils_1.requiresWSAPINewClientOID)(request, resolvedWsKey)) {
205
+ (0, requestUtils_1.validateWSAPINewClientOID)(request, resolvedWsKey);
175
206
  }
176
207
  // Sign, if needed
177
208
  const signedEvent = yield this.signWSAPIRequest(request);
178
209
  // 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);
210
+ const promiseRef = (0, websocket_util_1.getPromiseRefForWSAPIRequest)(resolvedWsKey, signedEvent);
211
+ const deferredPromise = this.getWsStore().createDeferredPromise(resolvedWsKey, promiseRef, false);
212
+ (_b = deferredPromise.promise) === null || _b === void 0 ? void 0 : _b.then((res) => {
213
+ if (!Array.isArray(res)) {
214
+ res.request = Object.assign({ wsKey: resolvedWsKey }, signedEvent);
215
+ }
216
+ return res;
217
+ }).catch((e) => {
218
+ if (typeof e === 'string') {
219
+ this.logger.error('unexpcted string', { e });
220
+ return e;
221
+ }
222
+ e.request = {
223
+ wsKey: resolvedWsKey,
224
+ operation,
225
+ params: signedEvent.params,
226
+ };
227
+ // throw e;
228
+ return e;
229
+ });
181
230
  // this.logger.trace(
182
231
  // `sendWSAPIRequest(): sending raw request: ${JSON.stringify(signedEvent)} with promiseRef(${promiseRef})`,
183
232
  // );
184
- // Send event
185
- this.tryWsSend(wsKey, JSON.stringify(signedEvent));
233
+ // Send event.
234
+ const throwExceptions = true;
235
+ this.tryWsSend(resolvedWsKey, JSON.stringify(signedEvent), throwExceptions);
186
236
  this.logger.trace(`sendWSAPIRequest(): sent "${operation}" event with promiseRef(${promiseRef})`);
187
237
  // Return deferred promise, so caller can await this call
188
238
  return deferredPromise.promise;
@@ -210,13 +260,35 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
210
260
  if (typeof this.options.customSignMessageFn === 'function') {
211
261
  return this.options.customSignMessageFn(paramsStr, secret);
212
262
  }
213
- return yield (0, webCryptoAPI_1.signMessage)(paramsStr, secret, method, algorithm);
263
+ return yield (0, node_support_1.signMessage)(paramsStr, secret, method, algorithm);
264
+ // return await signMessageWebCryptoAPI(paramsStr, secret, method, algorithm);
214
265
  });
215
266
  }
216
267
  signWSAPIRequest(requestEvent) {
217
268
  return __awaiter(this, void 0, void 0, function* () {
218
- // Not needed for Binance. Auth happens only on connection open, automatically.
269
+ if (!requestEvent.params) {
270
+ return requestEvent;
271
+ }
272
+ /**
273
+ *
274
+ */
275
+ // Not needed for most commands on binance Binance. Auth happens only on connection open, automatically.
219
276
  // Faster than performing auth for every request
277
+ // However, some commands don't work without this for some reason...
278
+ const _a = requestEvent.params, { signRequest } = _a, otherParams = __rest(_a, ["signRequest"]);
279
+ if (signRequest) {
280
+ const strictParamValidation = true;
281
+ const encodeValues = true;
282
+ const filterUndefinedParams = true;
283
+ const semiFinalRequestParams = Object.assign({ apiKey: this.options.api_key }, otherParams);
284
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(semiFinalRequestParams, strictParamValidation, encodeValues, filterUndefinedParams);
285
+ const signature = yield this.signMessage(serialisedParams, this.options.api_secret, 'base64', 'SHA-256');
286
+ console.log('signWSAPIRequest()', {
287
+ semiFinalRequestParams,
288
+ serialisedParams,
289
+ });
290
+ return Object.assign(Object.assign({}, requestEvent), { params: Object.assign(Object.assign({}, semiFinalRequestParams), { signature }) });
291
+ }
220
292
  return requestEvent;
221
293
  });
222
294
  }
@@ -227,22 +299,21 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
227
299
  // 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
300
  // 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
301
  // 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;
302
+ // const recvWindow = this.options.recvWindow || 0;
303
+ const timestamp = Date.now() + (this.getTimeOffsetMs() || 0); // + recvWindow;
232
304
  const strictParamValidation = true;
233
305
  const encodeValues = true;
234
306
  const filterUndefinedParams = true;
235
- const authParams = {
307
+ const params = {
236
308
  apiKey: this.options.api_key,
237
309
  timestamp,
238
310
  };
239
- const serialisedParams = (0, requestUtils_1.serialiseParams)(authParams, strictParamValidation, encodeValues, filterUndefinedParams);
311
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(params, strictParamValidation, encodeValues, filterUndefinedParams);
240
312
  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
313
  const request = {
243
314
  id: this.getNewRequestId(),
244
315
  method: 'session.logon',
245
- params: Object.assign(Object.assign({}, authParams), { signature }),
316
+ params: Object.assign(Object.assign({}, params), { signature }),
246
317
  };
247
318
  return request;
248
319
  }
@@ -252,36 +323,12 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
252
323
  }
253
324
  });
254
325
  }
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
326
  sendPingEvent(wsKey) {
284
327
  try {
328
+ if (!(0, websocket_util_1.isWSPingFrameAvailable)()) {
329
+ this.logger.trace('Unable to send WS ping frame. Not available in this environment.', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
330
+ return;
331
+ }
285
332
  // this.logger.trace(`Sending upstream ping: `, { ...loggerCategory, wsKey });
286
333
  if (!wsKey) {
287
334
  throw new Error('No wsKey provided');
@@ -304,8 +351,11 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
304
351
  }
305
352
  }
306
353
  sendPongEvent(wsKey) {
307
- // ws.pong();
308
354
  try {
355
+ if (!(0, websocket_util_1.isWSPongFrameAvailable)()) {
356
+ this.logger.trace('Unable to send WS pong frame. Not available in this environment.', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
357
+ return;
358
+ }
309
359
  // this.logger.trace(`Sending upstream ping: `, { ...loggerCategory, wsKey });
310
360
  if (!wsKey) {
311
361
  throw new Error('No wsKey provided');
@@ -347,6 +397,8 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
347
397
  params: topics,
348
398
  id: req_id,
349
399
  };
400
+ // Cache midflight subs on the req ID
401
+ // Enrich response with subs for that req ID
350
402
  const midflightWsEvent = {
351
403
  requestKey: wsEvent.id,
352
404
  requestEvent: wsEvent,
@@ -354,9 +406,6 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
354
406
  wsRequestEvents.push(Object.assign({}, midflightWsEvent));
355
407
  break;
356
408
  }
357
- // default: {
358
- // throw neverGuard(wsKey, `Unhandled wsKey "${wsKey}"`);
359
- // }
360
409
  }
361
410
  if (wsRequestBuildingErrors.length) {
362
411
  const label = wsRequestBuildingErrors.length === requests.length ? 'all' : 'some';
@@ -402,28 +451,71 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
402
451
  * Abstraction called to sort ws events into emittable event types (response to a request, data update, etc)
403
452
  */
404
453
  resolveEmittableEvents(wsKey, event) {
405
- var _a, _b, _c;
454
+ var _a, _b;
455
+ this.logger.trace(`resolveEmittableEvents(${wsKey}): `, event === null || event === void 0 ? void 0 : event.data);
406
456
  const results = [];
407
457
  try {
408
- // const parsed = JSON.parse(event.data);
409
- const parsed = (0, websocket_util_1.parseRawWsMessage)(event);
410
- const eventType = (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, parsed);
458
+ /**
459
+ *
460
+ * Extract event from JSON
461
+ *
462
+ */
463
+ const parsedEvent = (0, websocket_util_1.parseRawWsMessage)(event);
464
+ /**
465
+ *
466
+ * Minor data normalisation & preparation
467
+ *
468
+ */
469
+ // ws consumers: { data: ... }
470
+ // ws api consumers (user data): { event: ... }
471
+ // other responses: { ... }
472
+ const eventData = (parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.data) || (parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.event) || parsedEvent;
473
+ const parsedEventId = parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.id;
474
+ const parsedEventErrorCode = (_a = parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.error) === null || _a === void 0 ? void 0 : _a.code;
475
+ const streamName = parsedEvent === null || parsedEvent === void 0 ? void 0 : parsedEvent.stream;
476
+ const eventType =
477
+ // First try, the child node
478
+ (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, eventData) ||
479
+ // Second try, the parent
480
+ (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, parsedEvent);
411
481
  // Some events don't include the topic (event name)
412
482
  // This tries to extract and append it, using available context
413
- (0, requestUtils_1.appendEventIfMissing)(parsed, wsKey);
483
+ (0, requestUtils_1.appendEventIfMissing)(eventData, wsKey, eventType);
414
484
  const legacyContext = (0, websocket_util_1.getLegacyWsKeyContext)(wsKey);
415
- if (legacyContext) {
416
- parsed.wsMarket = legacyContext.market;
485
+ const wsMarket = legacyContext
486
+ ? legacyContext.market
487
+ : (0, websocket_util_1.resolveUserDataMarketForWsKey)(wsKey);
488
+ // This attaches `wsMarket` and `streamName` to incoming events
489
+ // If the event is an array, it's attached to each element in the array
490
+ if (Array.isArray(eventData)) {
491
+ for (const row of eventData) {
492
+ row.wsMarket = wsMarket;
493
+ if (streamName && !row.streamName) {
494
+ row.streamName = streamName;
495
+ }
496
+ }
417
497
  }
418
- // const eventType = eventType; //parsed?.stream;
419
- // const eventOperation = parsed?.op;
498
+ else {
499
+ eventData.wsMarket = wsMarket;
500
+ if (streamName && !eventData.streamName) {
501
+ eventData.streamName = streamName;
502
+ }
503
+ }
504
+ /**
505
+ *
506
+ *
507
+ * Main parsing logic below:
508
+ *
509
+ *
510
+ */
420
511
  const traceEmittable = false;
421
512
  if (traceEmittable) {
422
- this.logger.trace('resolveEmittableEvents', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey,
423
- eventType, parsed: JSON.stringify(parsed) }));
513
+ this.logger.trace('resolveEmittableEvents', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, parsedEvent: JSON.stringify(parsedEvent), parsedEventData: JSON.stringify(eventData), eventType, properties: {
514
+ parsedEventId,
515
+ parsedEventErrorCode,
516
+ } }));
424
517
  }
425
- const reqId = parsed.id;
426
- const isWSAPIResponse = typeof parsed.id === 'number';
518
+ const isWSAPIResponse = typeof parsedEventId === 'number';
427
519
  const EVENTS_AUTHENTICATED = ['auth'];
428
520
  const EVENTS_RESPONSES = [
429
521
  'subscribe',
@@ -433,9 +525,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
433
525
  'pong',
434
526
  ];
435
527
  // WS API response
436
- // TODO: after
437
528
  if (isWSAPIResponse) {
438
- const retCode = parsed.status;
439
529
  /**
440
530
  * Responses to "subscribe" are quite basic, with no indication of errors (e.g. bad topic):
441
531
  *
@@ -444,86 +534,92 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
444
534
  * id: 1,
445
535
  * };
446
536
  *
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
537
+ * Currently there's no simple way to tell this apart from an actual WS API response with
538
+ * error. So subscribe/unsubscribe requests will simply look like a WS API response internally,
539
+ * but that will not affect usage.
448
540
  *
449
- * Example wsapi error:
541
+ * Unrelated, example wsapi error for reference:
450
542
  *
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
- }
543
+ {
544
+ id: 1,
545
+ status: 400,
546
+ error: {
547
+ code: -1021,
548
+ msg: "Timestamp for this request was 1000ms ahead of the server's time."
549
+ },
550
+ rateLimits: [
551
+ {
552
+ rateLimitType: 'REQUEST_WEIGHT',
553
+ interval: 'MINUTE',
554
+ intervalNum: 1,
555
+ limit: 6000,
556
+ count: 4
557
+ }
558
+ ],
559
+ wsKey: 'mainWSAPI',
560
+ isWSAPIResponse: true
561
+ }
470
562
  */
471
- 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;
563
+ const isError = typeof parsedEventErrorCode === 'number' &&
564
+ parsedEventErrorCode !== 0;
472
565
  // This is the counterpart to getPromiseRefForWSAPIRequest
473
- const promiseRef = [wsKey, reqId].join('_');
474
- if (!reqId) {
566
+ const promiseRef = [wsKey, parsedEventId].join('_');
567
+ if (!parsedEventId) {
475
568
  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:', {
476
569
  wsKey,
477
570
  promiseRef,
478
- parsedEvent: parsed,
571
+ parsedEvent: eventData,
479
572
  });
480
573
  }
481
574
  // WS API Exception
482
575
  if (isError) {
483
576
  try {
484
- this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
577
+ this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, eventData), true);
485
578
  }
486
579
  catch (e) {
487
580
  this.logger.error('Exception trying to reject WSAPI promise', {
488
581
  wsKey,
489
582
  promiseRef,
490
- parsedEvent: parsed,
583
+ parsedEvent: eventData,
584
+ e,
491
585
  });
492
586
  }
493
587
  results.push({
494
588
  eventType: 'exception',
495
- event: parsed,
589
+ event: eventData,
496
590
  isWSAPIResponse: isWSAPIResponse,
497
591
  });
498
592
  return results;
499
593
  }
500
594
  // authenticated
501
- 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
595
+ if ((_b = eventData.result) === null || _b === void 0 ? void 0 : _b.apiKey) {
596
+ // Note: Without this check, this will also trigger "onWsAuthenticated()" for session.status requests
503
597
  if (this.getWsStore().getAuthenticationInProgressPromise(wsKey)) {
504
598
  results.push({
505
599
  eventType: 'authenticated',
506
- event: parsed,
600
+ event: eventData,
507
601
  isWSAPIResponse: isWSAPIResponse,
508
602
  });
509
603
  }
510
604
  }
511
605
  // WS API Success
512
606
  try {
513
- this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
607
+ this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, eventData), true);
514
608
  }
515
609
  catch (e) {
516
610
  this.logger.error('Exception trying to resolve WSAPI promise', {
517
611
  wsKey,
518
612
  promiseRef,
519
- parsedEvent: parsed,
613
+ parsedEvent: eventData,
614
+ e,
520
615
  });
521
616
  }
522
617
  results.push({
523
618
  eventType: 'response',
524
- event: parsed,
619
+ event: Object.assign(Object.assign({}, eventData), { request: this.getCachedMidFlightRequest(wsKey, `${parsedEventId}`) }),
525
620
  isWSAPIResponse: isWSAPIResponse,
526
621
  });
622
+ this.removeCachedMidFlightRequest(wsKey, `${parsedEventId}`);
527
623
  return results;
528
624
  }
529
625
  // Handle incoming event that listen key expired
@@ -533,16 +629,16 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
533
629
  // handle this how?
534
630
  throw new Error('No context found within wsKey - fatal error with expired listen key');
535
631
  }
536
- this.logger.info(`${legacyContext.market} listenKey EXPIRED - attempting to respawn user data stream: ${wsKey}`, parsed);
632
+ this.logger.info(`${legacyContext.market} listenKey EXPIRED - attempting to respawn user data stream: ${wsKey}`, eventData);
537
633
  // Just closing the connection (with the last parameter as true) will handle cleanup and respawn
538
634
  // Automatically leads to triggerCustomReconnectionWorkflow() to handle fresh user data respawn
539
635
  this.getUserDataStreamManager().teardownUserDataListenKey(legacyContext.listenKey, this.getWsStore().getWs(wsKey));
540
636
  this.executeReconnectableClose(wsKey, 'listenKeyExpired');
541
637
  }
542
638
  if (this.options.beautify) {
543
- const beautifiedMessage = this.beautifier.beautifyWsMessage(parsed, eventType, false,
639
+ const beautifiedMessage = this.beautifier.beautifyWsMessage(eventData, eventType, false,
544
640
  // Suffix all events for the beautifier, if market is options
545
- // Options has some conflicting keys with different intentions
641
+ // Options has some conflicting keys with different intentions, so will be suffixed
546
642
  wsKey === 'eoptions' ? 'Options' : '');
547
643
  results.push({
548
644
  eventType: 'formattedMessage',
@@ -551,19 +647,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
551
647
  });
552
648
  // emit an additional event for user data messages
553
649
  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)) {
650
+ if (websocket_util_1.EVENT_TYPES_USER_DATA.includes(eventType)) {
567
651
  results.push({
568
652
  eventType: 'formattedUserDataMessage',
569
653
  event: beautifiedMessage,
@@ -576,17 +660,17 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
576
660
  if (typeof eventType === 'string') {
577
661
  results.push({
578
662
  eventType: 'message',
579
- event: (parsed === null || parsed === void 0 ? void 0 : parsed.data) ? parsed.data : parsed,
663
+ event: (eventData === null || eventData === void 0 ? void 0 : eventData.data) ? eventData.data : eventData,
580
664
  });
581
665
  return results;
582
666
  }
583
667
  // Messages that are a "reply" to a request/command (e.g. subscribe to these topics) typically include the "op" property
584
668
  if (typeof eventType === 'string') {
585
669
  // Failed request
586
- if (parsed.success === false) {
670
+ if (eventData.success === false) {
587
671
  results.push({
588
672
  eventType: 'exception',
589
- event: parsed,
673
+ event: eventData,
590
674
  });
591
675
  return results;
592
676
  }
@@ -594,7 +678,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
594
678
  if (EVENTS_RESPONSES.includes(eventType)) {
595
679
  results.push({
596
680
  eventType: 'response',
597
- event: parsed,
681
+ event: eventData,
598
682
  });
599
683
  return results;
600
684
  }
@@ -602,19 +686,19 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
602
686
  if (EVENTS_AUTHENTICATED.includes(eventType)) {
603
687
  results.push({
604
688
  eventType: 'authenticated',
605
- event: parsed,
689
+ event: eventData,
606
690
  });
607
691
  return results;
608
692
  }
609
- this.logger.error(`!! Unhandled string operation type "${eventType}". Defaulting to "update" channel...`, parsed);
693
+ this.logger.error(`!! Unhandled string operation type "${eventType}". Defaulting to "update" channel... raw event:`, JSON.stringify(parsedEvent));
610
694
  }
611
695
  else {
612
- this.logger.error(`!!!! Unhandled non-string event type "${eventType}". Defaulting to "update" channel...`, parsed);
696
+ this.logger.error(`!!!! Unhandled non-string event type "${eventType}". Defaulting to "update" channel... raw event:`, JSON.stringify(parsedEvent));
613
697
  }
614
698
  // In case of catastrophic failure, fallback to noisy emit update
615
699
  results.push({
616
700
  eventType: 'message',
617
- event: parsed,
701
+ event: eventData,
618
702
  });
619
703
  }
620
704
  catch (e) {
@@ -748,7 +832,6 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
748
832
  }
749
833
  triggerCustomReconnectionWorkflow(legacyWsKey) {
750
834
  return __awaiter(this, void 0, void 0, function* () {
751
- console.log(`triggerCustomReconnectionWorkflow(${legacyWsKey})`);
752
835
  if (legacyWsKey.includes('userData')) {
753
836
  return this.getUserDataStreamManager().triggerUserDataReconnectionWorkflow(legacyWsKey);
754
837
  }
@@ -790,13 +873,13 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
790
873
  ws = yield this.subscribeUsdFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
791
874
  break;
792
875
  case 'usdmTestnet':
793
- ws = yield this.subscribeUsdFuturesUserDataStream('usdmTestnet', forceNewConnection, miscConnectionState);
876
+ ws = yield this.subscribeUsdFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
794
877
  break;
795
878
  case 'coinm':
796
879
  ws = yield this.subscribeCoinFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
797
880
  break;
798
881
  case 'coinmTestnet':
799
- ws = yield this.subscribeCoinFuturesUserDataStream('coinmTestnet', forceNewConnection, miscConnectionState);
882
+ ws = yield this.subscribeCoinFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
800
883
  break;
801
884
  case 'portfoliom':
802
885
  ws = yield this.subscribePortfolioMarginUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
@@ -849,7 +932,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
849
932
  forceNewConnection, miscState) {
850
933
  try {
851
934
  const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.usdmTestnet;
852
- const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet);
935
+ const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions);
853
936
  const { listenKey } = yield restClient.getFuturesUserDataListenKey();
854
937
  const market = isTestnet ? 'usdmTestnet' : 'usdm';
855
938
  return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
@@ -861,6 +944,18 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
861
944
  }
862
945
  });
863
946
  }
947
+ closeUserDataStream() {
948
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'usdm') {
949
+ const wsKeys = this.getWsStore().getKeys();
950
+ const userDataWsKey = wsKeys.find((key) => {
951
+ return key === wsKey || key.startsWith(wsKey + '_userData');
952
+ });
953
+ if (!userDataWsKey) {
954
+ throw new Error(`No matching connection found with wsKey "${wsKey}". Active connections: [${JSON.stringify(wsKey)}]`);
955
+ }
956
+ this.close(userDataWsKey);
957
+ });
958
+ }
864
959
  /**
865
960
  * Subscribe to COIN-M Futures user data stream - listen key is automatically generated.
866
961
  */
@@ -870,7 +965,7 @@ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
870
965
  try {
871
966
  const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.coinmTestnet;
872
967
  const { listenKey } = yield this.restClientCache
873
- .getCOINMRestClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet)
968
+ .getCOINMRestClient(this.getRestClientOptions(), this.options.requestOptions)
874
969
  .getFuturesUserDataListenKey();
875
970
  const market = isTestnet ? 'coinmTestnet' : 'coinm';
876
971
  return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);