binance 2.15.14 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +5 -2
  2. package/lib/coinm-client.js.map +1 -1
  3. package/lib/index.d.ts +10 -4
  4. package/lib/index.js +10 -4
  5. package/lib/index.js.map +1 -1
  6. package/lib/main-client.d.ts +13 -2
  7. package/lib/main-client.js +15 -1
  8. package/lib/main-client.js.map +1 -1
  9. package/lib/portfolio-client.js.map +1 -1
  10. package/lib/types/futures.d.ts +1 -0
  11. package/lib/types/futures.js +3 -3
  12. package/lib/types/futures.js.map +1 -1
  13. package/lib/types/spot.js +4 -4
  14. package/lib/types/spot.js.map +1 -1
  15. package/lib/types/websockets/ws-api-requests.d.ts +7 -0
  16. package/lib/types/websockets/ws-api-requests.js +3 -0
  17. package/lib/types/websockets/ws-api-requests.js.map +1 -0
  18. package/lib/types/websockets/ws-api-responses.d.ts +8 -0
  19. package/lib/types/websockets/ws-api-responses.js +3 -0
  20. package/lib/types/websockets/ws-api-responses.js.map +1 -0
  21. package/lib/types/websockets/ws-api.d.ts +201 -0
  22. package/lib/types/websockets/ws-api.js +29 -0
  23. package/lib/types/websockets/ws-api.js.map +1 -0
  24. package/lib/types/{websockets.d.ts → websockets/ws-events-formatted.d.ts} +4 -410
  25. package/lib/types/websockets/ws-events-formatted.js +3 -0
  26. package/lib/types/websockets/ws-events-formatted.js.map +1 -0
  27. package/lib/types/websockets/ws-events-raw.d.ts +401 -0
  28. package/lib/types/{websockets.js → websockets/ws-events-raw.js} +1 -1
  29. package/lib/types/websockets/ws-events-raw.js.map +1 -0
  30. package/lib/types/websockets/ws-general.d.ts +98 -0
  31. package/lib/types/websockets/ws-general.js +11 -0
  32. package/lib/types/websockets/ws-general.js.map +1 -0
  33. package/lib/usdm-client.js.map +1 -1
  34. package/lib/util/BaseRestClient.d.ts +1 -1
  35. package/lib/util/BaseRestClient.js +1 -1
  36. package/lib/util/BaseRestClient.js.map +1 -1
  37. package/lib/util/BaseWSClient.d.ts +225 -0
  38. package/lib/util/BaseWSClient.js +729 -0
  39. package/lib/util/BaseWSClient.js.map +1 -0
  40. package/lib/util/beautifier-maps.d.ts +151 -0
  41. package/lib/util/beautifier-maps.js +198 -37
  42. package/lib/util/beautifier-maps.js.map +1 -1
  43. package/lib/util/beautifier.d.ts +7 -3
  44. package/lib/util/beautifier.js +40 -11
  45. package/lib/util/beautifier.js.map +1 -1
  46. package/lib/util/browser-support.d.ts +2 -1
  47. package/lib/util/browser-support.js +46 -28
  48. package/lib/util/browser-support.js.map +1 -1
  49. package/lib/util/logger.d.ts +8 -0
  50. package/lib/util/logger.js +17 -0
  51. package/lib/util/logger.js.map +1 -0
  52. package/lib/util/node-support.d.ts +2 -1
  53. package/lib/util/node-support.js +35 -15
  54. package/lib/util/node-support.js.map +1 -1
  55. package/lib/util/requestUtils.d.ts +19 -19
  56. package/lib/util/requestUtils.js +119 -38
  57. package/lib/util/requestUtils.js.map +1 -1
  58. package/lib/util/typeGuards.d.ts +9 -1
  59. package/lib/util/typeGuards.js +59 -34
  60. package/lib/util/typeGuards.js.map +1 -1
  61. package/lib/util/usdm/exchangeInfo.js +2 -3
  62. package/lib/util/usdm/exchangeInfo.js.map +1 -1
  63. package/lib/util/webCryptoAPI.d.ts +14 -0
  64. package/lib/util/webCryptoAPI.js +120 -0
  65. package/lib/util/webCryptoAPI.js.map +1 -0
  66. package/lib/util/websockets/WsStore.d.ts +74 -0
  67. package/lib/util/websockets/WsStore.js +279 -0
  68. package/lib/util/websockets/WsStore.js.map +1 -0
  69. package/lib/util/websockets/WsStore.types.d.ts +53 -0
  70. package/lib/util/websockets/WsStore.types.js +14 -0
  71. package/lib/util/websockets/WsStore.types.js.map +1 -0
  72. package/lib/util/websockets/listen-key-state-cache.d.ts +21 -0
  73. package/lib/util/websockets/listen-key-state-cache.js +80 -0
  74. package/lib/util/websockets/listen-key-state-cache.js.map +1 -0
  75. package/lib/util/websockets/rest-client-cache.d.ts +13 -0
  76. package/lib/util/websockets/rest-client-cache.js +56 -0
  77. package/lib/util/websockets/rest-client-cache.js.map +1 -0
  78. package/lib/util/websockets/user-data-stream-manager.d.ts +54 -0
  79. package/lib/util/websockets/user-data-stream-manager.js +256 -0
  80. package/lib/util/websockets/user-data-stream-manager.js.map +1 -0
  81. package/lib/util/websockets/websocket-util.d.ts +124 -0
  82. package/lib/util/websockets/websocket-util.js +481 -0
  83. package/lib/util/websockets/websocket-util.js.map +1 -0
  84. package/lib/websocket-client-legacy.d.ts +288 -0
  85. package/lib/websocket-client-legacy.js +1113 -0
  86. package/lib/websocket-client-legacy.js.map +1 -0
  87. package/lib/websocket-client.d.ts +228 -168
  88. package/lib/websocket-client.js +927 -834
  89. package/lib/websocket-client.js.map +1 -1
  90. package/package.json +5 -5
  91. package/lib/logger.d.ts +0 -9
  92. package/lib/logger.js +0 -23
  93. package/lib/logger.js.map +0 -1
  94. package/lib/types/websockets.js.map +0 -1
  95. package/lib/util/WsStore.d.ts +0 -57
  96. package/lib/util/WsStore.js +0 -101
  97. package/lib/util/WsStore.js.map +0 -1
  98. package/lib/util/ws-utils.d.ts +0 -7
  99. package/lib/util/ws-utils.js +0 -16
  100. package/lib/util/ws-utils.js.map +0 -1
@@ -1,27 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -31,812 +8,1097 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
32
9
  });
33
10
  };
34
- var __rest = (this && this.__rest) || function (s, e) {
35
- var t = {};
36
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
37
- t[p] = s[p];
38
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
39
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
40
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
41
- t[p[i]] = s[p[i]];
42
- }
43
- return t;
44
- };
45
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
46
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
13
  };
48
14
  Object.defineProperty(exports, "__esModule", { value: true });
49
- exports.WebsocketClient = exports.parseRawWsMessage = void 0;
50
- const events_1 = require("events");
51
- const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
52
- const coinm_client_1 = require("./coinm-client");
53
- const logger_1 = require("./logger");
54
- const main_client_1 = require("./main-client");
55
- const usdm_client_1 = require("./usdm-client");
15
+ exports.WebsocketClient = void 0;
16
+ const BaseWSClient_1 = require("./util/BaseWSClient");
56
17
  const beautifier_1 = __importDefault(require("./util/beautifier"));
57
18
  const requestUtils_1 = require("./util/requestUtils");
58
- const ws_utils_1 = require("./util/ws-utils");
59
- const WsStore_1 = __importStar(require("./util/WsStore"));
60
- const wsBaseEndpoints = {
61
- spot: 'wss://stream.binance.com:9443',
62
- margin: 'wss://stream.binance.com:9443',
63
- isolatedMargin: 'wss://stream.binance.com:9443',
64
- usdm: 'wss://fstream.binance.com',
65
- usdmTestnet: 'wss://stream.binancefuture.com',
66
- coinm: 'wss://dstream.binance.com',
67
- coinmTestnet: 'wss://dstream.binancefuture.com',
68
- options: 'wss://vstream.binance.com',
69
- optionsTestnet: 'wss://testnetws.binanceops.com',
70
- };
71
- const loggerCategory = { category: 'binance-ws' };
72
- function throwUnhandledSwitch(x, msg) {
73
- throw new Error(msg);
74
- }
75
- function parseEventTypeFromMessage(parsedMsg) {
76
- var _a;
77
- if (parsedMsg === null || parsedMsg === void 0 ? void 0 : parsedMsg.e) {
78
- return parsedMsg.e;
79
- }
80
- if (Array.isArray(parsedMsg) && parsedMsg.length) {
81
- return (_a = parsedMsg[0]) === null || _a === void 0 ? void 0 : _a.e;
82
- }
83
- return;
84
- }
19
+ const typeGuards_1 = require("./util/typeGuards");
20
+ const webCryptoAPI_1 = require("./util/webCryptoAPI");
21
+ const rest_client_cache_1 = require("./util/websockets/rest-client-cache");
22
+ const user_data_stream_manager_1 = require("./util/websockets/user-data-stream-manager");
23
+ const websocket_util_1 = require("./util/websockets/websocket-util");
24
+ const WS_LOGGER_CATEGORY = { category: 'binance-ws' };
85
25
  /**
86
- * Try to resolve event.data. Example circumstance: {"stream":"!forceOrder@arr","data":{"e":"forceOrder","E":1634653599186,"o":{"s":"IOTXUSDT","S":"SELL","o":"LIMIT","f":"IOC","q":"3661","p":"0.06606","ap":"0.06669","X":"FILLED","l":"962","z":"3661","T":1634653599180}}}
26
+ * Multiplex Node.js, JavaScript & TypeScript Websocket Client for all of Binance's available WebSockets.
27
+ *
28
+ * When possible, it will subscribe to all requested topics on a single websocket connection. A list of
29
+ * all available streams can be seen in the WS_KEY_URL_MAP found in util/websockets/websocket-util.ts.
30
+ *
31
+ * Connectivity is automatically maintained. If disconnected, the WebsocketClient will automatically
32
+ * clean out the old dead connection, respawn a fresh one and resubscribe to all the requested topics.
33
+ *
34
+ * If any connection is reconnected, the WS client will:
35
+ * - Emit the "reconnecting" event when the process begins.
36
+ * - Emit the "reconnected" event, when the process has completed. When this event arrives, it is often a
37
+ * good time to execute any synchorisation workflow (e.g. via the REST API) if any information was missed
38
+ * while disconnected.
39
+ *
40
+ * User data streams will use a dedicated connection per stream for increased resilience.
87
41
  */
88
- function parseRawWsMessage(event) {
89
- if (typeof event === 'string') {
90
- const parsedEvent = JSON.parse(event);
91
- if (parsedEvent.data) {
92
- if (typeof parsedEvent.data === 'string') {
93
- return parseRawWsMessage(parsedEvent.data);
94
- }
95
- return parsedEvent.data;
96
- }
97
- }
98
- if (event === null || event === void 0 ? void 0 : event.data) {
99
- return JSON.parse(event.data);
100
- }
101
- return event;
102
- }
103
- exports.parseRawWsMessage = parseRawWsMessage;
104
- class WebsocketClient extends events_1.EventEmitter {
42
+ class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
105
43
  constructor(options, logger) {
106
- super();
107
- this.logger = logger || logger_1.DefaultLogger;
108
- this.wsStore = new WsStore_1.default(this.logger);
109
- this.beautifier = new beautifier_1.default();
110
- this.restClients = {};
111
- this.options = Object.assign({ pongTimeout: 7500, pingInterval: 10000, reconnectTimeout: 500 }, options);
112
- this.listenKeyStateStore = {};
113
- this.wsUrlKeyMap = {};
114
- // add default error handling so this doesn't crash node (if the user didn't set a handler)
115
- this.on('error', () => { });
44
+ super(options, logger);
45
+ this.restClientCache = new rest_client_cache_1.RestClientCache();
46
+ this.beautifier = new beautifier_1.default({
47
+ warnKeyMissingInMap: true,
48
+ });
49
+ this.respawnTimeoutCache = {};
50
+ this.userDataStreamManager = new user_data_stream_manager_1.UserDataStreamManager({
51
+ logger: this.logger,
52
+ wsStore: this.getWsStore(),
53
+ restClientCache: this.restClientCache,
54
+ // fn pointers:
55
+ respawnUserDataFn: (wsKey, market, context = {}) => this.respawnUserDataStream(wsKey, market, context),
56
+ getWsUrlFn: (wsKey, connectionType) => this.getWsUrl(wsKey, connectionType),
57
+ getRestClientOptionsFn: () => this.getRestClientOptions(),
58
+ getWsClientOptionsfn: () => this.options,
59
+ closeWsFn: (wsKey, force) => this.close(wsKey, force),
60
+ connectFn: (wsKey, customUrl, throwOnError) => this.connect(wsKey, customUrl, throwOnError),
61
+ });
62
+ }
63
+ getUserDataStreamManager() {
64
+ return this.userDataStreamManager;
116
65
  }
117
66
  getRestClientOptions() {
118
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 });
119
68
  }
