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.
- package/README.md +5 -2
- package/lib/coinm-client.js.map +1 -1
- package/lib/index.d.ts +10 -4
- package/lib/index.js +10 -4
- package/lib/index.js.map +1 -1
- package/lib/main-client.d.ts +13 -2
- package/lib/main-client.js +15 -1
- package/lib/main-client.js.map +1 -1
- package/lib/portfolio-client.js.map +1 -1
- package/lib/types/futures.d.ts +1 -0
- package/lib/types/futures.js +3 -3
- package/lib/types/futures.js.map +1 -1
- package/lib/types/spot.js +4 -4
- package/lib/types/spot.js.map +1 -1
- package/lib/types/websockets/ws-api-requests.d.ts +7 -0
- package/lib/types/websockets/ws-api-requests.js +3 -0
- package/lib/types/websockets/ws-api-requests.js.map +1 -0
- package/lib/types/websockets/ws-api-responses.d.ts +8 -0
- package/lib/types/websockets/ws-api-responses.js +3 -0
- package/lib/types/websockets/ws-api-responses.js.map +1 -0
- package/lib/types/websockets/ws-api.d.ts +201 -0
- package/lib/types/websockets/ws-api.js +29 -0
- package/lib/types/websockets/ws-api.js.map +1 -0
- package/lib/types/{websockets.d.ts → websockets/ws-events-formatted.d.ts} +4 -410
- package/lib/types/websockets/ws-events-formatted.js +3 -0
- package/lib/types/websockets/ws-events-formatted.js.map +1 -0
- package/lib/types/websockets/ws-events-raw.d.ts +401 -0
- package/lib/types/{websockets.js → websockets/ws-events-raw.js} +1 -1
- package/lib/types/websockets/ws-events-raw.js.map +1 -0
- package/lib/types/websockets/ws-general.d.ts +98 -0
- package/lib/types/websockets/ws-general.js +11 -0
- package/lib/types/websockets/ws-general.js.map +1 -0
- package/lib/usdm-client.js.map +1 -1
- package/lib/util/BaseRestClient.d.ts +1 -1
- package/lib/util/BaseRestClient.js +1 -1
- package/lib/util/BaseRestClient.js.map +1 -1
- package/lib/util/BaseWSClient.d.ts +225 -0
- package/lib/util/BaseWSClient.js +729 -0
- package/lib/util/BaseWSClient.js.map +1 -0
- package/lib/util/beautifier-maps.d.ts +151 -0
- package/lib/util/beautifier-maps.js +198 -37
- package/lib/util/beautifier-maps.js.map +1 -1
- package/lib/util/beautifier.d.ts +7 -3
- package/lib/util/beautifier.js +40 -11
- package/lib/util/beautifier.js.map +1 -1
- package/lib/util/browser-support.d.ts +2 -1
- package/lib/util/browser-support.js +46 -28
- package/lib/util/browser-support.js.map +1 -1
- package/lib/util/logger.d.ts +8 -0
- package/lib/util/logger.js +17 -0
- package/lib/util/logger.js.map +1 -0
- package/lib/util/node-support.d.ts +2 -1
- package/lib/util/node-support.js +35 -15
- package/lib/util/node-support.js.map +1 -1
- package/lib/util/requestUtils.d.ts +19 -19
- package/lib/util/requestUtils.js +119 -38
- package/lib/util/requestUtils.js.map +1 -1
- package/lib/util/typeGuards.d.ts +9 -1
- package/lib/util/typeGuards.js +59 -34
- package/lib/util/typeGuards.js.map +1 -1
- package/lib/util/usdm/exchangeInfo.js +2 -3
- package/lib/util/usdm/exchangeInfo.js.map +1 -1
- package/lib/util/webCryptoAPI.d.ts +14 -0
- package/lib/util/webCryptoAPI.js +120 -0
- package/lib/util/webCryptoAPI.js.map +1 -0
- package/lib/util/websockets/WsStore.d.ts +74 -0
- package/lib/util/websockets/WsStore.js +279 -0
- package/lib/util/websockets/WsStore.js.map +1 -0
- package/lib/util/websockets/WsStore.types.d.ts +53 -0
- package/lib/util/websockets/WsStore.types.js +14 -0
- package/lib/util/websockets/WsStore.types.js.map +1 -0
- package/lib/util/websockets/listen-key-state-cache.d.ts +21 -0
- package/lib/util/websockets/listen-key-state-cache.js +80 -0
- package/lib/util/websockets/listen-key-state-cache.js.map +1 -0
- package/lib/util/websockets/rest-client-cache.d.ts +13 -0
- package/lib/util/websockets/rest-client-cache.js +56 -0
- package/lib/util/websockets/rest-client-cache.js.map +1 -0
- package/lib/util/websockets/user-data-stream-manager.d.ts +54 -0
- package/lib/util/websockets/user-data-stream-manager.js +256 -0
- package/lib/util/websockets/user-data-stream-manager.js.map +1 -0
- package/lib/util/websockets/websocket-util.d.ts +124 -0
- package/lib/util/websockets/websocket-util.js +481 -0
- package/lib/util/websockets/websocket-util.js.map +1 -0
- package/lib/websocket-client-legacy.d.ts +288 -0
- package/lib/websocket-client-legacy.js +1113 -0
- package/lib/websocket-client-legacy.js.map +1 -0
- package/lib/websocket-client.d.ts +228 -168
- package/lib/websocket-client.js +927 -834
- package/lib/websocket-client.js.map +1 -1
- package/package.json +5 -5
- package/lib/logger.d.ts +0 -9
- package/lib/logger.js +0 -23
- package/lib/logger.js.map +0 -1
- package/lib/types/websockets.js.map +0 -1
- package/lib/util/WsStore.d.ts +0 -57
- package/lib/util/WsStore.js +0 -101
- package/lib/util/WsStore.js.map +0 -1
- package/lib/util/ws-utils.d.ts +0 -7
- package/lib/util/ws-utils.js +0 -16
- package/lib/util/ws-utils.js.map +0 -1
package/lib/websocket-client.js
CHANGED
|
@@ -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 =
|
|
50
|
-
const
|
|
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
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
108
|
-
this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
306
|
+
sendPongEvent(wsKey) {
|
|
307
|
+
// ws.pong();
|
|
166
308
|
try {
|
|
167
|
-
// this.logger.
|
|
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.
|
|
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
|
|
326
|
+
this.logger.error('Failed to send WS pong', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey, exception: e }));
|
|
186
327
|
}
|
|
187
328
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
357
|
+
// default: {
|
|
358
|
+
// throw neverGuard(wsKey, `Unhandled wsKey "${wsKey}"`);
|
|
359
|
+
// }
|
|
277
360
|
}
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
source }));
|
|
303
|
-
ws.pong();
|
|
368
|
+
getPrivateWSKeys() {
|
|
369
|
+
return websocket_util_1.WS_AUTH_ON_CONNECT_KEYS;
|
|
304
370
|
}
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
if (!
|
|
353
|
-
|
|
393
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
394
|
+
isWsPong(msg) {
|
|
395
|
+
if (!msg) {
|
|
396
|
+
return false;
|
|
354
397
|
}
|
|
355
|
-
|
|
398
|
+
// For binance, all ping/pong events are frame events
|
|
399
|
+
return false;
|
|
356
400
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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
|
|
634
|
+
return results;
|
|
495
635
|
}
|
|
496
636
|
/**
|
|
497
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
*
|
|
664
|
+
* Subscribe to spot user data stream - listen key is automaticallyr generated. Calling multiple times only opens one connection.
|
|
520
665
|
*/
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
*
|
|
687
|
+
* Subscribe to margin user data stream - listen key is automatically generated.
|
|
530
688
|
*/
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
*
|
|
706
|
+
* Subscribe to isolated margin user data stream - listen key is automatically generated.
|
|
541
707
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
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
|
|
654
|
-
|
|
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
|
-
|
|
777
|
+
case 'spotTestnet':
|
|
778
|
+
ws = yield this.subscribeSpotUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
|
|
659
779
|
break;
|
|
660
|
-
case '
|
|
661
|
-
ws = yield this.
|
|
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,
|
|
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(
|
|
790
|
+
ws = yield this.subscribeUsdFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
|
|
668
791
|
break;
|
|
669
792
|
case 'usdmTestnet':
|
|
670
|
-
ws = yield this.subscribeUsdFuturesUserDataStream(
|
|
793
|
+
ws = yield this.subscribeUsdFuturesUserDataStream('usdmTestnet', forceNewConnection, miscConnectionState);
|
|
671
794
|
break;
|
|
672
795
|
case 'coinm':
|
|
673
|
-
ws = yield this.subscribeCoinFuturesUserDataStream(
|
|
796
|
+
ws = yield this.subscribeCoinFuturesUserDataStream(realWsKey, forceNewConnection, miscConnectionState);
|
|
674
797
|
break;
|
|
675
798
|
case 'coinmTestnet':
|
|
676
|
-
ws = yield this.subscribeCoinFuturesUserDataStream(
|
|
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('
|
|
806
|
+
throw new Error('European options are not supported yet. Please get in touch if you need this.');
|
|
681
807
|
default:
|
|
682
|
-
|
|
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({},
|
|
687
|
-
|
|
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('
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
|
711
|
-
const
|
|
712
|
-
|
|
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
|
|
985
|
+
subscribeAggregateTrades(symbol, market) {
|
|
718
986
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
719
987
|
const streamName = 'aggTrade';
|
|
720
|
-
const wsKey = (0,
|
|
721
|
-
return this.
|
|
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
|
|
995
|
+
subscribeTrades(symbol, market) {
|
|
728
996
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
729
997
|
const streamName = 'trade';
|
|
730
|
-
const wsKey = (0,
|
|
731
|
-
return this.
|
|
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
|
|
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,
|
|
742
|
-
return this.
|
|
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
|
|
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,
|
|
753
|
-
return this.
|
|
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
|
|
1025
|
+
subscribeAllMarketMarkPrice(market, updateSpeedMs = 3000) {
|
|
760
1026
|
const streamName = '!markPrice@arr';
|
|
761
1027
|
const speedSuffix = updateSpeedMs === 1000 ? '@1s' : '';
|
|
762
|
-
const wsKey = (0,
|
|
763
|
-
return this.
|
|
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
|
|
1034
|
+
subscribeKlines(symbol, interval, market) {
|
|
769
1035
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
770
1036
|
const streamName = 'kline';
|
|
771
|
-
const wsKey = (0,
|
|
772
|
-
return this.
|
|
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
|
|
1043
|
+
subscribeContinuousContractKlines(symbol, contractType, interval, market) {
|
|
779
1044
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
780
1045
|
const streamName = 'continuousKline';
|
|
781
|
-
const wsKey = (0,
|
|
782
|
-
return this.
|
|
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
|
|
1052
|
+
subscribeIndexKlines(symbol, interval) {
|
|
789
1053
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
790
1054
|
const streamName = 'indexPriceKline';
|
|
791
1055
|
const market = 'coinm';
|
|
792
|
-
const wsKey = (0,
|
|
793
|
-
return this.
|
|
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
|
|
1062
|
+
subscribeMarkPriceKlines(symbol, interval) {
|
|
800
1063
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
801
|
-
const streamName = '
|
|
1064
|
+
const streamName = 'markPriceKline';
|
|
802
1065
|
const market = 'coinm';
|
|
803
|
-
const wsKey = (0,
|
|
804
|
-
return this.
|
|
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
|
|
1072
|
+
subscribeSymbolMini24hrTicker(symbol, market) {
|
|
811
1073
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
812
1074
|
const streamName = 'miniTicker';
|
|
813
|
-
const wsKey = (0,
|
|
814
|
-
return this.
|
|
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
|
|
1081
|
+
subscribeAllMini24hrTickers(market) {
|
|
820
1082
|
const streamName = 'miniTicker';
|
|
821
|
-
const wsKey = (0,
|
|
822
|
-
return this.
|
|
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
|
|
1089
|
+
subscribeSymbol24hrTicker(symbol, market) {
|
|
828
1090
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
829
1091
|
const streamName = 'ticker';
|
|
830
|
-
const wsKey = (0,
|
|
831
|
-
return this.
|
|
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
|
|
1098
|
+
subscribeAll24hrTickers(market) {
|
|
837
1099
|
const streamName = 'ticker';
|
|
838
|
-
const wsKey = (0,
|
|
839
|
-
return this.
|
|
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
|
|
1112
|
+
subscribeAllRollingWindowTickers(market, windowSize) {
|
|
851
1113
|
const streamName = 'ticker';
|
|
852
|
-
const wsKey = (0,
|
|
853
|
-
|
|
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
|
|
1120
|
+
subscribeSymbolBookTicker(symbol, market) {
|
|
860
1121
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
861
1122
|
const streamName = 'bookTicker';
|
|
862
|
-
const wsKey = (0,
|
|
863
|
-
return this.
|
|
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
|
|
1129
|
+
subscribeAllBookTickers(market) {
|
|
869
1130
|
const streamName = 'bookTicker';
|
|
870
|
-
const wsKey = (0,
|
|
871
|
-
return this.
|
|
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
|
|
1137
|
+
subscribeSymbolLiquidationOrders(symbol, market) {
|
|
877
1138
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
878
1139
|
const streamName = 'forceOrder';
|
|
879
|
-
const wsKey = (0,
|
|
880
|
-
return this.
|
|
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
|
|
1146
|
+
subscribeAllLiquidationOrders(market) {
|
|
886
1147
|
const streamName = 'forceOrder@arr';
|
|
887
|
-
const wsKey = (0,
|
|
888
|
-
return this.
|
|
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
|
|
1158
|
+
subscribePartialBookDepths(symbol, levels, updateMs, market) {
|
|
900
1159
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
901
1160
|
const streamName = 'depth';
|
|
902
|
-
const wsKey = (0,
|
|
1161
|
+
const wsKey = (0, websocket_util_1.resolveWsKeyForLegacyMarket)(market);
|
|
903
1162
|
const updateMsSuffx = typeof updateMs === 'number' ? `@${updateMs}ms` : '';
|
|
904
|
-
return this.
|
|
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
|
|
1173
|
+
subscribeDiffBookDepth(symbol, updateMs = 100, market) {
|
|
918
1174
|
const lowerCaseSymbol = symbol.toLowerCase();
|
|
919
|
-
const streamName = 'depth';
|
|
920
|
-
const wsKey = (0,
|
|
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.
|
|
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
|
|
1183
|
+
subscribeContractInfoStream(market) {
|
|
929
1184
|
const streamName = '!contractInfo';
|
|
930
|
-
const wsKey = (0,
|
|
931
|
-
return this.
|
|
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
|
|
942
|
-
return this.subscribeAggregateTrades(symbol, 'spot'
|
|
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
|
|
948
|
-
return this.subscribeTrades(symbol, 'spot'
|
|
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
|
|
954
|
-
return this.subscribeKlines(symbol, interval, 'spot'
|
|
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
|
|
960
|
-
return this.subscribeSymbolMini24hrTicker(symbol, 'spot'
|
|
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(
|
|
966
|
-
return this.subscribeAllMini24hrTickers('spot'
|
|
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
|
|
972
|
-
return this.subscribeSymbol24hrTicker(symbol, 'spot'
|
|
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(
|
|
978
|
-
return this.subscribeAll24hrTickers('spot'
|
|
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
|
|
984
|
-
return this.subscribeSymbolBookTicker(symbol, 'spot'
|
|
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
|
|
996
|
-
return this.subscribePartialBookDepths(symbol, levels, updateMs, 'spot'
|
|
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
|
|
1002
|
-
return this.subscribeDiffBookDepth(symbol, updateMs, 'spot'
|
|
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;
|