120
- connectToWsUrl(url, wsKey, forceNewConnection) {
121
- const wsRefKey = wsKey || url;
122
- const oldWs = this.wsStore.getWs(wsRefKey);
123
- if (oldWs && this.wsStore.isWsOpen(wsRefKey) && !forceNewConnection) {
124
- this.logger.silly('connectToWsUrl(): Returning existing open WS connection', Object.assign(Object.assign({}, loggerCategory), { wsRefKey }));
125
- return oldWs;
126
- }
127
- this.logger.silly(`connectToWsUrl(): Opening WS connection to URL: ${url}`, Object.assign(Object.assign({}, loggerCategory), { wsRefKey }));
128
- const _a = this.options.wsOptions || {}, { protocols = [] } = _a, wsOptions = __rest(_a, ["protocols"]);
129
- const ws = new isomorphic_ws_1.default(url, protocols, wsOptions);
130
- this.wsUrlKeyMap[url] = wsRefKey;
131
- if (typeof ws.on === 'function') {
132
- ws.on('ping', (event) => this.onWsPing(event, wsRefKey, ws, 'event'));
133
- ws.on('pong', (event) => this.onWsPong(event, wsRefKey, 'event'));
134
- }
135
- ws.onopen = (event) => this.onWsOpen(event, wsRefKey, url);
136
- ws.onerror = (event) => this.parseWsError('WS Error Event', event, wsRefKey, url);
137
- ws.onclose = (event) => this.onWsClose(event, wsRefKey, ws, url);
138
- ws.onmessage = (event) => this.onWsMessage(event, wsRefKey, 'function');
139
- // Not sure these work in the browser, the traditional event listeners are required for ping/pong frames in node
140
- ws.onping = (event) => this.onWsPing(event, wsRefKey, ws, 'function');
141
- ws.onpong = (event) => this.onWsPong(event, wsRefKey, 'function');
142
- // Add ws connection with key to store
143
- this.wsStore.setWs(wsRefKey, ws);
144
- ws.wsKey = wsRefKey;
145
- return ws;
146
- }
147
- tryWsSend(wsKey, wsMessage) {
69
+ /**
70
+ * 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?
72
+ */
73
+ connectAll() {
74
+ return this.connectPublic();
75
+ }
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
+ /**
87
+ * Request connection to all public websockets in prod (spot, margin, futures, options). Overkill if
88
+ * you're only working with one product group.
89
+ */
90
+ connectPublic() {
91
+ return [
92
+ this.connect(websocket_util_1.WS_KEY_MAP.main),
93
+ this.connect(websocket_util_1.WS_KEY_MAP.usdm),
94
+ this.connect(websocket_util_1.WS_KEY_MAP.coinm),
95
+ this.connect(websocket_util_1.WS_KEY_MAP.eoptions),
96
+ ];
97
+ }
98
+ connectPrivate() {
99
+ 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
+ return;
108
+ });
109
+ }
110
+ /**
111
+ * Request subscription to one or more topics. Pass topics as either an array of strings,
112
+ * or array of objects (if the topic has parameters).
113
+ *
114
+ * Objects should be formatted as {topic: string, params: object, category: CategoryV5}.
115
+ *
116
+ * - Subscriptions are automatically routed to the correct websocket connection.
117
+ * - Authentication/connection is automatic.
118
+ * - Resubscribe after network issues is automatic.
119
+ *
120
+ * Call `unsubscribe(topics)` to remove topics
121
+ */
122
+ subscribe(requests, wsKey) {
123
+ const topicRequests = Array.isArray(requests) ? requests : [requests];
124
+ const normalisedTopicRequests = (0, websocket_util_1.getNormalisedTopicRequests)(topicRequests);
125
+ return this.subscribeTopicsForWsKey(normalisedTopicRequests, wsKey);
126
+ }
127
+ /**
128
+ * Unsubscribe from one or more topics. Similar to subscribe() but in reverse.
129
+ *
130
+ * - Requests are automatically routed to the correct websocket connection.
131
+ * - These topics will be removed from the topic cache, so they won't be subscribed to again.
132
+ */
133
+ unsubscribe(requests, wsKey) {
134
+ const topicRequests = Array.isArray(requests) ? requests : [requests];
135
+ const normalisedTopicRequests = (0, websocket_util_1.getNormalisedTopicRequests)(topicRequests);
136
+ return this.unsubscribeTopicsForWsKey(normalisedTopicRequests, wsKey);
137
+ }
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
+ sendWSAPIRequest(wsKey, operation, params) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ //WsAPIOperationResponseMap[TWSOperation]> {
160
+ /**
161
+ * Spot: https://developers.binance.com/docs/binance-spot-api-docs/web-socket-api/general-api-information
162
+ */
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');
168
+ const request = {
169
+ id: this.getNewRequestId(),
170
+ method: operation,
171
+ params: Object.assign({ timestamp: Date.now(), recvWindow: this.options.recvWindow }, params),
172
+ };
173
+ if ((0, requestUtils_1.requiresWSAPINewClientOID)(request, wsKey)) {
174
+ (0, requestUtils_1.validateWSAPINewClientOID)(request, wsKey);
175
+ }
176
+ // Sign, if needed
177
+ const signedEvent = yield this.signWSAPIRequest(request);
178
+ // 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);
181
+ // this.logger.trace(
182
+ // `sendWSAPIRequest(): sending raw request: ${JSON.stringify(signedEvent)} with promiseRef(${promiseRef})`,
183
+ // );
184
+ // Send event
185
+ this.tryWsSend(wsKey, JSON.stringify(signedEvent));
186
+ this.logger.trace(`sendWSAPIRequest(): sent "${operation}" event with promiseRef(${promiseRef})`);
187
+ // Return deferred promise, so caller can await this call
188
+ return deferredPromise.promise;
189
+ });
190
+ }
191
+ /**
192
+ *
193
+ *
194
+ * Internal methods - not intended for public use
195
+ *
196
+ *
197
+ */
198
+ /**
199
+ * @returns The WS URL to connect to for this WS key
200
+ */
201
+ getWsUrl(wsKey_1) {
202
+ return __awaiter(this, arguments, void 0, function* (wsKey, connectionType = 'market') {
203
+ const wsBaseURL = (0, websocket_util_1.getWsUrl)(wsKey, this.options, this.logger) +
204
+ (0, websocket_util_1.getWsURLSuffix)(wsKey, connectionType);
205
+ return wsBaseURL;
206
+ });
207
+ }
208
+ signMessage(paramsStr, secret, method, algorithm) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ if (typeof this.options.customSignMessageFn === 'function') {
211
+ return this.options.customSignMessageFn(paramsStr, secret);
212
+ }
213
+ return yield (0, webCryptoAPI_1.signMessage)(paramsStr, secret, method, algorithm);
214
+ });
215
+ }
216
+ signWSAPIRequest(requestEvent) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ // Not needed for Binance. Auth happens only on connection open, automatically.
219
+ // Faster than performing auth for every request
220
+ return requestEvent;
221
+ });
222
+ }
223
+ getWsAuthRequestEvent(wsKey) {
224
+ return __awaiter(this, void 0, void 0, function* () {
225
+ try {
226
+ // Note: Only Ed25519 keys are supported for this feature.
227
+ // 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
+ // 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
+ // 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;
232
+ const strictParamValidation = true;
233
+ const encodeValues = true;
234
+ const filterUndefinedParams = true;
235
+ const authParams = {
236
+ apiKey: this.options.api_key,
237
+ timestamp,
238
+ };
239
+ const serialisedParams = (0, requestUtils_1.serialiseParams)(authParams, strictParamValidation, encodeValues, filterUndefinedParams);
240
+ 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
+ const request = {
243
+ id: this.getNewRequestId(),
244
+ method: 'session.logon',
245
+ params: Object.assign(Object.assign({}, authParams), { signature }),
246
+ };
247
+ return request;
248
+ }
249
+ catch (e) {
250
+ this.logger.error(e, Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
251
+ throw e;
252
+ }
253
+ });
254
+ }
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
+ sendPingEvent(wsKey) {
148
284
  try {
149
- this.logger.silly('Sending upstream ws message: ', Object.assign(Object.assign({}, loggerCategory), { wsMessage,
150
- wsKey }));
285
+ // this.logger.trace(`Sending upstream ping: `, { ...loggerCategory, wsKey });
151
286
  if (!wsKey) {
152
287
  throw new Error('No wsKey provided');
153
288
  }
154
- const ws = this.getWs(wsKey);
289
+ const ws = this.getWsStore().getWs(wsKey);
155
290
  if (!ws) {
156
291
  throw new Error(`No active websocket connection exists for wsKey: ${wsKey}`);
157
292
  }
158
- ws.send(wsMessage);
293
+ // Binance allows unsolicited pongs, so we send both (though we expect a pong in response to our ping if the connection is still alive)
294
+ if (ws.readyState === 1) {
295
+ ws.ping();
296
+ ws.pong();
297
+ }
298
+ else {
299
+ this.logger.trace('WS ready state not open - refusing to send WS ping', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, readyState: ws === null || ws === void 0 ? void 0 : ws.readyState }));
300
+ }
159
301
  }
160
302
  catch (e) {
161
- this.logger.error('Failed to send WS message', Object.assign(Object.assign({}, loggerCategory), { wsMessage,
162
- wsKey, exception: e }));
303
+ this.logger.error('Failed to send WS ping', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, exception: e }));
163
304
  }
164
305
  }
165
- tryWsPing(wsKey) {
306
+ sendPongEvent(wsKey) {
307
+ // ws.pong();
166
308
  try {
167
- // this.logger.silly(`Sending upstream ping: `, { ...loggerCategory, wsKey });
309
+ // this.logger.trace(`Sending upstream ping: `, { ...loggerCategory, wsKey });
168
310
  if (!wsKey) {
169
311
  throw new Error('No wsKey provided');
170
312
  }
171
- const ws = this.getWs(wsKey);
313
+ const ws = this.getWsStore().getWs(wsKey);
172
314
  if (!ws) {
173
315
  throw new Error(`No active websocket connection exists for wsKey: ${wsKey}`);
174
316
  }
175
317
  // Binance allows unsolicited pongs, so we send both (though we expect a pong in response to our ping if the connection is still alive)
176
318
  if (ws.readyState === 1) {
177
- ws.ping();
178
319
  ws.pong();
179
320
  }
180
321
  else {
181
- this.logger.silly('WS ready state not open - refusing to send WS ping', Object.assign(Object.assign({}, loggerCategory), { wsKey, readyState: ws === null || ws === void 0 ? void 0 : ws.readyState }));
322
+ this.logger.trace('WS ready state not open - refusing to send WS pong', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, readyState: ws === null || ws === void 0 ? void 0 : ws.readyState }));
182
323
  }
183
324
  }
184
325
  catch (e) {
185
- this.logger.error('Failed to send WS ping', Object.assign(Object.assign({}, loggerCategory), { wsKey, exception: e }));
326
+ this.logger.error('Failed to send WS pong', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, exception: e }));
186
327
  }
187
328
  }
188
- onWsOpen(ws, wsKey, wsUrl) {
189
- this.logger.silly(`onWsOpen(): ${wsUrl} : ${wsKey}`);
190
- if (this.wsStore.isConnectionState(wsKey, WsStore_1.WsConnectionStateEnum.RECONNECTING)) {
191
- this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
192
- this.emit('reconnected', { wsKey, ws });
193
- }
194
- else {
195
- this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
196
- this.emit('open', { wsKey, ws });
197
- }
198
- this.setWsState(wsKey, WsStore_1.WsConnectionStateEnum.CONNECTED);
199
- const topics = [...this.wsStore.getTopics(wsKey)];
200
- if (topics.length) {
201
- this.requestSubscribeTopics(wsKey, topics);
202
- }
203
- if (!this.options.disableHeartbeat) {
204
- const wsState = this.wsStore.get(wsKey, true);
205
- if (wsState.activePingTimer) {
206
- clearInterval(wsState.activePingTimer);
207
- }
208
- wsState.activePingTimer = setInterval(() => this.sendPing(wsKey, wsUrl), this.options.pingInterval);
209
- }
210
- }
211
- onWsClose(event, wsKey, ws, wsUrl) {
212
- var _a;
213
- const wsConnectionState = this.wsStore.getConnectionState(wsKey);
214
- const { market, listenKey, isUserData } = (0, requestUtils_1.getContextFromWsKey)(wsKey);
215
- this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, loggerCategory), { wsKey, eventCloseCode: (_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a._closeCode, wsConnectionState,
216
- isUserData,
217
- listenKey,
218
- market }));
219
- // Clear any timers before we initiate revival
220
- this.clearTimers(wsKey);
221
- // User data sockets include the listen key. To prevent accummulation in memory we should clean up old disconnected states
222
- if (isUserData) {
223
- this.wsStore.delete(wsKey);
224
- if (listenKey) {
225
- this.clearUserDataKeepAliveTimer(listenKey);
226
- }
227
- }
228
- if (wsConnectionState !== WsStore_1.WsConnectionStateEnum.CLOSING) {
229
- this.reconnectWithDelay(wsKey, this.options.reconnectTimeout, wsUrl);
230
- this.emit('reconnecting', { wsKey, event, ws });
231
- }
232
- else {
233
- this.setWsState(wsKey, WsStore_1.WsConnectionStateEnum.INITIAL);
234
- this.emit('close', { wsKey, event, ws });
235
- }
329
+ /** Force subscription requests to be sent in smaller batches, if a number is returned */
330
+ getMaxTopicsPerSubscribeEvent(wsKey) {
331
+ return (0, websocket_util_1.getMaxTopicsPerSubscribeEvent)(wsKey);
236
332
  }
237
- onWsMessage(event, wsKey, source) {
238
- try {
239
- this.clearPongTimer(wsKey);
240
- const msg = parseRawWsMessage(event);
241
- // Edge case where raw event does not include event type, detect using wsKey and mutate msg.e
242
- (0, requestUtils_1.appendEventIfMissing)(msg, wsKey);
243
- (0, requestUtils_1.appendEventMarket)(msg, wsKey);
244
- const eventType = parseEventTypeFromMessage(msg);
245
- if (eventType) {
246
- this.emit('message', msg);
247
- if (eventType === 'listenKeyExpired') {
248
- const { market } = (0, requestUtils_1.getContextFromWsKey)(wsKey);
249
- this.logger.info(`${market} listenKey expired - attempting to respawn user data stream: ${wsKey}`);
250
- // Just closing the connection (with the last parameter as true) will handle cleanup and respawn
251
- const shouldTriggerReconnect = true;
252
- this.close(wsKey, shouldTriggerReconnect);
253
- }
254
- if (this.options.beautify) {
255
- const beautifiedMessage = this.beautifier.beautifyWsMessage(msg, eventType, false);
256
- this.emit('formattedMessage', beautifiedMessage);
257
- // emit a separate event for user data messages
258
- if (!Array.isArray(beautifiedMessage)) {
259
- if ([
260
- 'balanceUpdate',
261
- 'executionReport',
262
- 'listStatus',
263
- 'listenKeyExpired',
264
- 'outboundAccountPosition',
265
- 'ACCOUNT_CONFIG_UPDATE',
266
- 'ACCOUNT_UPDATE',
267
- 'MARGIN_CALL',
268
- 'ORDER_TRADE_UPDATE',
269
- 'TRADE_LITE',
270
- 'CONDITIONAL_ORDER_TRIGGER_REJECT',
271
- ].includes(eventType)) {
272
- this.emit('formattedUserDataMessage', beautifiedMessage);
273
- }
274
- }
333
+ /**
334
+ * @returns one or more correctly structured request events for performing a operations over WS. This can vary per exchange spec.
335
+ */
336
+ getWsRequestEvents(wsKey, operation, requests) {
337
+ return __awaiter(this, void 0, void 0, function* () {
338
+ const wsRequestEvents = [];
339
+ const wsRequestBuildingErrors = [];
340
+ switch (wsKey) {
341
+ default: {
342
+ const topics = requests.map((r) => r.topic);
343
+ // Previously used to track topics in a request. Keeping this for subscribe/unsubscribe requests, no need for incremental values
344
+ const req_id = this.getNewRequestId();
345
+ const wsEvent = {
346
+ method: operation,
347
+ params: topics,
348
+ id: req_id,
349
+ };
350
+ const midflightWsEvent = {
351
+ requestKey: wsEvent.id,
352
+ requestEvent: wsEvent,
353
+ };
354
+ wsRequestEvents.push(Object.assign({}, midflightWsEvent));
355
+ break;
275
356
  }
276
- return;
357
+ // default: {
358
+ // throw neverGuard(wsKey, `Unhandled wsKey "${wsKey}"`);
359
+ // }
277
360
  }
278
- if (msg.result !== undefined) {
279
- this.emit('reply', {
280
- type: event.type,
281
- data: msg,
282
- wsKey,
283
- });
284
- return;
361
+ if (wsRequestBuildingErrors.length) {
362
+ const label = wsRequestBuildingErrors.length === requests.length ? 'all' : 'some';
363
+ this.logger.error(`Failed to build/send ${wsRequestBuildingErrors.length} event(s) for ${label} WS requests due to exceptions`, Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsRequestBuildingErrors, wsRequestBuildingErrorsStringified: JSON.stringify(wsRequestBuildingErrors, null, 2) }));
285
364
  }
286
- this.logger.warning('Bug? Unhandled ws message event type. Check if appendEventIfMissing needs to parse wsKey.', Object.assign(Object.assign({}, loggerCategory), { parsedMessage: JSON.stringify(msg), rawEvent: event, wsKey,
287
- source }));
288
- }
289
- catch (e) {
290
- this.logger.error('Exception parsing ws message: ', Object.assign(Object.assign({}, loggerCategory), { rawEvent: event, wsKey, error: e, source }));
291
- this.emit('error', { wsKey, error: e, rawEvent: event, source });
292
- }
293
- }
294
- sendPing(wsKey, wsUrl) {
295
- this.clearPongTimer(wsKey);
296
- this.logger.silly('Sending ping', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
297
- this.tryWsPing(wsKey);
298
- this.wsStore.get(wsKey, true).activePongTimer = setTimeout(() => this.executeReconnectableClose(wsKey, 'Pong timeout', wsUrl), this.options.pongTimeout);
365
+ return wsRequestEvents;
366
+ });
299
367
  }
300
- onWsPing(event, wsKey, ws, source) {
301
- this.logger.silly('Received ping, sending pong frame', Object.assign(Object.assign({}, loggerCategory), { wsKey,
302
- source }));
303
- ws.pong();
368
+ getPrivateWSKeys() {
369
+ return websocket_util_1.WS_AUTH_ON_CONNECT_KEYS;
304
370
  }
305
- onWsPong(event, wsKey, source) {
306
- this.logger.silly('Received pong, clearing pong timer', Object.assign(Object.assign({}, loggerCategory), { wsKey,
307
- source }));
308
- this.clearPongTimer(wsKey);
371
+ isAuthOnConnectWsKey(wsKey) {
372
+ return websocket_util_1.WS_AUTH_ON_CONNECT_KEYS.includes(wsKey);
309
373
  }
310
374
  /**
311
- * Closes a connection, if it's even open. If open, this will trigger a reconnect asynchronously.
312
- * If closed, trigger a reconnect immediately
375
+ * Determines if a topic is for a private channel, using a hardcoded list of strings
313
376
  */
314
- executeReconnectableClose(wsKey, reason, wsUrl) {
315
- this.logger.info(`${reason} - closing socket to reconnect`, Object.assign(Object.assign({}, loggerCategory), { wsKey,
316
- reason }));
317
- const wasOpen = this.wsStore.isWsOpen(wsKey);
318
- (0, ws_utils_1.safeTerminateWs)(this.getWs(wsKey));
319
- this.clearPingTimer(wsKey);
320
- this.clearPongTimer(wsKey);
321
- if (!wasOpen) {
322
- this.logger.info(`${reason} - socket already closed - trigger immediate reconnect`, Object.assign(Object.assign({}, loggerCategory), { wsKey,
323
- reason }));
324
- this.reconnectWithDelay(wsKey, this.options.reconnectTimeout, wsUrl);
325
- }
326
- }
327
- close(wsKey, shouldReconnectAfterClose) {
377
+ isPrivateTopicRequest(request) {
328
378
  var _a;
329
- this.logger.info('Closing connection', Object.assign(Object.assign({}, loggerCategory), { wsKey, willReconnect: shouldReconnectAfterClose }));
330
- this.setWsState(wsKey, shouldReconnectAfterClose
331
- ? WsStore_1.WsConnectionStateEnum.RECONNECTING
332
- : WsStore_1.WsConnectionStateEnum.CLOSING);
333
- this.clearTimers(wsKey);
334
- (_a = this.getWs(wsKey)) === null || _a === void 0 ? void 0 : _a.close();
335
- const { listenKey } = (0, requestUtils_1.getContextFromWsKey)(wsKey);
336
- if (listenKey) {
337
- this.teardownUserDataListenKey(listenKey, this.getWs(wsKey));
338
- }
339
- else {
340
- (0, ws_utils_1.safeTerminateWs)(this.getWs(wsKey));
379
+ const topicName = (_a = request === null || request === void 0 ? void 0 : request.topic) === null || _a === void 0 ? void 0 : _a.toLowerCase();
380
+ if (!topicName) {
381
+ return false;
341
382
  }
383
+ return (0, websocket_util_1.isPrivateWsTopic)(topicName);
342
384
  }
343
- closeAll(shouldReconnectAfterClose) {
344
- const keys = this.wsStore.getKeys();
345
- this.logger.info(`Closing all ws connections: ${keys}`);
346
- keys.forEach((key) => {
347
- this.close(key, shouldReconnectAfterClose);
348
- });
385
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
+ isWsPing(msg) {
387
+ if (!msg) {
388
+ return false;
389
+ }
390
+ // For binance, all ping/pong events are frame events
391
+ return false;
349
392
  }
350
- closeWs(ws, shouldReconnectAfterClose) {
351
- const wsKey = this.wsUrlKeyMap[ws.url] || (ws === null || ws === void 0 ? void 0 : ws.wsKey);
352
- if (!wsKey) {
353
- throw new Error('Cannot close websocket as it has no known wsKey attached.');
393
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
394
+ isWsPong(msg) {
395
+ if (!msg) {
396
+ return false;
354
397
  }
355
- return this.close(wsKey, shouldReconnectAfterClose);
398
+ // For binance, all ping/pong events are frame events
399
+ return false;
356
400
  }
357
- parseWsError(context, error, wsKey, wsUrl) {
358
- this.logger.error(context, Object.assign(Object.assign({}, loggerCategory), { wsKey, error }));
359
- if (!error.message) {
360
- this.logger.error(`${context} due to unexpected error: `, error);
361
- this.emit('error', { error, wsKey, wsUrl });
362
- return;
401
+ /**
402
+ * Abstraction called to sort ws events into emittable event types (response to a request, data update, etc)
403
+ */
404
+ resolveEmittableEvents(wsKey, event) {
405
+ var _a, _b, _c;
406
+ const results = [];
407
+ 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);
411
+ // Some events don't include the topic (event name)
412
+ // This tries to extract and append it, using available context
413
+ (0, requestUtils_1.appendEventIfMissing)(parsed, wsKey);
414
+ const legacyContext = (0, websocket_util_1.getLegacyWsKeyContext)(wsKey);
415
+ if (legacyContext) {
416
+ parsed.wsMarket = legacyContext.market;
417
+ }
418
+ // const eventType = eventType; //parsed?.stream;
419
+ // const eventOperation = parsed?.op;
420
+ const traceEmittable = false;
421
+ if (traceEmittable) {
422
+ this.logger.trace('resolveEmittableEvents', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey,
423
+ eventType, parsed: JSON.stringify(parsed) }));
424
+ }
425
+ const reqId = parsed.id;
426
+ const isWSAPIResponse = typeof parsed.id === 'number';
427
+ const EVENTS_AUTHENTICATED = ['auth'];
428
+ const EVENTS_RESPONSES = [
429
+ 'subscribe',
430
+ 'unsubscribe',
431
+ 'COMMAND_RESP',
432
+ 'ping',
433
+ 'pong',
434
+ ];
435
+ // WS API response
436
+ // TODO: after
437
+ if (isWSAPIResponse) {
438
+ const retCode = parsed.status;
439
+ /**
440
+ * Responses to "subscribe" are quite basic, with no indication of errors (e.g. bad topic):
441
+ *
442
+ * {
443
+ * result: null,
444
+ * id: 1,
445
+ * };
446
+ *
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
448
+ *
449
+ * Example wsapi error:
450
+ *
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
363
469
  }
364
- switch (error.message) {
365
- case 'Unexpected server response: 401':
366
- this.logger.error(`${context} due to 401 authorization failure.`, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
367
- break;
368
- default:
369
- if (this.wsStore.getConnectionState(wsKey) !==
370
- WsStore_1.WsConnectionStateEnum.CLOSING) {
371
- this.logger.error(`${context} due to unexpected response error: "${(error === null || error === void 0 ? void 0 : error.msg) || (error === null || error === void 0 ? void 0 : error.message) || error}"`, Object.assign(Object.assign({}, loggerCategory), { wsKey, error }));
372
- this.executeReconnectableClose(wsKey, 'unhandled onWsError', wsUrl);
470
+ */
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;
472
+ // This is the counterpart to getPromiseRefForWSAPIRequest
473
+ const promiseRef = [wsKey, reqId].join('_');
474
+ if (!reqId) {
475
+ 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
+ wsKey,
477
+ promiseRef,
478
+ parsedEvent: parsed,
479
+ });
373
480
  }
374
- else {
375
- this.logger.info(`${wsKey} socket forcefully closed. Will not reconnect.`);
481
+ // WS API Exception
482
+ if (isError) {
483
+ try {
484
+ this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
485
+ }
486
+ catch (e) {
487
+ this.logger.error('Exception trying to reject WSAPI promise', {
488
+ wsKey,
489
+ promiseRef,
490
+ parsedEvent: parsed,
491
+ });
492
+ }
493
+ results.push({
494
+ eventType: 'exception',
495
+ event: parsed,
496
+ isWSAPIResponse: isWSAPIResponse,
497
+ });
498
+ return results;
376
499
  }
377
- break;
378
- }
379
- this.emit('error', { error, wsKey, wsUrl });
380
- }
381
- reconnectWithDelay(wsKey, connectionDelayMs, wsUrl) {
382
- var _a;
383
- this.clearTimers(wsKey);
384
- if (this.wsStore.getConnectionState(wsKey) !==
385
- WsStore_1.WsConnectionStateEnum.CONNECTING) {
386
- this.setWsState(wsKey, WsStore_1.WsConnectionStateEnum.RECONNECTING);
387
- }
388
- this.logger.info('Reconnecting to websocket with delay...', Object.assign(Object.assign({}, loggerCategory), { wsKey,
389
- connectionDelayMs }));
390
- if ((_a = this.wsStore.get(wsKey)) === null || _a === void 0 ? void 0 : _a.activeReconnectTimer) {
391
- this.clearReconnectTimer(wsKey);
392
- }
393
- this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
394
- this.clearReconnectTimer(wsKey);
395
- if (wsKey.includes('userData')) {
396
- const { market, symbol, isTestnet } = (0, requestUtils_1.getContextFromWsKey)(wsKey);
397
- this.logger.info('Reconnecting to user data stream', Object.assign(Object.assign({}, loggerCategory), { wsKey,
398
- market,
399
- symbol }));
400
- // We'll set a new one once the new stream respawns, with a diff listenKey in the key
401
- this.wsStore.delete(wsKey);
402
- this.respawnUserDataStream(market, symbol, isTestnet);
403
- return;
500
+ // 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
503
+ if (this.getWsStore().getAuthenticationInProgressPromise(wsKey)) {
504
+ results.push({
505
+ eventType: 'authenticated',
506
+ event: parsed,
507
+ isWSAPIResponse: isWSAPIResponse,
508
+ });
509
+ }
510
+ }
511
+ // WS API Success
512
+ try {
513
+ this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
514
+ }
515
+ catch (e) {
516
+ this.logger.error('Exception trying to resolve WSAPI promise', {
517
+ wsKey,
518
+ promiseRef,
519
+ parsedEvent: parsed,
520
+ });
521
+ }
522
+ results.push({
523
+ eventType: 'response',
524
+ event: parsed,
525
+ isWSAPIResponse: isWSAPIResponse,
526
+ });
527
+ return results;
404
528
  }
405
- this.logger.info('Reconnecting to public websocket', Object.assign(Object.assign({}, loggerCategory), { wsKey,
406
- wsUrl }));
407
- this.connectToWsUrl(wsUrl, wsKey);
408
- }, connectionDelayMs);
409
- }
410
- clearTimers(wsKey) {
411
- this.clearPingTimer(wsKey);
412
- this.clearPongTimer(wsKey);
413
- this.clearReconnectTimer(wsKey);
414
- }
415
- // Send a ping at intervals
416
- clearPingTimer(wsKey) {
417
- const wsState = this.wsStore.get(wsKey);
418
- if (wsState === null || wsState === void 0 ? void 0 : wsState.activePingTimer) {
419
- clearInterval(wsState.activePingTimer);
420
- wsState.activePingTimer = undefined;
421
- }
422
- }
423
- // Expect a pong within a time limit
424
- clearPongTimer(wsKey) {
425
- const wsState = this.wsStore.get(wsKey);
426
- if (wsState === null || wsState === void 0 ? void 0 : wsState.activePongTimer) {
427
- clearTimeout(wsState.activePongTimer);
428
- wsState.activePongTimer = undefined;
429
- }
430
- }
431
- // Timer tracking that a reconnect is about to happen / in progress
432
- clearReconnectTimer(wsKey) {
433
- const wsState = this.wsStore.get(wsKey);
434
- if (wsState === null || wsState === void 0 ? void 0 : wsState.activeReconnectTimer) {
435
- clearTimeout(wsState.activeReconnectTimer);
436
- wsState.activeReconnectTimer = undefined;
437
- }
438
- }
439
- clearUserDataKeepAliveTimer(listenKey) {
440
- const state = this.listenKeyStateStore[listenKey];
441
- if (!state) {
442
- return;
443
- }
444
- if (state.keepAliveTimer) {
445
- this.logger.silly(`Clearing old listen key interval timer for ${listenKey}`);
446
- clearInterval(state.keepAliveTimer);
447
- }
448
- if (state.keepAliveRetryTimer) {
449
- this.logger.silly(`Clearing old listen key keepAliveRetry timer for ${listenKey}`);
450
- clearTimeout(state.keepAliveRetryTimer);
451
- }
452
- }
453
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
454
- getWsBaseUrl(market, wsKey) {
455
- if (this.options.wsUrl) {
456
- return this.options.wsUrl;
457
- }
458
- return wsBaseEndpoints[market];
459
- }
460
- getWs(wsKey) {
461
- return this.wsStore.getWs(wsKey);
462
- }
463
- setWsState(wsKey, state) {
464
- this.wsStore.setConnectionState(wsKey, state);
465
- }
466
- getSpotRestClient() {
467
- if (!this.restClients.spot) {
468
- this.restClients.spot = new main_client_1.MainClient(this.getRestClientOptions(), this.options.requestOptions);
469
- }
470
- return this.restClients.spot;
471
- }
472
- getUSDMRestClient(isTestnet) {
473
- if (isTestnet) {
474
- if (!this.restClients.usdmFuturesTestnet) {
475
- this.restClients.usdmFuturesTestnet = new usdm_client_1.USDMClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet);
529
+ // Handle incoming event that listen key expired
530
+ if (eventType === 'listenKeyExpired') {
531
+ const legacyContext = (0, websocket_util_1.getLegacyWsKeyContext)(wsKey);
532
+ if (!legacyContext) {
533
+ // handle this how?
534
+ throw new Error('No context found within wsKey - fatal error with expired listen key');
535
+ }
536
+ this.logger.info(`${legacyContext.market} listenKey EXPIRED - attempting to respawn user data stream: ${wsKey}`, parsed);
537
+ // Just closing the connection (with the last parameter as true) will handle cleanup and respawn
538
+ // Automatically leads to triggerCustomReconnectionWorkflow() to handle fresh user data respawn
539
+ this.getUserDataStreamManager().teardownUserDataListenKey(legacyContext.listenKey, this.getWsStore().getWs(wsKey));
540
+ this.executeReconnectableClose(wsKey, 'listenKeyExpired');
476
541
  }
477
- return this.restClients.usdmFuturesTestnet;
478
- }
479
- if (!this.restClients.usdmFutures) {
480
- this.restClients.usdmFutures = new usdm_client_1.USDMClient(this.getRestClientOptions(), this.options.requestOptions);
481
- }
482
- return this.restClients.usdmFutures;
483
- }
484
- getCOINMRestClient(isTestnet) {
485
- if (isTestnet) {
486
- if (!this.restClients.coinmFuturesTestnet) {
487
- this.restClients.coinmFuturesTestnet = new coinm_client_1.CoinMClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet);
542
+ if (this.options.beautify) {
543
+ const beautifiedMessage = this.beautifier.beautifyWsMessage(parsed, eventType, false,
544
+ // Suffix all events for the beautifier, if market is options
545
+ // Options has some conflicting keys with different intentions
546
+ wsKey === 'eoptions' ? 'Options' : '');
547
+ results.push({
548
+ eventType: 'formattedMessage',
549
+ event: beautifiedMessage,
550
+ isWSAPIResponse,
551
+ });
552
+ // emit an additional event for user data messages
553
+ 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)) {
567
+ results.push({
568
+ eventType: 'formattedUserDataMessage',
569
+ event: beautifiedMessage,
570
+ isWSAPIResponse,
571
+ });
572
+ }
573
+ }
574
+ }
575
+ // Messages for a subscribed topic all include the "topic" property
576
+ if (typeof eventType === 'string') {
577
+ results.push({
578
+ eventType: 'message',
579
+ event: (parsed === null || parsed === void 0 ? void 0 : parsed.data) ? parsed.data : parsed,
580
+ });
581
+ return results;
488
582
  }
489
- return this.restClients.coinmFuturesTestnet;
583
+ // Messages that are a "reply" to a request/command (e.g. subscribe to these topics) typically include the "op" property
584
+ if (typeof eventType === 'string') {
585
+ // Failed request
586
+ if (parsed.success === false) {
587
+ results.push({
588
+ eventType: 'exception',
589
+ event: parsed,
590
+ });
591
+ return results;
592
+ }
593
+ // These are r equest/reply pattern events (e.g. after subscribing to topics or authenticating)
594
+ if (EVENTS_RESPONSES.includes(eventType)) {
595
+ results.push({
596
+ eventType: 'response',
597
+ event: parsed,
598
+ });
599
+ return results;
600
+ }
601
+ // Request/reply pattern for authentication success
602
+ if (EVENTS_AUTHENTICATED.includes(eventType)) {
603
+ results.push({
604
+ eventType: 'authenticated',
605
+ event: parsed,
606
+ });
607
+ return results;
608
+ }
609
+ this.logger.error(`!! Unhandled string operation type "${eventType}". Defaulting to "update" channel...`, parsed);
610
+ }
611
+ else {
612
+ this.logger.error(`!!!! Unhandled non-string event type "${eventType}". Defaulting to "update" channel...`, parsed);
613
+ }
614
+ // In case of catastrophic failure, fallback to noisy emit update
615
+ results.push({
616
+ eventType: 'message',
617
+ event: parsed,
618
+ });
490
619
  }
491
- if (!this.restClients.coinmFutures) {
492
- this.restClients.coinmFutures = new coinm_client_1.CoinMClient(this.getRestClientOptions(), this.options.requestOptions);
620
+ catch (e) {
621
+ results.push({
622
+ event: {
623
+ message: 'Failed to parse event data due to exception',
624
+ exception: e,
625
+ eventData: event.data,
626
+ },
627
+ eventType: 'exception',
628
+ });
629
+ this.logger.error('Error caught within resolveEmittableEvents - failed to parse event data? Caught exception: ', {
630
+ exception: e,
631
+ eventData: event.data,
632
+ });
493
633
  }
494
- return this.restClients.coinmFutures;
634
+ return results;
495
635
  }
496
636
  /**
497
- * Send WS message to subscribe to topics. Use subscribe() to call this.
637
+ *
638
+ *
639
+ *
640
+ *
641
+ *
642
+ * User Data Streams
643
+ *
644
+ *
645
+ *
646
+ *
647
+ *
498
648
  */
499
- requestSubscribeTopics(wsKey, topics) {
500
- const wsMessage = JSON.stringify({
501
- method: 'SUBSCRIBE',
502
- params: topics,
503
- id: new Date().getTime(),
504
- });
505
- this.tryWsSend(wsKey, wsMessage);
506
- }
507
649
  /**
508
- * Send WS message to unsubscribe from topics. Use unsubscribe() to call this.
650
+ * --------------------------
651
+ * User data listen key tracking & persistence
652
+ * --------------------------
653
+ **/
654
+ /**
655
+ * Subscribe to a spot user data stream. Use REST client to generate and persist listen key.
656
+ * Supports spot, margin & isolated margin listen keys.
509
657
  */
510
- requestUnsubscribeTopics(wsKey, topics) {
511
- const wsMessage = JSON.stringify({
512
- op: 'UNSUBSCRIBE',
513
- params: topics,
514
- id: new Date().getTime(),
658
+ subscribeSpotUserDataStreamWithListenKey(wsKey, listenKey, forceNewConnection, miscState) {
659
+ return __awaiter(this, void 0, void 0, function* () {
660
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, 'spot', listenKey, forceNewConnection, miscState);
515
661
  });
516
- this.tryWsSend(wsKey, wsMessage);
517
662
  }
518
663
  /**
519
- * Send WS message to unsubscribe from topics.
664
+ * Subscribe to spot user data stream - listen key is automaticallyr generated. Calling multiple times only opens one connection.
520
665
  */
521
- requestListSubscriptions(wsKey, requestId) {
522
- const wsMessage = JSON.stringify({
523
- method: 'LIST_SUBSCRIPTIONS',
524
- id: requestId,
666
+ subscribeSpotUserDataStream() {
667
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'main', forceNewConnection, miscState) {
668
+ this.logger.trace('subscribeSpotUserDataStream()', {
669
+ wsKey,
670
+ forceNewConnection,
671
+ miscState,
672
+ });
673
+ try {
674
+ const { listenKey } = yield this.restClientCache
675
+ .getSpotRestClient(this.getRestClientOptions(), this.options.requestOptions)
676
+ .getSpotUserDataListenKey();
677
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, 'spot', listenKey, forceNewConnection, miscState);
678
+ }
679
+ catch (e) {
680
+ this.logger.error('Failed to connect to spot user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
681
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeSpotUserDataStream()', wsKey,
682
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
683
+ }
525
684
  });
526
- this.tryWsSend(wsKey, wsMessage);
527
685
  }
528
686
  /**
529
- * Send WS message to set property state
687
+ * Subscribe to margin user data stream - listen key is automatically generated.
530
688
  */
531
- requestSetProperty(wsKey, property, value, requestId) {
532
- const wsMessage = JSON.stringify({
533
- method: 'SET_PROPERTY',
534
- params: [property, value],
535
- id: requestId,
689
+ subscribeCrossMarginUserDataStream() {
690
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'main', forceNewConnection, miscState) {
691
+ try {
692
+ const { listenKey } = yield this.restClientCache
693
+ .getSpotRestClient(this.getRestClientOptions(), this.options.requestOptions)
694
+ .getMarginUserDataListenKey();
695
+ const market = 'crossMargin';
696
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
697
+ }
698
+ catch (e) {
699
+ this.logger.error('Failed to connect to margin user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
700
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeMarginUserDataStream()', wsKey,
701
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
702
+ }
536
703
  });
537
- this.tryWsSend(wsKey, wsMessage);
538
704
  }
539
705
  /**
540
- * Send WS message to get property state
706
+ * Subscribe to isolated margin user data stream - listen key is automatically generated.
541
707
  */
542
- requestGetProperty(wsKey, property, requestId) {
543
- const wsMessage = JSON.stringify({
544
- method: 'GET_PROPERTY',
545
- params: [property],
546
- id: requestId,
708
+ subscribeIsolatedMarginUserDataStream(symbol_1) {
709
+ return __awaiter(this, arguments, void 0, function* (symbol, wsKey = 'main', forceNewConnection, miscState) {
710
+ try {
711
+ const lowerCaseSymbol = symbol.toLowerCase();
712
+ const { listenKey } = yield this.restClientCache
713
+ .getSpotRestClient(this.getRestClientOptions(), this.options.requestOptions)
714
+ .getIsolatedMarginUserDataListenKey({
715
+ symbol: lowerCaseSymbol,
716
+ });
717
+ const market = 'isolatedMargin';
718
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
719
+ }
720
+ catch (e) {
721
+ this.logger.error('Failed to connect to isolated margin user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e, symbol }));
722
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeIsolatedMarginUserDataStream()', wsKey,
723
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
724
+ }
547
725
  });
548
- this.tryWsSend(wsKey, wsMessage);
549
726
  }
550
727
  /**
551
- * --------------------------
552
- * User data listen key tracking & persistence
553
- * --------------------------
554
- **/
555
- getListenKeyState(listenKey, market) {
556
- const state = this.listenKeyStateStore[listenKey];
557
- if (state) {
558
- return state;
559
- }
560
- this.listenKeyStateStore[listenKey] = {
561
- market,
562
- lastKeepAlive: 0,
563
- keepAliveTimer: undefined,
564
- keepAliveRetryTimer: undefined,
565
- keepAliveFailures: 0,
566
- };
567
- return this.listenKeyStateStore[listenKey];
568
- }
569
- setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey, symbol, isTestnet) {
570
- const listenKeyState = this.getListenKeyState(listenKey, market);
571
- this.clearUserDataKeepAliveTimer(listenKey);
572
- this.logger.silly(`Created new listen key interval timer for ${listenKey}`);
573
- // Set timer to keep WS alive every 50 minutes
574
- const minutes50 = 1000 * 60 * 50;
575
- listenKeyState.keepAliveTimer = setInterval(() => this.checkKeepAliveListenKey(listenKey, market, ws, wsKey, symbol, isTestnet), minutes50);
576
- }
577
- sendKeepAliveForMarket(listenKey, market, ws, wsKey, symbol, isTestnet) {
578
- switch (market) {
579
- case 'spot':
580
- return this.getSpotRestClient().keepAliveSpotUserDataListenKey(listenKey);
581
- case 'margin':
582
- return this.getSpotRestClient().keepAliveMarginUserDataListenKey(listenKey);
583
- case 'isolatedMargin':
584
- return this.getSpotRestClient().keepAliveIsolatedMarginUserDataListenKey({ listenKey, symbol: symbol });
585
- case 'coinm':
586
- case 'options':
587
- case 'optionsTestnet':
588
- case 'usdm':
589
- return this.getUSDMRestClient().keepAliveFuturesUserDataListenKey();
590
- case 'usdmTestnet':
591
- return this.getUSDMRestClient(isTestnet).keepAliveFuturesUserDataListenKey();
592
- case 'coinmTestnet':
593
- return this.getUSDMRestClient(isTestnet).keepAliveFuturesUserDataListenKey();
594
- default:
595
- throwUnhandledSwitch(market, `Failed to send keep alive for user data stream in unhandled market ${market}`);
596
- }
597
- }
598
- checkKeepAliveListenKey(listenKey, market, ws, wsKey, symbol, isTestnet) {
599
- return __awaiter(this, void 0, void 0, function* () {
600
- const listenKeyState = this.getListenKeyState(listenKey, market);
728
+ * Subscribe to margin risk user data stream - listen key is automatically generated.
729
+ */
730
+ subscribeMarginRiskUserDataStream() {
731
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'main', forceNewConnection, miscState) {
601
732
  try {
602
- if (listenKeyState.keepAliveRetryTimer) {
603
- clearTimeout(listenKeyState.keepAliveRetryTimer);
604
- listenKeyState.keepAliveRetryTimer = undefined;
605
- }
606
- // Simple way to test keep alive failure handling:
607
- // throw new Error(`Fake keep alive failure`);
608
- yield this.sendKeepAliveForMarket(listenKey, market, ws, wsKey, symbol, isTestnet);
609
- listenKeyState.lastKeepAlive = Date.now();
610
- listenKeyState.keepAliveFailures = 0;
611
- this.logger.info(`Completed keep alive cycle for listenKey(${listenKey}) in market(${market})`, Object.assign(Object.assign({}, loggerCategory), { listenKey }));
733
+ const { listenKey } = yield this.restClientCache
734
+ .getSpotRestClient(this.getRestClientOptions(), this.options.requestOptions)
735
+ .getMarginRiskUserDataListenKey();
736
+ const market = 'riskDataMargin';
737
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
612
738
  }
613
739
  catch (e) {
614
- listenKeyState.keepAliveFailures++;
615
- // code: -1125,
616
- // message: 'This listenKey does not exist.',
617
- const errorCode = e === null || e === void 0 ? void 0 : e.code;
618
- if (errorCode === -1125) {
619
- this.logger.error('FATAL: Failed to keep WS alive for listen key - listen key expired/invalid. Respawning with fresh listen key...', Object.assign(Object.assign({}, loggerCategory), { listenKey, error: e, errorCode, errorMsg: e === null || e === void 0 ? void 0 : e.message }));
620
- const shouldReconnectAfterClose = false;
621
- this.close(wsKey, shouldReconnectAfterClose);
622
- this.respawnUserDataStream(market, symbol);
623
- return;
624
- }
625
- // If max failurees reached, tear down and respawn if allowed
626
- if (listenKeyState.keepAliveFailures >= 3) {
627
- this.logger.error('FATAL: Failed to keep WS alive for listen key after 3 attempts', Object.assign(Object.assign({}, loggerCategory), { listenKey, error: e }));
628
- // reconnect follows a less automatic workflow since this is tied to a listen key (which may need a new one).
629
- // Kill connection first, with instruction NOT to reconnect automatically
630
- const shouldReconnectAfterClose = false;
631
- this.close(wsKey, shouldReconnectAfterClose);
632
- // Then respawn a connection with a potentially new listen key (since the old one may be invalid now)
633
- this.respawnUserDataStream(market, symbol);
634
- return;
635
- }
636
- const reconnectDelaySeconds = 1000 * 15;
637
- this.logger.warning(`Userdata keep alive request failed due to error, trying again with short delay (${reconnectDelaySeconds} seconds)`, Object.assign(Object.assign({}, loggerCategory), { listenKey, error: e, keepAliveAttempts: listenKeyState.keepAliveFailures }));
638
- listenKeyState.keepAliveRetryTimer = setTimeout(() => this.checkKeepAliveListenKey(listenKey, market, ws, wsKey, symbol), reconnectDelaySeconds);
740
+ this.logger.error('Failed to connect to margin risk user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
741
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeMarginRiskUserDataStream()', wsKey,
742
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
639
743
  }
640
744
  });
641
745
  }
642
- teardownUserDataListenKey(listenKey, ws) {
643
- if (listenKey) {
644
- this.clearUserDataKeepAliveTimer(listenKey);
645
- delete this.listenKeyStateStore[listenKey];
646
- (0, ws_utils_1.safeTerminateWs)(ws);
647
- }
746
+ isCustomReconnectionNeeded(wsKey) {
747
+ return wsKey.includes('userData');
648
748
  }
649
- respawnUserDataStream(market, symbol, isTestnet, respawnAttempt) {
749
+ triggerCustomReconnectionWorkflow(legacyWsKey) {
650
750
  return __awaiter(this, void 0, void 0, function* () {
751
+ console.log(`triggerCustomReconnectionWorkflow(${legacyWsKey})`);
752
+ if (legacyWsKey.includes('userData')) {
753
+ return this.getUserDataStreamManager().triggerUserDataReconnectionWorkflow(legacyWsKey);
754
+ }
755
+ });
756
+ }
757
+ respawnUserDataStream(wsKey_1, market_1) {
758
+ return __awaiter(this, arguments, void 0, function* (wsKey, market, context = {}) {
759
+ // Handle corner case where wsKey is still the derived key for some reason...
760
+ const realWsKey = (0, websocket_util_1.getRealWsKeyFromDerivedWsKey)(wsKey);
761
+ if (realWsKey !== wsKey) {
762
+ console.error('Derived key fed into respawn method!! ', Object.assign(Object.assign({ wsKey,
763
+ market }, context), { realWsKey }));
764
+ console.trace();
765
+ process.exit(-1);
766
+ }
651
767
  // If another connection attempt is in progress for this listen key, don't initiate a retry or the risk is multiple connections on the same listen key
652
768
  const forceNewConnection = false;
653
- const isReconnecting = true;
654
- let ws;
769
+ const miscConnectionState = {
770
+ isReconnecting: true,
771
+ respawnAttempt: context === null || context === void 0 ? void 0 : context.respawnAttempt,
772
+ };
773
+ let ws = undefined;
655
774
  try {
656
775
  switch (market) {
657
776
  case 'spot':
658
- ws = yield this.subscribeSpotUserDataStream(forceNewConnection, isReconnecting);
777
+ case 'spotTestnet':
778
+ ws = yield this.subscribeSpotUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
659
779
  break;
660
- case 'margin':
661
- ws = yield this.subscribeMarginUserDataStream(forceNewConnection, isReconnecting);
780
+ case 'crossMargin':
781
+ ws = yield this.subscribeCrossMarginUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
662
782
  break;
663
783
  case 'isolatedMargin':
664
- ws = yield this.subscribeIsolatedMarginUserDataStream(symbol, forceNewConnection, isReconnecting);
784
+ ws = yield this.subscribeIsolatedMarginUserDataStream(context.symbol, realWsKey, forceNewConnection, miscConnectionState);
785
+ break;
786
+ case 'riskDataMargin':
787
+ ws = yield this.subscribeMarginRiskUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
665
788
  break;
666
789
  case 'usdm':
667
- ws = yield this.subscribeUsdFuturesUserDataStream(isTestnet, forceNewConnection, isReconnecting);
790
+ ws = yield this.subscribeUsdFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
668
791
  break;
669
792
  case 'usdmTestnet':
670
- ws = yield this.subscribeUsdFuturesUserDataStream(true, forceNewConnection, isReconnecting);
793
+ ws = yield this.subscribeUsdFuturesUserDataStream('usdmTestnet', forceNewConnection, miscConnectionState);
671
794
  break;
672
795
  case 'coinm':
673
- ws = yield this.subscribeCoinFuturesUserDataStream(isTestnet, forceNewConnection, isReconnecting);
796
+ ws = yield this.subscribeCoinFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
674
797
  break;
675
798
  case 'coinmTestnet':
676
- ws = yield this.subscribeCoinFuturesUserDataStream(true, forceNewConnection, isReconnecting);
799
+ ws = yield this.subscribeCoinFuturesUserDataStream('coinmTestnet', forceNewConnection, miscConnectionState);
800
+ break;
801
+ case 'portfoliom':
802
+ ws = yield this.subscribePortfolioMarginUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
677
803
  break;
678
804
  case 'options':
679
805
  case 'optionsTestnet':
680
- throw new Error('TODO: respawn other user data streams once subscribe methods have been added');
806
+ throw new Error('European options are not supported yet. Please get in touch if you need this.');
681
807
  default:
682
- throwUnhandledSwitch(market, `Failed to respawn user data stream - unhandled market: ${market}`);
808
+ throw (0, typeGuards_1.neverGuard)(market, `Failed to respawn user data stream - unhandled market: ${market}`);
683
809
  }
684
810
  }
685
811
  catch (e) {
686
- this.logger.error('Exception trying to spawn user data stream', Object.assign(Object.assign({}, loggerCategory), { market,
687
- symbol,
688
- isTestnet, error: e }));
689
- this.emit('error', { wsKey: market + '_' + 'userData', error: e });
812
+ this.logger.error('Exception trying to spawn user data stream', Object.assign(Object.assign(Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { market }), context), { error: e }));
813
+ this.emit('exception', { wsKey: market + '_' + 'userData', error: e });
690
814
  }
691
815
  if (!ws) {
692
816
  const delayInSeconds = 2;
693
- this.logger.error('User key respawn failed, trying again with short delay', Object.assign(Object.assign({}, loggerCategory), { market,
694
- symbol,
695
- isTestnet,
696
- respawnAttempt,
697
- delayInSeconds }));
698
- setTimeout(() => this.respawnUserDataStream(market, symbol, isTestnet, respawnAttempt ? respawnAttempt + 1 : 1), 1000 * delayInSeconds);
817
+ this.logger.error('Userdata respawn failed, trying again with short delay', Object.assign(Object.assign(Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { market }), context), { delayInSeconds }));
818
+ const respawnTimeoutKey = [
819
+ market,
820
+ context === null || context === void 0 ? void 0 : context.symbol,
821
+ context === null || context === void 0 ? void 0 : context.isTestnet,
822
+ ].join('_');
823
+ // Prevent simultaneous timers in same scope
824
+ if (this.respawnTimeoutCache[respawnTimeoutKey]) {
825
+ clearTimeout(this.respawnTimeoutCache[respawnTimeoutKey]);
826
+ delete this.respawnTimeoutCache[respawnTimeoutKey];
827
+ this.logger.error('Respawn timer already active while trying to queue respawn...delaying existing timer further...', Object.assign(Object.assign(Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { market }), context), { delayInSeconds }));
828
+ }
829
+ // Execute reconnection workflow after short delay
830
+ this.respawnTimeoutCache[respawnTimeoutKey] = setTimeout(() => {
831
+ delete this.respawnTimeoutCache[respawnTimeoutKey];
832
+ this.respawnUserDataStream(realWsKey, market, Object.assign(Object.assign({}, context), { respawnAttempt: (context === null || context === void 0 ? void 0 : context.respawnAttempt)
833
+ ? context.respawnAttempt + 1
834
+ : 1 }));
835
+ }, 1000 * delayInSeconds);
836
+ }
837
+ });
838
+ }
839
+ /**
840
+ * --------------------------
841
+ * End of SPOT market websocket streams
842
+ * --------------------------
843
+ **/
844
+ /**
845
+ * Subscribe to USD-M Futures user data stream - listen key is automatically generated.
846
+ */
847
+ subscribeUsdFuturesUserDataStream() {
848
+ return __awaiter(this, arguments, void 0, function* (wsKey = 'usdm', // usdm | usdmTestnet
849
+ forceNewConnection, miscState) {
850
+ try {
851
+ const isTestnet = wsKey === websocket_util_1.WS_KEY_MAP.usdmTestnet;
852
+ const restClient = this.restClientCache.getUSDMRestClient(this.getRestClientOptions(), this.options.requestOptions, isTestnet);
853
+ const { listenKey } = yield restClient.getFuturesUserDataListenKey();
854
+ const market = isTestnet ? 'usdmTestnet' : 'usdm';
855
+ return this.getUserDataStreamManager().subscribeGeneralUserDataStreamWithListenKey(wsKey, market, listenKey, forceNewConnection, miscState);
856
+ }
857
+ catch (e) {
858
+ this.logger.error('Failed to connect to USD Futures user data', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { error: e }));
859
+ this.emit('exception', Object.assign(Object.assign({ functionRef: 'subscribeUsdFuturesUserDataStream()', wsKey,
860
+ forceNewConnection }, miscState), { error: (e === null || e === void 0 ? void 0 : e.stack) || e }));
699
861
  }
700
862
  });
701
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, isTestnet)
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
+ /**
905
+ * Subscribe to the European Options user data stream - listen key is automatically generated.
906
+ *
907
+ * Not supported at this time. Please get in touch if you need this.
908
+ */
909
+ // public async subscribeEuropeanOptionsUserData(
910
+ // wsKey: WsKey = 'eoptions',
911
+ // forceNewConnection?: boolean,
912
+ // isReconnecting?: boolean,
913
+ // ): Promise<WSConnectedResult | undefined> {
914
+ // try {
915
+ // const { listenKey } = await this.restClientCache
916
+ // .getEuropeanOptionsClient(
917
+ // this.getRestClientOptions(),
918
+ // this.options.requestOptions,
919
+ // )
920
+ // .getUserDataListenKey();
921
+ // const market: WsMarket = 'options';
922
+ // return this.subscribeGeneralUserDataStreamWithListenKey(
923
+ // wsKey,
924
+ // market,
925
+ // listenKey,
926
+ // forceNewConnection,
927
+ // isReconnecting,
928
+ // );
929
+ // } catch (e) {
930
+ // this.logger.error('Failed to connect to Options user data', {
931
+ // ...WS_LOGGER_CATEGORY,
932
+ // error: e,
933
+ // });
934
+ // this.emit('exception', {
935
+ // functionRef: 'subscribePortfolioMarginUserDataStream()',
936
+ // wsKey,
937
+ // forceNewConnection,
938
+ // isReconnecting,
939
+ // error: e?.stack || e,
940
+ // });
941
+ // }
942
+ // }
943
+ /**
944
+ *
945
+ *
946
+ *
947
+ *
948
+ * Convenient subscribe methods, similar to the legacy WebsocketClient for Binance.
949
+ *
950
+ *
951
+ *
952
+ *
953
+ */
702
954
  /**
703
955
  * --------------------------
704
956
  * Universal market websocket streams (may apply to one or more API markets)
705
957
  * --------------------------
706
958
  **/
707
959
  /**
708
- * Subscribe to a universal market websocket stream
960
+ * Advanced: Subscribe to a universal market websocket stream
961
+ *
962
+ * This is NOT recommended unless you're very confident with what you're doing.
709
963
  */
710
- subscribeEndpoint(endpoint, market, forceNewConnection) {
711
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, endpoint);
712
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${endpoint}`, wsKey, forceNewConnection);
964
+ subscribeEndpoint(endpoint, market) {
965
+ const wsBaseEndpoints = {
966
+ spot: 'wss://stream.binance.com:9443',
967
+ crossMargin: 'wss://stream.binance.com:9443',
968
+ isolatedMargin: 'wss://stream.binance.com:9443',
969
+ usdm: 'wss://fstream.binance.com',
970
+ usdmTestnet: 'wss://stream.binancefuture.com',
971
+ coinm: 'wss://dstream.binance.com',
972
+ coinmTestnet: 'wss://dstream.binancefuture.com',
973
+ options: 'wss://vstream.binance.com',
974
+ optionsTestnet: 'wss://testnetws.binanceops.com',
975
+ riskDataMargin: '',
976
+ spotTestnet: '',
977
+ portfoliom: '',
978
+ };
979
+ const wsKey = (0, websocket_util_1.getLegacyWsStoreKeyWithContext)(market, endpoint);
980
+ return this.connect(wsKey, wsBaseEndpoints[market] + `/ws/${endpoint}`);
713
981
  }
714
982
  /**
715
983
  * Subscribe to aggregate trades for a symbol in a market category
716
984
  */
717
- subscribeAggregateTrades(symbol, market, forceNewConnection) {
985
+ subscribeAggregateTrades(symbol, market) {
718
986
  const lowerCaseSymbol = symbol.toLowerCase();
719
987
  const streamName = 'aggTrade';
720
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
721
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${lowerCaseSymbol}@${streamName}`, wsKey, forceNewConnection);
988
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
989
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}`, wsKey);
722
990
  }
723
991
  /**
724
992
  * Subscribe to trades for a symbol in a market category
725
993
  * IMPORTANT: This topic for usdm and coinm is not listed in the api docs and might stop working without warning
726
994
  */
727
- subscribeTrades(symbol, market, forceNewConnection) {
995
+ subscribeTrades(symbol, market) {
728
996
  const lowerCaseSymbol = symbol.toLowerCase();
729
997
  const streamName = 'trade';
730
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
731
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${lowerCaseSymbol}@${streamName}`, wsKey, forceNewConnection);
998
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
999
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}`, wsKey);
732
1000
  }
733
1001
  /**
734
1002
  * Subscribe to coin index for a symbol in COINM Futures markets
735
1003
  */
736
- subscribeCoinIndexPrice(symbol, updateSpeedMs = 3000, forceNewConnection) {
1004
+ subscribeCoinIndexPrice(symbol, updateSpeedMs = 3000) {
737
1005
  const lowerCaseSymbol = symbol.toLowerCase();
738
1006
  const streamName = 'indexPrice';
739
1007
  const speedSuffix = updateSpeedMs === 1000 ? '@1s' : '';
740
1008
  const market = 'coinm';
741
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
742
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
743
- `/ws/${lowerCaseSymbol}@${streamName}${speedSuffix}`, wsKey, forceNewConnection);
1009
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1010
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}${speedSuffix}`, wsKey);
744
1011
  }
745
1012
  /**
746
1013
  * Subscribe to mark price for a symbol in a market category
747
1014
  */
748
- subscribeMarkPrice(symbol, market, updateSpeedMs = 3000, forceNewConnection) {
1015
+ subscribeMarkPrice(symbol, market, updateSpeedMs = 3000) {
749
1016
  const lowerCaseSymbol = symbol.toLowerCase();
750
1017
  const streamName = 'markPrice';
751
1018
  const speedSuffix = updateSpeedMs === 1000 ? '@1s' : '';
752
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
753
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
754
- `/ws/${lowerCaseSymbol}@${streamName}${speedSuffix}`, wsKey, forceNewConnection);
1019
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1020
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}${speedSuffix}`, wsKey);
755
1021
  }
756
1022
  /**
757
1023
  * Subscribe to mark price for all symbols in a market category
758
1024
  */
759
- subscribeAllMarketMarkPrice(market, updateSpeedMs = 3000, forceNewConnection) {
1025
+ subscribeAllMarketMarkPrice(market, updateSpeedMs = 3000) {
760
1026
  const streamName = '!markPrice@arr';
761
1027
  const speedSuffix = updateSpeedMs === 1000 ? '@1s' : '';
762
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName);
763
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${streamName}${speedSuffix}`, wsKey, forceNewConnection);
1028
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1029
+ return this.subscribe(`${streamName}${speedSuffix}`, wsKey);
764
1030
  }
765
1031
  /**
766
1032
  * Subscribe to klines(candles) for a symbol in a market category
767
1033
  */
768
- subscribeKlines(symbol, interval, market, forceNewConnection) {
1034
+ subscribeKlines(symbol, interval, market) {
769
1035
  const lowerCaseSymbol = symbol.toLowerCase();
770
1036
  const streamName = 'kline';
771
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol, interval);
772
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
773
- `/ws/${lowerCaseSymbol}@${streamName}_${interval}`, wsKey, forceNewConnection);
1037
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1038
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}_${interval}`, wsKey);
774
1039
  }
775
1040
  /**
776
1041
  * Subscribe to continuous contract klines(candles) for a symbol futures
777
1042
  */
778
- subscribeContinuousContractKlines(symbol, contractType, interval, market, forceNewConnection) {
1043
+ subscribeContinuousContractKlines(symbol, contractType, interval, market) {
779
1044
  const lowerCaseSymbol = symbol.toLowerCase();
780
1045
  const streamName = 'continuousKline';
781
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol, interval);
782
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
783
- `/ws/${lowerCaseSymbol}_${contractType}@${streamName}_${interval}`, wsKey, forceNewConnection);
1046
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1047
+ return this.subscribe(`${lowerCaseSymbol}_${contractType}@${streamName}_${interval}`, wsKey);
784
1048
  }
785
1049
  /**
786
1050
  * Subscribe to index klines(candles) for a symbol in a coinm futures
787
1051
  */
788
- subscribeIndexKlines(symbol, interval, forceNewConnection) {
1052
+ subscribeIndexKlines(symbol, interval) {
789
1053
  const lowerCaseSymbol = symbol.toLowerCase();
790
1054
  const streamName = 'indexPriceKline';
791
1055
  const market = 'coinm';
792
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol, interval);
793
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
794
- `/ws/${lowerCaseSymbol}@${streamName}_${interval}`, wsKey, forceNewConnection);
1056
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1057
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}_${interval}`, wsKey);
795
1058
  }
796
1059
  /**
797
1060
  * Subscribe to index klines(candles) for a symbol in a coinm futures
798
1061
  */
799
- subscribeMarkPriceKlines(symbol, interval, forceNewConnection) {
1062
+ subscribeMarkPriceKlines(symbol, interval) {
800
1063
  const lowerCaseSymbol = symbol.toLowerCase();
801
- const streamName = 'markPrice_kline';
1064
+ const streamName = 'markPriceKline';
802
1065
  const market = 'coinm';
803
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol, interval);
804
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
805
- `/ws/${lowerCaseSymbol}@${streamName}_${interval}`, wsKey, forceNewConnection);
1066
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1067
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}_${interval}`, wsKey);
806
1068
  }
807
1069
  /**
808
1070
  * Subscribe to mini 24hr ticker for a symbol in market category.
809
1071
  */
810
- subscribeSymbolMini24hrTicker(symbol, market, forceNewConnection) {
1072
+ subscribeSymbolMini24hrTicker(symbol, market) {
811
1073
  const lowerCaseSymbol = symbol.toLowerCase();
812
1074
  const streamName = 'miniTicker';
813
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
814
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${lowerCaseSymbol}@${streamName}`, wsKey, forceNewConnection);
1075
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1076
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}`, wsKey);
815
1077
  }
816
1078
  /**
817
1079
  * Subscribe to mini 24hr mini ticker in market category.
818
1080
  */
819
- subscribeAllMini24hrTickers(market, forceNewConnection) {
1081
+ subscribeAllMini24hrTickers(market) {
820
1082
  const streamName = 'miniTicker';
821
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName);
822
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/!${streamName}@arr`, wsKey, forceNewConnection);
1083
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1084
+ return this.subscribe(`!${streamName}@arr`, wsKey);
823
1085
  }
824
1086
  /**
825
1087
  * Subscribe to 24hr ticker for a symbol in any market.
826
1088
  */
827
- subscribeSymbol24hrTicker(symbol, market, forceNewConnection) {
1089
+ subscribeSymbol24hrTicker(symbol, market) {
828
1090
  const lowerCaseSymbol = symbol.toLowerCase();
829
1091
  const streamName = 'ticker';
830
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
831
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${lowerCaseSymbol}@${streamName}`, wsKey, forceNewConnection);
1092
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1093
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}`, wsKey);
832
1094
  }
833
1095
  /**
834
1096
  * Subscribe to 24hr ticker in any market.
835
1097
  */
836
- subscribeAll24hrTickers(market, forceNewConnection) {
1098
+ subscribeAll24hrTickers(market) {
837
1099
  const streamName = 'ticker';
838
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName);
839
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/!${streamName}@arr`, wsKey, forceNewConnection);
1100
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1101
+ return this.subscribe(`!${streamName}@arr`, wsKey);
840
1102
  }
841
1103
  /**
842
1104
  * Subscribe to rolling window ticker statistics for all market symbols,
@@ -847,45 +1109,44 @@ class WebsocketClient extends events_1.EventEmitter {
847
1109
  * - Supported window sizes: 1h,4h,1d.
848
1110
  * - Supported markets: spot
849
1111
  */
850
- subscribeAllRollingWindowTickers(market, windowSize, forceNewConnection) {
1112
+ subscribeAllRollingWindowTickers(market, windowSize) {
851
1113
  const streamName = 'ticker';
852
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, windowSize);
853
- const wsUrl = this.getWsBaseUrl(market, wsKey) + `/ws/!${streamName}_${windowSize}@arr`;
854
- return this.connectToWsUrl(wsUrl, wsKey, forceNewConnection);
1114
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1115
+ return this.subscribe(`!${streamName}_${windowSize}@arr`, wsKey);
855
1116
  }
856
1117
  /**
857
1118
  * Subscribe to best bid/ask for symbol in spot markets.
858
1119
  */
859
- subscribeSymbolBookTicker(symbol, market, forceNewConnection) {
1120
+ subscribeSymbolBookTicker(symbol, market) {
860
1121
  const lowerCaseSymbol = symbol.toLowerCase();
861
1122
  const streamName = 'bookTicker';
862
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
863
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${lowerCaseSymbol}@${streamName}`, wsKey, forceNewConnection);
1123
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1124
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}`, wsKey);
864
1125
  }
865
1126
  /**
866
1127
  * Subscribe to best bid/ask for all symbols in spot markets.
867
1128
  */
868
- subscribeAllBookTickers(market, forceNewConnection) {
1129
+ subscribeAllBookTickers(market) {
869
1130
  const streamName = 'bookTicker';
870
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName);
871
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/!${streamName}`, wsKey, forceNewConnection);
1131
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1132
+ return this.subscribe(`!${streamName}`, wsKey);
872
1133
  }
873
1134
  /**
874
1135
  * Subscribe to best bid/ask for symbol in spot markets.
875
1136
  */
876
- subscribeSymbolLiquidationOrders(symbol, market, forceNewConnection) {
1137
+ subscribeSymbolLiquidationOrders(symbol, market) {
877
1138
  const lowerCaseSymbol = symbol.toLowerCase();
878
1139
  const streamName = 'forceOrder';
879
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
880
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${lowerCaseSymbol}@${streamName}`, wsKey, forceNewConnection);
1140
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1141
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}`, wsKey);
881
1142
  }
882
1143
  /**
883
1144
  * Subscribe to best bid/ask for all symbols in spot markets.
884
1145
  */
885
- subscribeAllLiquidationOrders(market, forceNewConnection) {
1146
+ subscribeAllLiquidationOrders(market) {
886
1147
  const streamName = 'forceOrder@arr';
887
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName);
888
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/!${streamName}`, wsKey, forceNewConnection);
1148
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1149
+ return this.subscribe(`!${streamName}`, wsKey);
889
1150
  }
890
1151
  /**
891
1152
  * Subscribe to partial book depths (snapshots).
@@ -893,16 +1154,13 @@ class WebsocketClient extends events_1.EventEmitter {
893
1154
  * Note:
894
1155
  * - spot only supports 1000ms or 100ms for updateMs
895
1156
  * - futures only support 100, 250 or 500ms for updateMs
896
- *
897
- * Use getContextFromWsKey(data.wsKey) to extract symbol from events
898
1157
  */
899
- subscribePartialBookDepths(symbol, levels, updateMs, market, forceNewConnection) {
1158
+ subscribePartialBookDepths(symbol, levels, updateMs, market) {
900
1159
  const lowerCaseSymbol = symbol.toLowerCase();
901
1160
  const streamName = 'depth';
902
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName, lowerCaseSymbol);
1161
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
903
1162
  const updateMsSuffx = typeof updateMs === 'number' ? `@${updateMs}ms` : '';
904
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
905
- `/ws/${lowerCaseSymbol}@${streamName}${levels}${updateMsSuffx}`, wsKey, forceNewConnection);
1163
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}${levels}${updateMsSuffx}`, wsKey);
906
1164
  }
907
1165
  /**
908
1166
  * Subscribe to orderbook depth updates to locally manage an order book.
@@ -911,24 +1169,21 @@ class WebsocketClient extends events_1.EventEmitter {
911
1169
  *
912
1170
  * - Spot: https://binance-docs.github.io/apidocs/spot/en/#diff-depth-stream
913
1171
  * - USDM Futures: https://binance-docs.github.io/apidocs/futures/en/#diff-book-depth-streams
914
- *
915
- * Use getContextFromWsKey(data.wsKey) to extract symbol from events
916
1172
  */
917
- subscribeDiffBookDepth(symbol, updateMs = 100, market, forceNewConnection) {
1173
+ subscribeDiffBookDepth(symbol, updateMs = 100, market) {
918
1174
  const lowerCaseSymbol = symbol.toLowerCase();
919
- const streamName = 'depth';
920
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, 'diffBookDepth', lowerCaseSymbol, String(updateMs));
1175
+ const streamName = market === 'spot' ? 'depth' : 'diffBookDepth';
1176
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
921
1177
  const updateMsSuffx = typeof updateMs === 'number' ? `@${updateMs}ms` : '';
922
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) +
923
- `/ws/${lowerCaseSymbol}@${streamName}${updateMsSuffx}`, wsKey, forceNewConnection);
1178
+ return this.subscribe(`${lowerCaseSymbol}@${streamName}${updateMsSuffx}`, wsKey);
924
1179
  }
925
1180
  /**
926
1181
  * Subscribe to best bid/ask for all symbols in spot markets.
927
1182
  */
928
- subscribeContractInfoStream(market, forceNewConnection) {
1183
+ subscribeContractInfoStream(market) {
929
1184
  const streamName = '!contractInfo';
930
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, streamName);
931
- return this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${streamName}`, wsKey, forceNewConnection);
1185
+ const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
1186
+ return this.subscribe(`${streamName}`, wsKey);
932
1187
  }
933
1188
  /**
934
1189
  * --------------------------
@@ -938,224 +1193,62 @@ class WebsocketClient extends events_1.EventEmitter {
938
1193
  /**
939
1194
  * Subscribe to aggregate trades for a symbol in spot markets.
940
1195
  */
941
- subscribeSpotAggregateTrades(symbol, forceNewConnection) {
942
- return this.subscribeAggregateTrades(symbol, 'spot', forceNewConnection);
1196
+ subscribeSpotAggregateTrades(symbol) {
1197
+ return this.subscribeAggregateTrades(symbol, 'spot');
943
1198
  }
944
1199
  /**
945
1200
  * Subscribe to trades for a symbol in spot markets.
946
1201
  */
947
- subscribeSpotTrades(symbol, forceNewConnection) {
948
- return this.subscribeTrades(symbol, 'spot', forceNewConnection);
1202
+ subscribeSpotTrades(symbol) {
1203
+ return this.subscribeTrades(symbol, 'spot');
949
1204
  }
950
1205
  /**
951
1206
  * Subscribe to candles for a symbol in spot markets.
952
1207
  */
953
- subscribeSpotKline(symbol, interval, forceNewConnection) {
954
- return this.subscribeKlines(symbol, interval, 'spot', forceNewConnection);
1208
+ subscribeSpotKline(symbol, interval) {
1209
+ return this.subscribeKlines(symbol, interval, 'spot');
955
1210
  }
956
1211
  /**
957
1212
  * Subscribe to mini 24hr ticker for a symbol in spot markets.
958
1213
  */
959
- subscribeSpotSymbolMini24hrTicker(symbol, forceNewConnection) {
960
- return this.subscribeSymbolMini24hrTicker(symbol, 'spot', forceNewConnection);
1214
+ subscribeSpotSymbolMini24hrTicker(symbol) {
1215
+ return this.subscribeSymbolMini24hrTicker(symbol, 'spot');
961
1216
  }
962
1217
  /**
963
1218
  * Subscribe to mini 24hr mini ticker in spot markets.
964
1219
  */
965
- subscribeSpotAllMini24hrTickers(forceNewConnection) {
966
- return this.subscribeAllMini24hrTickers('spot', forceNewConnection);
1220
+ subscribeSpotAllMini24hrTickers() {
1221
+ return this.subscribeAllMini24hrTickers('spot');
967
1222
  }
968
1223
  /**
969
1224
  * Subscribe to 24hr ticker for a symbol in spot markets.
970
1225
  */
971
- subscribeSpotSymbol24hrTicker(symbol, forceNewConnection) {
972
- return this.subscribeSymbol24hrTicker(symbol, 'spot', forceNewConnection);
1226
+ subscribeSpotSymbol24hrTicker(symbol) {
1227
+ return this.subscribeSymbol24hrTicker(symbol, 'spot');
973
1228
  }
974
1229
  /**
975
1230
  * Subscribe to 24hr ticker in spot markets.
976
1231
  */
977
- subscribeSpotAll24hrTickers(forceNewConnection) {
978
- return this.subscribeAll24hrTickers('spot', forceNewConnection);
1232
+ subscribeSpotAll24hrTickers() {
1233
+ return this.subscribeAll24hrTickers('spot');
979
1234
  }
980
1235
  /**
981
1236
  * Subscribe to best bid/ask for symbol in spot markets.
982
1237
  */
983
- subscribeSpotSymbolBookTicker(symbol, forceNewConnection) {
984
- return this.subscribeSymbolBookTicker(symbol, 'spot', forceNewConnection);
985
- }
986
- /**
987
- * Subscribe to best bid/ask for all symbols in spot markets.
988
- */
989
- subscribeSpotAllBookTickers(forceNewConnection) {
990
- return this.subscribeAllBookTickers('spot', forceNewConnection);
1238
+ subscribeSpotSymbolBookTicker(symbol) {
1239
+ return this.subscribeSymbolBookTicker(symbol, 'spot');
991
1240
  }
992
1241
  /**
993
1242
  * Subscribe to top bid/ask levels for symbol in spot markets.
994
1243
  */
995
- subscribeSpotPartialBookDepth(symbol, levels, updateMs = 1000, forceNewConnection) {
996
- return this.subscribePartialBookDepths(symbol, levels, updateMs, 'spot', forceNewConnection);
1244
+ subscribeSpotPartialBookDepth(symbol, levels, updateMs = 1000) {
1245
+ return this.subscribePartialBookDepths(symbol, levels, updateMs, 'spot');
997
1246
  }
998
1247
  /**
999
1248
  * Subscribe to spot orderbook depth updates to locally manage an order book.
1000
1249
  */
1001
- subscribeSpotDiffBookDepth(symbol, updateMs = 1000, forceNewConnection) {
1002
- return this.subscribeDiffBookDepth(symbol, updateMs, 'spot', forceNewConnection);
1003
- }
1004
- /**
1005
- * Subscribe to a spot user data stream. Use REST client to generate and persist listen key.
1006
- * Supports spot, margin & isolated margin listen keys.
1007
- */
1008
- subscribeSpotUserDataStreamWithListenKey(listenKey, forceNewConnection, isReconnecting) {
1009
- const market = 'spot';
1010
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, 'userData', undefined, listenKey);
1011
- if (!forceNewConnection && this.wsStore.isWsConnecting(wsKey)) {
1012
- this.logger.silly('Existing spot user data connection in progress for listen key. Avoiding duplicate');
1013
- return this.getWs(wsKey);
1014
- }
1015
- this.setWsState(wsKey, isReconnecting
1016
- ? WsStore_1.WsConnectionStateEnum.RECONNECTING
1017
- : WsStore_1.WsConnectionStateEnum.CONNECTING);
1018
- const ws = this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${listenKey}`, wsKey, forceNewConnection);
1019
- // Start & store timer to keep alive listen key (and handle expiration)
1020
- this.setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey);
1021
- return ws;
1022
- }
1023
- /**
1024
- * Subscribe to spot user data stream - listen key is automatically generated. Calling multiple times only opens one connection.
1025
- */
1026
- subscribeSpotUserDataStream(forceNewConnection, isReconnecting) {
1027
- return __awaiter(this, void 0, void 0, function* () {
1028
- try {
1029
- const { listenKey } = yield this.getSpotRestClient().getSpotUserDataListenKey();
1030
- return this.subscribeSpotUserDataStreamWithListenKey(listenKey, forceNewConnection, isReconnecting);
1031
- }
1032
- catch (e) {
1033
- this.logger.error('Failed to connect to spot user data', Object.assign(Object.assign({}, loggerCategory), { error: e }));
1034
- this.emit('error', { wsKey: 'spot' + '_' + 'userData', error: e });
1035
- }
1036
- });
1037
- }
1038
- /**
1039
- * Subscribe to margin user data stream - listen key is automatically generated.
1040
- */
1041
- subscribeMarginUserDataStream(forceNewConnection, isReconnecting) {
1042
- return __awaiter(this, void 0, void 0, function* () {
1043
- try {
1044
- const { listenKey } = yield this.getSpotRestClient().getMarginUserDataListenKey();
1045
- const market = 'margin';
1046
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, 'userData', undefined, listenKey);
1047
- if (!forceNewConnection && this.wsStore.isWsConnecting(wsKey)) {
1048
- this.logger.silly('Existing margin user data connection in progress for listen key. Avoiding duplicate');
1049
- return this.getWs(wsKey);
1050
- }
1051
- this.setWsState(wsKey, isReconnecting
1052
- ? WsStore_1.WsConnectionStateEnum.RECONNECTING
1053
- : WsStore_1.WsConnectionStateEnum.CONNECTING);
1054
- const ws = this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${listenKey}`, wsKey, forceNewConnection);
1055
- // Start & store timer to keep alive listen key (and handle expiration)
1056
- this.setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey);
1057
- return ws;
1058
- }
1059
- catch (e) {
1060
- this.logger.error('Failed to connect to margin user data', Object.assign(Object.assign({}, loggerCategory), { error: e }));
1061
- this.emit('error', { wsKey: 'margin' + '_' + 'userData', error: e });
1062
- }
1063
- });
1064
- }
1065
- /**
1066
- * Subscribe to isolated margin user data stream - listen key is automatically generated.
1067
- */
1068
- subscribeIsolatedMarginUserDataStream(symbol, forceNewConnection, isReconnecting) {
1069
- return __awaiter(this, void 0, void 0, function* () {
1070
- try {
1071
- const lowerCaseSymbol = symbol.toLowerCase();
1072
- const { listenKey } = yield this.getSpotRestClient().getIsolatedMarginUserDataListenKey({
1073
- symbol: lowerCaseSymbol,
1074
- });
1075
- const market = 'isolatedMargin';
1076
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, 'userData', lowerCaseSymbol, listenKey);
1077
- if (!forceNewConnection && this.wsStore.isWsConnecting(wsKey)) {
1078
- this.logger.silly('Existing isolated margin user data connection in progress for listen key. Avoiding duplicate');
1079
- return this.getWs(wsKey);
1080
- }
1081
- this.setWsState(wsKey, isReconnecting
1082
- ? WsStore_1.WsConnectionStateEnum.RECONNECTING
1083
- : WsStore_1.WsConnectionStateEnum.CONNECTING);
1084
- const ws = this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${listenKey}`, wsKey, forceNewConnection);
1085
- // Start & store timer to keep alive listen key (and handle expiration)
1086
- this.setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey, symbol);
1087
- return ws;
1088
- }
1089
- catch (e) {
1090
- this.logger.error('Failed to connect to isolated margin user data', Object.assign(Object.assign({}, loggerCategory), { error: e, symbol }));
1091
- this.emit('error', {
1092
- wsKey: 'isolatedMargin' + '_' + 'userData',
1093
- error: e,
1094
- });
1095
- }
1096
- });
1097
- }
1098
- /**
1099
- * --------------------------
1100
- * End of SPOT market websocket streams
1101
- * --------------------------
1102
- **/
1103
- /**
1104
- * Subscribe to USD-M Futures user data stream - listen key is automatically generated.
1105
- */
1106
- subscribeUsdFuturesUserDataStream(isTestnet, forceNewConnection, isReconnecting) {
1107
- return __awaiter(this, void 0, void 0, function* () {
1108
- try {
1109
- const restClient = this.getUSDMRestClient(isTestnet);
1110
- const { listenKey } = yield restClient.getFuturesUserDataListenKey();
1111
- const market = isTestnet ? 'usdmTestnet' : 'usdm';
1112
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, 'userData', undefined, listenKey);
1113
- if (!forceNewConnection && this.wsStore.isWsConnecting(wsKey)) {
1114
- this.logger.silly('Existing usd futures user data connection in progress for listen key. Avoiding duplicate');
1115
- return this.getWs(wsKey);
1116
- }
1117
- // Necessary so client knows this is a reconnect
1118
- this.setWsState(wsKey, isReconnecting
1119
- ? WsStore_1.WsConnectionStateEnum.RECONNECTING
1120
- : WsStore_1.WsConnectionStateEnum.CONNECTING);
1121
- const ws = this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${listenKey}`, wsKey, forceNewConnection);
1122
- // Start & store timer to keep alive listen key (and handle expiration)
1123
- this.setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey, undefined, isTestnet);
1124
- return ws;
1125
- }
1126
- catch (e) {
1127
- this.logger.error('Failed to connect to USD Futures user data', Object.assign(Object.assign({}, loggerCategory), { error: e }));
1128
- this.emit('error', { wsKey: 'usdm' + '_' + 'userData', error: e });
1129
- }
1130
- });
1131
- }
1132
- /**
1133
- * Subscribe to COIN-M Futures user data stream - listen key is automatically generated.
1134
- */
1135
- subscribeCoinFuturesUserDataStream(isTestnet, forceNewConnection, isReconnecting) {
1136
- return __awaiter(this, void 0, void 0, function* () {
1137
- try {
1138
- const { listenKey } = yield this.getCOINMRestClient(isTestnet).getFuturesUserDataListenKey();
1139
- const market = isTestnet ? 'coinmTestnet' : 'coinm';
1140
- const wsKey = (0, requestUtils_1.getWsKeyWithContext)(market, 'userData', undefined, listenKey);
1141
- if (!forceNewConnection && this.wsStore.isWsConnecting(wsKey)) {
1142
- this.logger.silly('Existing usd futures user data connection in progress for listen key. Avoiding duplicate');
1143
- return this.getWs(wsKey);
1144
- }
1145
- // Necessary so client knows this is a reconnect
1146
- this.setWsState(wsKey, isReconnecting
1147
- ? WsStore_1.WsConnectionStateEnum.RECONNECTING
1148
- : WsStore_1.WsConnectionStateEnum.CONNECTING);
1149
- const ws = this.connectToWsUrl(this.getWsBaseUrl(market, wsKey) + `/ws/${listenKey}`, wsKey, forceNewConnection);
1150
- // Start & store timer to keep alive listen key (and handle expiration)
1151
- this.setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey, undefined, isTestnet);
1152
- return ws;
1153
- }
1154
- catch (e) {
1155
- this.logger.error('Failed to connect to COIN Futures user data', Object.assign(Object.assign({}, loggerCategory), { error: e }));
1156
- this.emit('error', { wsKey: 'coinm' + '_' + 'userData', error: e });
1157
- }
1158
- });
1250
+ subscribeSpotDiffBookDepth(symbol, updateMs = 1000) {
1251
+ return this.subscribeDiffBookDepth(symbol, updateMs, 'spot');
1159
1252
  }
1160
1253
  }
1161
1254
  exports.WebsocketClient = WebsocketClient;