bybit-api 3.10.29 → 4.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -178
- package/lib/index.d.ts +0 -11
- package/lib/index.js +0 -11
- package/lib/index.js.map +1 -1
- package/lib/rest-client-v5.d.ts +1 -1
- package/lib/rest-client-v5.js +1 -1
- package/lib/spot-client-v3.d.ts +8 -81
- package/lib/spot-client-v3.js +7 -172
- package/lib/spot-client-v3.js.map +1 -1
- package/lib/types/index.d.ts +1 -2
- package/lib/types/index.js +1 -2
- package/lib/types/index.js.map +1 -1
- package/lib/types/request/index.d.ts +0 -1
- package/lib/types/request/index.js +0 -1
- package/lib/types/request/index.js.map +1 -1
- package/lib/types/request/v5-account.d.ts +1 -1
- package/lib/types/request/v5-asset.d.ts +1 -1
- package/lib/types/request/v5-market.d.ts +1 -1
- package/lib/types/request/v5-position.d.ts +1 -1
- package/lib/types/request/v5-pre-upgrade.d.ts +1 -1
- package/lib/types/request/v5-spot-leverage-token.d.ts +1 -1
- package/lib/types/request/v5-trade.d.ts +1 -1
- package/lib/types/request/v5-user.d.ts +1 -1
- package/lib/types/response/v5-account.d.ts +1 -1
- package/lib/types/response/v5-asset.d.ts +1 -1
- package/lib/types/response/v5-market.d.ts +1 -1
- package/lib/types/response/v5-position.d.ts +1 -1
- package/lib/types/response/v5-spot-leverage-token.d.ts +1 -1
- package/lib/types/response/v5-trade.d.ts +1 -1
- package/lib/types/response/v5-user.d.ts +1 -1
- package/lib/types/{request/spot.js → shared-v5.js} +1 -1
- package/lib/types/shared-v5.js.map +1 -0
- package/lib/types/shared.d.ts +1 -19
- package/lib/types/websockets/index.d.ts +3 -0
- package/lib/types/websockets/index.js +20 -0
- package/lib/types/websockets/index.js.map +1 -0
- package/lib/types/websockets/ws-api.d.ts +73 -0
- package/lib/types/websockets/ws-api.js +10 -0
- package/lib/types/websockets/ws-api.js.map +1 -0
- package/lib/types/websockets/ws-confirmations.d.ts +13 -0
- package/lib/types/{websocket.events.js → websockets/ws-confirmations.js} +1 -1
- package/lib/types/websockets/ws-confirmations.js.map +1 -0
- package/lib/types/{websocket.events.d.ts → websockets/ws-events.d.ts} +9 -2
- package/lib/types/websockets/ws-events.js +11 -0
- package/lib/types/websockets/ws-events.js.map +1 -0
- package/lib/types/{websockets.d.ts → websockets/ws-general.d.ts} +35 -10
- package/lib/types/{v5-shared.js → websockets/ws-general.js} +1 -1
- package/lib/types/websockets/ws-general.js.map +1 -0
- package/lib/util/BaseRestClient.d.ts +1 -1
- package/lib/util/BaseRestClient.js +15 -39
- package/lib/util/BaseRestClient.js.map +1 -1
- package/lib/util/BaseWSClient.d.ts +221 -0
- package/lib/util/BaseWSClient.js +733 -0
- package/lib/util/BaseWSClient.js.map +1 -0
- package/lib/util/index.d.ts +1 -2
- package/lib/util/index.js +1 -2
- package/lib/util/index.js.map +1 -1
- package/lib/util/logger.d.ts +2 -5
- package/lib/util/logger.js +3 -12
- package/lib/util/logger.js.map +1 -1
- package/lib/util/requestUtils.d.ts +7 -10
- package/lib/util/requestUtils.js +6 -33
- package/lib/util/requestUtils.js.map +1 -1
- package/lib/util/typeGuards.d.ts +7 -1
- package/lib/util/typeGuards.js +41 -5
- package/lib/util/typeGuards.js.map +1 -1
- package/lib/util/webCryptoAPI.d.ts +10 -0
- package/lib/util/webCryptoAPI.js +69 -0
- package/lib/util/webCryptoAPI.js.map +1 -0
- package/lib/util/websockets/WsStore.d.ts +71 -0
- package/lib/util/websockets/WsStore.js +269 -0
- package/lib/util/websockets/WsStore.js.map +1 -0
- package/lib/util/websockets/WsStore.types.d.ts +50 -0
- package/lib/util/websockets/WsStore.types.js +14 -0
- package/lib/util/websockets/WsStore.types.js.map +1 -0
- package/lib/util/websockets/index.d.ts +3 -0
- package/lib/util/websockets/index.js +20 -0
- package/lib/util/websockets/index.js.map +1 -0
- package/lib/util/websockets/websocket-util.d.ts +92 -0
- package/lib/util/websockets/websocket-util.js +280 -0
- package/lib/util/websockets/websocket-util.js.map +1 -0
- package/lib/websocket-client.d.ts +107 -114
- package/lib/websocket-client.js +422 -716
- package/lib/websocket-client.js.map +1 -1
- package/package.json +4 -4
- package/lib/account-asset-client-v3.d.ts +0 -87
- package/lib/account-asset-client-v3.js +0 -154
- package/lib/account-asset-client-v3.js.map +0 -1
- package/lib/account-asset-client.d.ts +0 -46
- package/lib/account-asset-client.js +0 -108
- package/lib/account-asset-client.js.map +0 -1
- package/lib/contract-client.d.ts +0 -136
- package/lib/contract-client.js +0 -251
- package/lib/contract-client.js.map +0 -1
- package/lib/copy-trading-client.d.ts +0 -60
- package/lib/copy-trading-client.js +0 -114
- package/lib/copy-trading-client.js.map +0 -1
- package/lib/inverse-client.d.ts +0 -104
- package/lib/inverse-client.js +0 -213
- package/lib/inverse-client.js.map +0 -1
- package/lib/inverse-futures-client.d.ts +0 -217
- package/lib/inverse-futures-client.js +0 -224
- package/lib/inverse-futures-client.js.map +0 -1
- package/lib/linear-client.d.ts +0 -112
- package/lib/linear-client.js +0 -225
- package/lib/linear-client.js.map +0 -1
- package/lib/spot-client.d.ts +0 -46
- package/lib/spot-client.js +0 -131
- package/lib/spot-client.js.map +0 -1
- package/lib/types/request/spot.d.ts +0 -79
- package/lib/types/request/spot.js.map +0 -1
- package/lib/types/v5-shared.js.map +0 -1
- package/lib/types/websocket.events.js.map +0 -1
- package/lib/types/websockets.js +0 -3
- package/lib/types/websockets.js.map +0 -1
- package/lib/types/ws-events/failed-topic-subscription-confirmation.d.ts +0 -4
- package/lib/types/ws-events/failed-topic-subscription-confirmation.js +0 -3
- package/lib/types/ws-events/failed-topic-subscription-confirmation.js.map +0 -1
- package/lib/types/ws-events/succeeded-topic-subscription-confirmation.d.ts +0 -4
- package/lib/types/ws-events/succeeded-topic-subscription-confirmation.js +0 -3
- package/lib/types/ws-events/succeeded-topic-subscription-confirmation.js.map +0 -1
- package/lib/types/ws-events/topic-subscription-confirmation.d.ts +0 -7
- package/lib/types/ws-events/topic-subscription-confirmation.js +0 -3
- package/lib/types/ws-events/topic-subscription-confirmation.js.map +0 -1
- package/lib/unified-margin-client.d.ts +0 -140
- package/lib/unified-margin-client.js +0 -270
- package/lib/unified-margin-client.js.map +0 -1
- package/lib/usdc-option-client.d.ts +0 -110
- package/lib/usdc-option-client.js +0 -196
- package/lib/usdc-option-client.js.map +0 -1
- package/lib/usdc-perpetual-client.d.ts +0 -97
- package/lib/usdc-perpetual-client.js +0 -191
- package/lib/usdc-perpetual-client.js.map +0 -1
- package/lib/util/WsStore.d.ts +0 -57
- package/lib/util/WsStore.js +0 -97
- package/lib/util/WsStore.js.map +0 -1
- package/lib/util/browser-support.d.ts +0 -1
- package/lib/util/browser-support.js +0 -26
- package/lib/util/browser-support.js.map +0 -1
- package/lib/util/node-support.d.ts +0 -2
- package/lib/util/node-support.js +0 -21
- package/lib/util/node-support.js.map +0 -1
- package/lib/util/websocket-util.d.ts +0 -68
- package/lib/util/websocket-util.js +0 -506
- package/lib/util/websocket-util.js.map +0 -1
- /package/lib/types/{v5-shared.d.ts → shared-v5.d.ts} +0 -0
package/lib/websocket-client.js
CHANGED
|
@@ -8,316 +8,41 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
exports.WebsocketClient = void 0;
|
|
16
|
-
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
|
|
17
|
-
/* eslint-disable max-len */
|
|
18
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
19
|
-
const events_1 = require("events");
|
|
20
|
-
const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
|
|
21
|
-
const contract_client_1 = require("./contract-client");
|
|
22
|
-
const inverse_client_1 = require("./inverse-client");
|
|
23
|
-
const linear_client_1 = require("./linear-client");
|
|
24
|
-
const rest_client_v5_1 = require("./rest-client-v5");
|
|
25
|
-
const spot_client_1 = require("./spot-client");
|
|
26
|
-
const spot_client_v3_1 = require("./spot-client-v3");
|
|
27
|
-
const unified_margin_client_1 = require("./unified-margin-client");
|
|
28
|
-
const usdc_option_client_1 = require("./usdc-option-client");
|
|
29
|
-
const usdc_perpetual_client_1 = require("./usdc-perpetual-client");
|
|
30
13
|
const util_1 = require("./util");
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
class WebsocketClient extends events_1.EventEmitter {
|
|
36
|
-
constructor(options, logger) {
|
|
37
|
-
super();
|
|
38
|
-
this.pendingTopicsSubscriptions = [];
|
|
39
|
-
this.logger = logger || util_1.DefaultLogger;
|
|
40
|
-
this.wsStore = new WsStore_1.default(this.logger);
|
|
41
|
-
this.options = Object.assign({ testnet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, recvWindow: 5000, fetchTimeOffsetBeforeAuth: false }, options);
|
|
42
|
-
this.options.restOptions = Object.assign(Object.assign({}, this.options.restOptions), { testnet: this.options.testnet });
|
|
43
|
-
this.prepareRESTClient();
|
|
44
|
-
// add default error handling so this doesn't crash node (if the user didn't set a handler)
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
46
|
-
this.on('error', () => { });
|
|
47
|
-
}
|
|
48
|
-
/** Get the WsStore that tracks websockets & topics */
|
|
49
|
-
getWsStore() {
|
|
50
|
-
return this.wsStore;
|
|
51
|
-
}
|
|
52
|
-
isTestnet() {
|
|
53
|
-
return this.options.testnet === true;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Subscribe to V5 topics & track/persist them.
|
|
57
|
-
* @param wsTopics - topic or list of topics
|
|
58
|
-
* @param category - the API category this topic is for (e.g. "linear"). The value is only important when connecting to public topics and will be ignored for private topics.
|
|
59
|
-
* @param isPrivateTopic - optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
|
|
60
|
-
*/
|
|
61
|
-
subscribeV5(wsTopics, category, isPrivateTopic) {
|
|
62
|
-
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
63
|
-
return new Promise((resolver, rejector) => {
|
|
64
|
-
topics.forEach((topic) => {
|
|
65
|
-
const wsKey = (0, util_1.getWsKeyForTopic)(this.options.market, topic, isPrivateTopic, category);
|
|
66
|
-
// Persist topic for reconnects
|
|
67
|
-
this.wsStore.addTopic(wsKey, topic);
|
|
68
|
-
this.upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector);
|
|
69
|
-
// if connected, send subscription request
|
|
70
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
71
|
-
return this.requestSubscribeTopics(wsKey, [topic]);
|
|
72
|
-
}
|
|
73
|
-
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
|
|
74
|
-
if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) &&
|
|
75
|
-
!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
|
|
76
|
-
return this.connect(wsKey);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
14
|
+
const BaseWSClient_1 = require("./util/BaseWSClient");
|
|
15
|
+
const webCryptoAPI_1 = require("./util/webCryptoAPI");
|
|
16
|
+
const WS_LOGGER_CATEGORY = { category: 'bybit-ws' };
|
|
17
|
+
class WebsocketClient extends BaseWSClient_1.BaseWebsocketClient {
|
|
81
18
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* Note: for public V5 topics use the `subscribeV5()` method.
|
|
85
|
-
*
|
|
86
|
-
* Topics will be automatically resubscribed to if the connection resets/drops/reconnects.
|
|
87
|
-
* @param wsTopics - topic or list of topics
|
|
88
|
-
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
|
|
89
|
-
*/
|
|
90
|
-
subscribe(wsTopics, isPrivateTopic) {
|
|
91
|
-
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
92
|
-
if (this.options.market === 'v5') {
|
|
93
|
-
topics.forEach((topic) => {
|
|
94
|
-
if (!(0, util_1.isPrivateWsTopic)(topic)) {
|
|
95
|
-
throw new Error('For public "v5" websocket topics, use the subscribeV5() method & provide the category parameter');
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
return new Promise((resolver, rejector) => {
|
|
100
|
-
topics.forEach((topic) => {
|
|
101
|
-
const wsKey = (0, util_1.getWsKeyForTopic)(this.options.market, topic, isPrivateTopic);
|
|
102
|
-
// Persist topic for reconnects
|
|
103
|
-
this.wsStore.addTopic(wsKey, topic);
|
|
104
|
-
this.upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector);
|
|
105
|
-
// if connected, send subscription request
|
|
106
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
107
|
-
return this.requestSubscribeTopics(wsKey, [topic]);
|
|
108
|
-
}
|
|
109
|
-
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
|
|
110
|
-
if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) &&
|
|
111
|
-
!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
|
|
112
|
-
return this.connect(wsKey);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector) {
|
|
118
|
-
const existingWsKeyPendingSubscriptions = this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
|
|
119
|
-
if (!existingWsKeyPendingSubscriptions) {
|
|
120
|
-
this.pendingTopicsSubscriptions.push({
|
|
121
|
-
wsKey,
|
|
122
|
-
resolver,
|
|
123
|
-
rejector,
|
|
124
|
-
failedTopicsSubscriptions: new Set(),
|
|
125
|
-
pendingTopicsSubscriptions: new Set([topic]),
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.add(topic);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Unsubscribe from V5 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
|
|
133
|
-
* @param wsTopics - topic or list of topics
|
|
134
|
-
* @param category - the API category this topic is for (e.g. "linear"). The value is only important when connecting to public topics and will be ignored for private topics.
|
|
135
|
-
* @param isPrivateTopic - optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
|
|
19
|
+
* Request connection of all dependent (public & private) websockets, instead of waiting
|
|
20
|
+
* for automatic connection by SDK.
|
|
136
21
|
*/
|
|
137
|
-
|
|
138
|
-
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
139
|
-
topics.forEach((topic) => {
|
|
140
|
-
const wsKey = (0, util_1.getWsKeyForTopic)(this.options.market, topic, isPrivateTopic, category);
|
|
141
|
-
// Remove topic from persistence for reconnects
|
|
142
|
-
this.wsStore.deleteTopic(wsKey, topic);
|
|
143
|
-
this.removeTopicPendingSubscription(wsKey, topic);
|
|
144
|
-
// unsubscribe request only necessary if active connection exists
|
|
145
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
146
|
-
this.requestUnsubscribeTopics(wsKey, [topic]);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
removeTopicPendingSubscription(wsKey, topic) {
|
|
151
|
-
const existingWsKeyPendingSubscriptions = this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
|
|
152
|
-
if (existingWsKeyPendingSubscriptions) {
|
|
153
|
-
existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.delete(topic);
|
|
154
|
-
if (!existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.size) {
|
|
155
|
-
this.pendingTopicsSubscriptions =
|
|
156
|
-
this.pendingTopicsSubscriptions.filter((s) => s.wsKey !== wsKey);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
clearTopicsPendingSubscriptions(wsKey) {
|
|
161
|
-
this.pendingTopicsSubscriptions = this.pendingTopicsSubscriptions.filter((s) => s.wsKey !== wsKey);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Unsubscribe from V1-V3 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
|
|
165
|
-
*
|
|
166
|
-
* Note: For public V5 topics, use `unsubscribeV5()` instead!
|
|
167
|
-
*
|
|
168
|
-
* @param wsTopics topic or list of topics
|
|
169
|
-
* @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
|
|
170
|
-
*/
|
|
171
|
-
unsubscribe(wsTopics, isPrivateTopic) {
|
|
172
|
-
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
173
|
-
if (this.options.market === 'v5') {
|
|
174
|
-
topics.forEach((topic) => {
|
|
175
|
-
if (!(0, util_1.isPrivateWsTopic)(topic)) {
|
|
176
|
-
throw new Error('For public "v5" websocket topics, use the unsubscribeV5() method & provide the category parameter');
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
topics.forEach((topic) => {
|
|
181
|
-
const wsKey = (0, util_1.getWsKeyForTopic)(this.options.market, topic, isPrivateTopic);
|
|
182
|
-
// Remove topic from persistence for reconnects
|
|
183
|
-
this.wsStore.deleteTopic(wsKey, topic);
|
|
184
|
-
this.removeTopicPendingSubscription(wsKey, topic);
|
|
185
|
-
// unsubscribe request only necessary if active connection exists
|
|
186
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
187
|
-
this.requestUnsubscribeTopics(wsKey, [topic]);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* @private Only used if we fetch exchange time before attempting auth. Disabled by default.
|
|
193
|
-
* I've removed this for ftx and it's working great, tempted to remove this here
|
|
194
|
-
*/
|
|
195
|
-
prepareRESTClient() {
|
|
22
|
+
connectAll() {
|
|
196
23
|
switch (this.options.market) {
|
|
197
|
-
case 'inverse': {
|
|
198
|
-
this.restClient = new inverse_client_1.InverseClient(this.options.restOptions, this.options.requestOptions);
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
case 'linear': {
|
|
202
|
-
this.restClient = new linear_client_1.LinearClient(this.options.restOptions, this.options.requestOptions);
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
case 'spot': {
|
|
206
|
-
this.restClient = new spot_client_1.SpotClient(this.options.restOptions, this.options.requestOptions);
|
|
207
|
-
this.connectPublic();
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
case 'spotv3': {
|
|
211
|
-
this.restClient = new spot_client_v3_1.SpotClientV3(this.options.restOptions, this.options.requestOptions);
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
case 'usdcOption': {
|
|
215
|
-
this.restClient = new usdc_option_client_1.USDCOptionClient(this.options.restOptions, this.options.requestOptions);
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
case 'usdcPerp': {
|
|
219
|
-
this.restClient = new usdc_perpetual_client_1.USDCPerpetualClient(this.options.restOptions, this.options.requestOptions);
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
case 'unifiedOption':
|
|
223
|
-
case 'unifiedPerp': {
|
|
224
|
-
this.restClient = new unified_margin_client_1.UnifiedMarginClient(this.options.restOptions, this.options.requestOptions);
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
case 'contractInverse':
|
|
228
|
-
case 'contractUSDT': {
|
|
229
|
-
this.restClient = new contract_client_1.ContractClient(this.options.restOptions, this.options.requestOptions);
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
24
|
case 'v5': {
|
|
233
|
-
this.
|
|
234
|
-
break;
|
|
25
|
+
return [...this.connectPublic(), this.connectPrivate()];
|
|
235
26
|
}
|
|
236
27
|
default: {
|
|
237
|
-
throw (0, util_1.neverGuard)(this.options.market, '
|
|
28
|
+
throw (0, util_1.neverGuard)(this.options.market, 'connectAll(): Unhandled market');
|
|
238
29
|
}
|
|
239
30
|
}
|
|
240
31
|
}
|
|
241
|
-
close(wsKey, force) {
|
|
242
|
-
this.logger.info('Closing connection', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
243
|
-
this.setWsState(wsKey, util_1.WsConnectionStateEnum.CLOSING);
|
|
244
|
-
this.clearTimers(wsKey);
|
|
245
|
-
const ws = this.getWs(wsKey);
|
|
246
|
-
ws === null || ws === void 0 ? void 0 : ws.close();
|
|
247
|
-
if (force) {
|
|
248
|
-
(0, util_1.safeTerminateWs)(ws);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
closeAll(force) {
|
|
252
|
-
const keys = this.wsStore.getKeys();
|
|
253
|
-
this.logger.info(`Closing all ws connections: ${keys}`);
|
|
254
|
-
keys.forEach((key) => {
|
|
255
|
-
this.close(key, force);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
32
|
/**
|
|
259
|
-
*
|
|
33
|
+
* Ensures the WS API connection is active and ready.
|
|
34
|
+
*
|
|
35
|
+
* You do not need to call this, but if you call this before making any WS API requests,
|
|
36
|
+
* it can accelerate the first request (by preparing the connection in advance).
|
|
260
37
|
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// only one for inverse
|
|
265
|
-
return [...this.connectPublic()];
|
|
266
|
-
}
|
|
267
|
-
// these all have separate public & private ws endpoints
|
|
268
|
-
case 'linear':
|
|
269
|
-
case 'spot':
|
|
270
|
-
case 'spotv3':
|
|
271
|
-
case 'usdcOption':
|
|
272
|
-
case 'usdcPerp':
|
|
273
|
-
case 'unifiedPerp':
|
|
274
|
-
case 'unifiedOption':
|
|
275
|
-
case 'contractUSDT':
|
|
276
|
-
case 'contractInverse': {
|
|
277
|
-
return [...this.connectPublic(), this.connectPrivate()];
|
|
278
|
-
}
|
|
279
|
-
case 'v5': {
|
|
280
|
-
return [this.connectPrivate()];
|
|
281
|
-
}
|
|
282
|
-
default: {
|
|
283
|
-
throw (0, util_1.neverGuard)(this.options.market, 'connectAll(): Unhandled market');
|
|
284
|
-
}
|
|
285
|
-
}
|
|
38
|
+
connectWSAPI() {
|
|
39
|
+
/** This call automatically ensures the connection is active AND authenticated before resolving */
|
|
40
|
+
return this.assertIsAuthenticated(util_1.WS_KEY_MAP.v5PrivateTrade);
|
|
286
41
|
}
|
|
287
42
|
connectPublic() {
|
|
288
43
|
switch (this.options.market) {
|
|
289
|
-
case '
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
case 'linear': {
|
|
293
|
-
return [this.connect(util_1.WS_KEY_MAP.linearPublic)];
|
|
294
|
-
}
|
|
295
|
-
case 'spot': {
|
|
296
|
-
return [this.connect(util_1.WS_KEY_MAP.spotPublic)];
|
|
297
|
-
}
|
|
298
|
-
case 'spotv3': {
|
|
299
|
-
return [this.connect(util_1.WS_KEY_MAP.spotV3Public)];
|
|
300
|
-
}
|
|
301
|
-
case 'usdcOption': {
|
|
302
|
-
return [this.connect(util_1.WS_KEY_MAP.usdcOptionPublic)];
|
|
303
|
-
}
|
|
304
|
-
case 'usdcPerp': {
|
|
305
|
-
return [this.connect(util_1.WS_KEY_MAP.usdcPerpPublic)];
|
|
306
|
-
}
|
|
307
|
-
case 'unifiedOption': {
|
|
308
|
-
return [this.connect(util_1.WS_KEY_MAP.unifiedOptionPublic)];
|
|
309
|
-
}
|
|
310
|
-
case 'unifiedPerp': {
|
|
311
|
-
return [
|
|
312
|
-
this.connect(util_1.WS_KEY_MAP.unifiedPerpUSDTPublic),
|
|
313
|
-
this.connect(util_1.WS_KEY_MAP.unifiedPerpUSDCPublic),
|
|
314
|
-
];
|
|
315
|
-
}
|
|
316
|
-
case 'contractUSDT':
|
|
317
|
-
return [this.connect(util_1.WS_KEY_MAP.contractUSDTPublic)];
|
|
318
|
-
case 'contractInverse':
|
|
319
|
-
return [this.connect(util_1.WS_KEY_MAP.contractInversePublic)];
|
|
320
|
-
case 'v5': {
|
|
44
|
+
case 'v5':
|
|
45
|
+
default: {
|
|
321
46
|
return [
|
|
322
47
|
this.connect(util_1.WS_KEY_MAP.v5SpotPublic),
|
|
323
48
|
this.connect(util_1.WS_KEY_MAP.v5LinearPublic),
|
|
@@ -325,121 +50,213 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
325
50
|
this.connect(util_1.WS_KEY_MAP.v5OptionPublic),
|
|
326
51
|
];
|
|
327
52
|
}
|
|
328
|
-
default: {
|
|
329
|
-
throw (0, util_1.neverGuard)(this.options.market, 'connectPublic(): Unhandled market');
|
|
330
|
-
}
|
|
331
53
|
}
|
|
332
54
|
}
|
|
333
55
|
connectPrivate() {
|
|
334
56
|
switch (this.options.market) {
|
|
335
|
-
case '
|
|
336
|
-
return this.connect(util_1.WS_KEY_MAP.inverse);
|
|
337
|
-
}
|
|
338
|
-
case 'linear': {
|
|
339
|
-
return this.connect(util_1.WS_KEY_MAP.linearPrivate);
|
|
340
|
-
}
|
|
341
|
-
case 'spot': {
|
|
342
|
-
return this.connect(util_1.WS_KEY_MAP.spotPrivate);
|
|
343
|
-
}
|
|
344
|
-
case 'spotv3': {
|
|
345
|
-
return this.connect(util_1.WS_KEY_MAP.spotV3Private);
|
|
346
|
-
}
|
|
347
|
-
case 'usdcOption': {
|
|
348
|
-
return this.connect(util_1.WS_KEY_MAP.usdcOptionPrivate);
|
|
349
|
-
}
|
|
350
|
-
case 'usdcPerp': {
|
|
351
|
-
return this.connect(util_1.WS_KEY_MAP.usdcPerpPrivate);
|
|
352
|
-
}
|
|
353
|
-
case 'unifiedPerp':
|
|
354
|
-
case 'unifiedOption': {
|
|
355
|
-
return this.connect(util_1.WS_KEY_MAP.unifiedPrivate);
|
|
356
|
-
}
|
|
357
|
-
case 'contractUSDT':
|
|
358
|
-
return this.connect(util_1.WS_KEY_MAP.contractUSDTPrivate);
|
|
359
|
-
case 'contractInverse':
|
|
360
|
-
return this.connect(util_1.WS_KEY_MAP.contractInversePrivate);
|
|
361
|
-
case 'v5': {
|
|
362
|
-
return this.connect(util_1.WS_KEY_MAP.v5Private);
|
|
363
|
-
}
|
|
57
|
+
case 'v5':
|
|
364
58
|
default: {
|
|
365
|
-
|
|
59
|
+
return this.connect(util_1.WS_KEY_MAP.v5Private);
|
|
366
60
|
}
|
|
367
61
|
}
|
|
368
62
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Subscribe to V5 topics & track/persist them.
|
|
65
|
+
* @param wsTopics - topic or list of topics
|
|
66
|
+
* @param category - the API category this topic is for (e.g. "linear").
|
|
67
|
+
* The value is only important when connecting to public topics and will be ignored for private topics.
|
|
68
|
+
* @param isPrivateTopic - optional - the library will try to detect private topics, you can use this
|
|
69
|
+
* to mark a topic as private (if the topic isn't recognised yet)
|
|
70
|
+
*/
|
|
71
|
+
subscribeV5(wsTopics, category, isPrivateTopic) {
|
|
72
|
+
const topicRequests = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
73
|
+
const perWsKeyTopics = {};
|
|
74
|
+
// Sort into per-WsKey batches, in case there is a mix of topics here
|
|
75
|
+
for (const topic of topicRequests) {
|
|
76
|
+
const derivedWsKey = (0, util_1.getWsKeyForTopic)(this.options.market, topic, isPrivateTopic, category);
|
|
77
|
+
const wsRequest = {
|
|
78
|
+
topic: topic,
|
|
79
|
+
category: category,
|
|
80
|
+
};
|
|
81
|
+
if (!perWsKeyTopics[derivedWsKey] ||
|
|
82
|
+
!Array.isArray(perWsKeyTopics[derivedWsKey])) {
|
|
83
|
+
perWsKeyTopics[derivedWsKey] = [];
|
|
84
|
+
}
|
|
85
|
+
perWsKeyTopics[derivedWsKey].push(wsRequest);
|
|
86
|
+
}
|
|
87
|
+
const promises = [];
|
|
88
|
+
// Batch sub topics per ws key
|
|
89
|
+
for (const wsKey in perWsKeyTopics) {
|
|
90
|
+
const wsKeyTopicRequests = perWsKeyTopics[wsKey];
|
|
91
|
+
if (wsKeyTopicRequests === null || wsKeyTopicRequests === void 0 ? void 0 : wsKeyTopicRequests.length) {
|
|
92
|
+
const requestPromise = this.subscribeTopicsForWsKey(wsKeyTopicRequests, wsKey);
|
|
93
|
+
if (Array.isArray(requestPromise)) {
|
|
94
|
+
promises.push(...requestPromise);
|
|
379
95
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTING);
|
|
96
|
+
else {
|
|
97
|
+
promises.push(requestPromise);
|
|
383
98
|
}
|
|
384
|
-
const authParams = yield this.getAuthParams(wsKey);
|
|
385
|
-
const url = (0, util_1.getWsUrl)(wsKey, this.options, this.logger);
|
|
386
|
-
const ws = this.connectToWsUrl(url + authParams, wsKey);
|
|
387
|
-
return this.wsStore.setWs(wsKey, ws);
|
|
388
99
|
}
|
|
389
|
-
catch (err) {
|
|
390
|
-
this.parseWsError('Connection failed', err, wsKey);
|
|
391
|
-
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
parseWsError(context, error, wsKey) {
|
|
396
|
-
if (!error.message) {
|
|
397
|
-
this.logger.error(`${context} due to unexpected error: `, error);
|
|
398
|
-
this.emit('error', error);
|
|
399
|
-
return;
|
|
400
100
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
101
|
+
// Return promise to resolve midflight WS request (only works if already connected before request)
|
|
102
|
+
return promises;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Unsubscribe from V5 topics & remove them from memory. They won't be re-subscribed to if the
|
|
106
|
+
* connection reconnects.
|
|
107
|
+
*
|
|
108
|
+
* @param wsTopics - topic or list of topics
|
|
109
|
+
* @param category - the API category this topic is for (e.g. "linear"). The value is only
|
|
110
|
+
* important when connecting to public topics and will be ignored for private topics.
|
|
111
|
+
* @param isPrivateTopic - optional - the library will try to detect private topics, you can
|
|
112
|
+
* use this to mark a topic as private (if the topic isn't recognised yet)
|
|
113
|
+
*/
|
|
114
|
+
unsubscribeV5(wsTopics, category, isPrivateTopic) {
|
|
115
|
+
const topicRequests = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
116
|
+
const perWsKeyTopics = {};
|
|
117
|
+
// Sort into per-WsKey batches, in case there is a mix of topics here
|
|
118
|
+
for (const topic of topicRequests) {
|
|
119
|
+
const derivedWsKey = (0, util_1.getWsKeyForTopic)(this.options.market, topic, isPrivateTopic, category);
|
|
120
|
+
const wsRequest = {
|
|
121
|
+
topic: topic,
|
|
122
|
+
category: category,
|
|
123
|
+
};
|
|
124
|
+
if (!perWsKeyTopics[derivedWsKey] ||
|
|
125
|
+
!Array.isArray(perWsKeyTopics[derivedWsKey])) {
|
|
126
|
+
perWsKeyTopics[derivedWsKey] = [];
|
|
127
|
+
}
|
|
128
|
+
perWsKeyTopics[derivedWsKey].push(wsRequest);
|
|
129
|
+
}
|
|
130
|
+
const promises = [];
|
|
131
|
+
// Batch sub topics per ws key
|
|
132
|
+
for (const wsKey in perWsKeyTopics) {
|
|
133
|
+
const wsKeyTopicRequests = perWsKeyTopics[wsKey];
|
|
134
|
+
if (wsKeyTopicRequests === null || wsKeyTopicRequests === void 0 ? void 0 : wsKeyTopicRequests.length) {
|
|
135
|
+
const requestPromise = this.unsubscribeTopicsForWsKey(wsKeyTopicRequests, wsKey);
|
|
136
|
+
if (Array.isArray(requestPromise)) {
|
|
137
|
+
promises.push(...requestPromise);
|
|
410
138
|
}
|
|
411
139
|
else {
|
|
412
|
-
|
|
140
|
+
promises.push(requestPromise);
|
|
413
141
|
}
|
|
414
|
-
|
|
142
|
+
}
|
|
415
143
|
}
|
|
416
|
-
|
|
144
|
+
// Return promise to resolve midflight WS request (only works if already connected before request)
|
|
145
|
+
return promises;
|
|
417
146
|
}
|
|
418
147
|
/**
|
|
419
|
-
*
|
|
148
|
+
* Note: subscribeV5() might be simpler to use. The end result is the same.
|
|
149
|
+
*
|
|
150
|
+
* Request subscription to one or more topics. Pass topics as either an array of strings,
|
|
151
|
+
* or array of objects (if the topic has parameters).
|
|
152
|
+
*
|
|
153
|
+
* Objects should be formatted as {topic: string, params: object, category: CategoryV5}.
|
|
154
|
+
*
|
|
155
|
+
* - Subscriptions are automatically routed to the correct websocket connection.
|
|
156
|
+
* - Authentication/connection is automatic.
|
|
157
|
+
* - Resubscribe after network issues is automatic.
|
|
158
|
+
*
|
|
159
|
+
* Call `unsubscribe(topics)` to remove topics
|
|
420
160
|
*/
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
161
|
+
subscribe(requests, wsKey) {
|
|
162
|
+
const topicRequests = Array.isArray(requests) ? requests : [requests];
|
|
163
|
+
const normalisedTopicRequests = (0, util_1.getNormalisedTopicRequests)(topicRequests);
|
|
164
|
+
const perWsKeyTopics = (0, util_1.getTopicsPerWSKey)(this.options.market, normalisedTopicRequests, wsKey);
|
|
165
|
+
// Batch sub topics per ws key
|
|
166
|
+
for (const wsKey in perWsKeyTopics) {
|
|
167
|
+
const wsKeyTopicRequests = perWsKeyTopics[wsKey];
|
|
168
|
+
if (wsKeyTopicRequests === null || wsKeyTopicRequests === void 0 ? void 0 : wsKeyTopicRequests.length) {
|
|
169
|
+
this.subscribeTopicsForWsKey(wsKeyTopicRequests, wsKey);
|
|
426
170
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Note: unsubscribe() might be simpler to use. The end result is the same.
|
|
175
|
+
* Unsubscribe from one or more topics. Similar to subscribe() but in reverse.
|
|
176
|
+
*
|
|
177
|
+
* - Requests are automatically routed to the correct websocket connection.
|
|
178
|
+
* - These topics will be removed from the topic cache, so they won't be subscribed to again.
|
|
179
|
+
*/
|
|
180
|
+
unsubscribe(requests, wsKey) {
|
|
181
|
+
const topicRequests = Array.isArray(requests) ? requests : [requests];
|
|
182
|
+
const normalisedTopicRequests = (0, util_1.getNormalisedTopicRequests)(topicRequests);
|
|
183
|
+
const perWsKeyTopics = (0, util_1.getTopicsPerWSKey)(this.options.market, normalisedTopicRequests, wsKey);
|
|
184
|
+
// Batch sub topics per ws key
|
|
185
|
+
for (const wsKey in perWsKeyTopics) {
|
|
186
|
+
const wsKeyTopicRequests = perWsKeyTopics[wsKey];
|
|
187
|
+
if (wsKeyTopicRequests === null || wsKeyTopicRequests === void 0 ? void 0 : wsKeyTopicRequests.length) {
|
|
188
|
+
this.unsubscribeTopicsForWsKey(wsKeyTopicRequests, wsKey);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
sendWSAPIRequest() {
|
|
193
|
+
return __awaiter(this, arguments, void 0, function* (wsKey = util_1.WS_KEY_MAP.v5PrivateTrade, operation, params) {
|
|
194
|
+
this.logger.trace(`sendWSAPIRequest(): assert "${wsKey}" is connected`);
|
|
195
|
+
yield this.assertIsConnected(wsKey);
|
|
196
|
+
this.logger.trace('sendWSAPIRequest()->assertIsConnected() ok');
|
|
197
|
+
yield this.assertIsAuthenticated(wsKey);
|
|
198
|
+
this.logger.trace('sendWSAPIRequest()->assertIsAuthenticated() ok');
|
|
199
|
+
const requestEvent = {
|
|
200
|
+
reqId: this.getNewRequestId(),
|
|
201
|
+
header: {
|
|
202
|
+
'X-BAPI-RECV-WINDOW': `${this.options.recvWindow}`,
|
|
203
|
+
'X-BAPI-TIMESTAMP': `${Date.now()}`,
|
|
204
|
+
Referer: util_1.APIID,
|
|
205
|
+
},
|
|
206
|
+
op: operation,
|
|
207
|
+
args: [params],
|
|
208
|
+
};
|
|
209
|
+
// Sign, if needed
|
|
210
|
+
const signedEvent = yield this.signWSAPIRequest(requestEvent);
|
|
211
|
+
// Store deferred promise, resolved within the "resolveEmittableEvents" method while parsing incoming events
|
|
212
|
+
const promiseRef = (0, util_1.getPromiseRefForWSAPIRequest)(requestEvent);
|
|
213
|
+
const deferredPromise = this.getWsStore().createDeferredPromise(wsKey, promiseRef, false);
|
|
214
|
+
this.logger.trace(`sendWSAPIRequest(): sending raw request: ${JSON.stringify(signedEvent, null, 2)}`);
|
|
215
|
+
// Send event
|
|
216
|
+
this.tryWsSend(wsKey, JSON.stringify(signedEvent));
|
|
217
|
+
this.logger.trace(`sendWSAPIRequest(): sent ${operation} event`);
|
|
218
|
+
// Return deferred promise, so caller can await this call
|
|
219
|
+
return deferredPromise.promise;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
*
|
|
224
|
+
*
|
|
225
|
+
* Internal methods - not intended for public use
|
|
226
|
+
*
|
|
227
|
+
*
|
|
228
|
+
*/
|
|
229
|
+
/**
|
|
230
|
+
* @returns The WS URL to connect to for this WS key
|
|
231
|
+
*/
|
|
232
|
+
getWsUrl(wsKey) {
|
|
233
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
234
|
+
const wsBaseURL = (0, util_1.getWsUrl)(wsKey, this.options, this.logger);
|
|
235
|
+
// If auth is needed for this wsKey URL, this returns a suffix
|
|
236
|
+
const authParams = yield this.getWsAuthURLSuffix();
|
|
237
|
+
if (!authParams) {
|
|
238
|
+
return wsBaseURL;
|
|
435
239
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
240
|
+
return wsBaseURL + '?' + authParams;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Return params required to make authorized request
|
|
245
|
+
*/
|
|
246
|
+
getWsAuthURLSuffix() {
|
|
247
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
248
|
+
return '';
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
signMessage(paramsStr, secret, method, algorithm) {
|
|
252
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
253
|
+
if (typeof this.options.customSignMessageFn === 'function') {
|
|
254
|
+
return this.options.customSignMessageFn(paramsStr, secret);
|
|
439
255
|
}
|
|
256
|
+
return yield (0, webCryptoAPI_1.signMessage)(paramsStr, secret, method, algorithm);
|
|
440
257
|
});
|
|
441
258
|
}
|
|
442
|
-
|
|
259
|
+
getWsAuthRequestEvent(wsKey) {
|
|
443
260
|
return __awaiter(this, void 0, void 0, function* () {
|
|
444
261
|
try {
|
|
445
262
|
const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey);
|
|
@@ -448,387 +265,276 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
448
265
|
args: [this.options.key, expiresAt, signature],
|
|
449
266
|
req_id: `${wsKey}-auth`,
|
|
450
267
|
};
|
|
451
|
-
return
|
|
268
|
+
return request;
|
|
452
269
|
}
|
|
453
270
|
catch (e) {
|
|
454
|
-
this.logger.error(e, Object.assign(Object.assign({},
|
|
271
|
+
this.logger.error(e, Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
|
|
272
|
+
throw e;
|
|
455
273
|
}
|
|
456
274
|
});
|
|
457
275
|
}
|
|
458
276
|
getWsAuthSignature(wsKey) {
|
|
459
|
-
var _a;
|
|
460
277
|
return __awaiter(this, void 0, void 0, function* () {
|
|
461
278
|
const { key, secret } = this.options;
|
|
462
279
|
if (!key || !secret) {
|
|
463
|
-
this.logger.
|
|
280
|
+
this.logger.error('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
|
|
464
281
|
throw new Error('Cannot auth - missing api or secret in config');
|
|
465
282
|
}
|
|
466
|
-
this.logger.
|
|
467
|
-
const timeOffset = this.options.fetchTimeOffsetBeforeAuth
|
|
468
|
-
? (yield ((_a = this.restClient) === null || _a === void 0 ? void 0 : _a.fetchTimeOffset())) || 0
|
|
469
|
-
: 0;
|
|
283
|
+
this.logger.trace("Getting auth'd request params", Object.assign(Object.assign({}, WS_LOGGER_CATEGORY), { wsKey }));
|
|
470
284
|
const recvWindow = this.options.recvWindow || 5000;
|
|
471
|
-
const signatureExpiresAt = Date.now() +
|
|
472
|
-
const signature = yield
|
|
285
|
+
const signatureExpiresAt = Date.now() + this.getTimeOffsetMs() + recvWindow;
|
|
286
|
+
const signature = yield this.signMessage('GET/realtime' + signatureExpiresAt, secret, 'hex', 'SHA-256');
|
|
473
287
|
return {
|
|
474
288
|
expiresAt: signatureExpiresAt,
|
|
475
289
|
signature,
|
|
476
290
|
};
|
|
477
291
|
});
|
|
478
292
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
this.setWsState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING);
|
|
485
|
-
}
|
|
486
|
-
if ((_a = this.wsStore.get(wsKey)) === null || _a === void 0 ? void 0 : _a.activeReconnectTimer) {
|
|
487
|
-
this.clearReconnectTimer(wsKey);
|
|
488
|
-
}
|
|
489
|
-
this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
|
|
490
|
-
this.logger.info('Reconnecting to websocket', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
491
|
-
this.clearReconnectTimer(wsKey);
|
|
492
|
-
this.connect(wsKey);
|
|
493
|
-
}, connectionDelayMs);
|
|
293
|
+
signWSAPIRequest(requestEvent) {
|
|
294
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
295
|
+
// Not needed for Bybit. Auth happens only on connection open, automatically.
|
|
296
|
+
return requestEvent;
|
|
297
|
+
});
|
|
494
298
|
}
|
|
495
|
-
|
|
496
|
-
if (this.wsStore.get(wsKey, true).activePongTimer) {
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
this.clearPongTimer(wsKey);
|
|
500
|
-
this.logger.silly('Sending ping', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
299
|
+
sendPingEvent(wsKey) {
|
|
501
300
|
this.tryWsSend(wsKey, JSON.stringify({ op: 'ping' }));
|
|
502
|
-
this.wsStore.get(wsKey, true).activePongTimer = setTimeout(() => this.executeReconnectableClose(wsKey, 'Pong timeout'), this.options.pongTimeout);
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Closes a connection, if it's even open. If open, this will trigger a reconnect asynchronously.
|
|
506
|
-
* If closed, trigger a reconnect immediately
|
|
507
|
-
*/
|
|
508
|
-
executeReconnectableClose(wsKey, reason) {
|
|
509
|
-
this.logger.info(`${reason} - closing socket to reconnect`, Object.assign(Object.assign({}, loggerCategory), { wsKey,
|
|
510
|
-
reason }));
|
|
511
|
-
const wasOpen = this.wsStore.isWsOpen(wsKey);
|
|
512
|
-
this.clearPingTimer(wsKey);
|
|
513
|
-
this.clearPongTimer(wsKey);
|
|
514
|
-
const ws = this.getWs(wsKey);
|
|
515
|
-
if (ws) {
|
|
516
|
-
ws.close();
|
|
517
|
-
(0, util_1.safeTerminateWs)(ws);
|
|
518
|
-
}
|
|
519
|
-
if (!wasOpen) {
|
|
520
|
-
this.logger.info(`${reason} - socket already closed - trigger immediate reconnect`, Object.assign(Object.assign({}, loggerCategory), { wsKey,
|
|
521
|
-
reason }));
|
|
522
|
-
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
|
523
|
-
}
|
|
524
301
|
}
|
|
525
|
-
|
|
526
|
-
this.
|
|
527
|
-
this.clearPongTimer(wsKey);
|
|
528
|
-
this.clearReconnectTimer(wsKey);
|
|
302
|
+
sendPongEvent(wsKey) {
|
|
303
|
+
this.tryWsSend(wsKey, JSON.stringify({ op: 'pong' }));
|
|
529
304
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (wsState === null || wsState === void 0 ? void 0 : wsState.activePingTimer) {
|
|
534
|
-
clearInterval(wsState.activePingTimer);
|
|
535
|
-
wsState.activePingTimer = undefined;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// Expect a pong within a time limit
|
|
539
|
-
clearPongTimer(wsKey) {
|
|
540
|
-
const wsState = this.wsStore.get(wsKey);
|
|
541
|
-
if (wsState === null || wsState === void 0 ? void 0 : wsState.activePongTimer) {
|
|
542
|
-
clearTimeout(wsState.activePongTimer);
|
|
543
|
-
wsState.activePongTimer = undefined;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
clearReconnectTimer(wsKey) {
|
|
547
|
-
const wsState = this.wsStore.get(wsKey);
|
|
548
|
-
if (wsState === null || wsState === void 0 ? void 0 : wsState.activeReconnectTimer) {
|
|
549
|
-
clearTimeout(wsState.activeReconnectTimer);
|
|
550
|
-
wsState.activeReconnectTimer = undefined;
|
|
551
|
-
}
|
|
305
|
+
/** Force subscription requests to be sent in smaller batches, if a number is returned */
|
|
306
|
+
getMaxTopicsPerSubscribeEvent(wsKey) {
|
|
307
|
+
return (0, util_1.getMaxTopicsPerSubscribeEvent)(this.options.market, wsKey);
|
|
552
308
|
}
|
|
553
309
|
/**
|
|
554
|
-
* @
|
|
310
|
+
* @returns one or more correctly structured request events for performing a operations over WS. This can vary per exchange spec.
|
|
555
311
|
*/
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
312
|
+
getWsRequestEvents(market, operation, requests,
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
|
314
|
+
wsKey) {
|
|
315
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
316
|
+
const wsRequestEvents = [];
|
|
317
|
+
const wsRequestBuildingErrors = [];
|
|
318
|
+
switch (market) {
|
|
319
|
+
case 'all': {
|
|
320
|
+
const wsEvent = {
|
|
321
|
+
req_id: this.getNewRequestId(),
|
|
322
|
+
op: operation,
|
|
323
|
+
args: requests.map((r) => r.topic),
|
|
324
|
+
};
|
|
325
|
+
const midflightWsEvent = {
|
|
326
|
+
requestKey: wsEvent.req_id,
|
|
327
|
+
requestEvent: wsEvent,
|
|
328
|
+
};
|
|
329
|
+
wsRequestEvents.push(Object.assign({}, midflightWsEvent));
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
default: {
|
|
333
|
+
throw (0, util_1.neverGuard)(market, `Unhandled market "${market}"`);
|
|
334
|
+
}
|
|
567
335
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
op: 'subscribe',
|
|
574
|
-
args: topics,
|
|
336
|
+
if (wsRequestBuildingErrors.length) {
|
|
337
|
+
const label = wsRequestBuildingErrors.length === requests.length ? 'all' : 'some';
|
|
338
|
+
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) }));
|
|
339
|
+
}
|
|
340
|
+
return wsRequestEvents;
|
|
575
341
|
});
|
|
576
|
-
|
|
342
|
+
}
|
|
343
|
+
getPrivateWSKeys() {
|
|
344
|
+
return util_1.WS_AUTH_ON_CONNECT_KEYS;
|
|
345
|
+
}
|
|
346
|
+
isAuthOnConnectWsKey(wsKey) {
|
|
347
|
+
return util_1.WS_AUTH_ON_CONNECT_KEYS.includes(wsKey);
|
|
577
348
|
}
|
|
578
349
|
/**
|
|
579
|
-
*
|
|
350
|
+
* Determines if a topic is for a private channel, using a hardcoded list of strings
|
|
580
351
|
*/
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) {
|
|
587
|
-
this.logger.silly(`Unsubscribing to topics in batches of ${maxTopicsPerEvent}`);
|
|
588
|
-
for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
|
|
589
|
-
const batch = topics.slice(i, i + maxTopicsPerEvent);
|
|
590
|
-
this.logger.silly(`Unsubscribing to batch of ${batch.length}`);
|
|
591
|
-
this.requestUnsubscribeTopics(wsKey, batch);
|
|
592
|
-
}
|
|
593
|
-
this.logger.silly(`Finished batch unsubscribing to ${topics.length} topics`);
|
|
594
|
-
return;
|
|
352
|
+
isPrivateTopicRequest(request) {
|
|
353
|
+
var _a;
|
|
354
|
+
const topicName = (_a = request === null || request === void 0 ? void 0 : request.topic) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
355
|
+
if (!topicName) {
|
|
356
|
+
return false;
|
|
595
357
|
}
|
|
596
|
-
|
|
597
|
-
op: 'unsubscribe',
|
|
598
|
-
args: topics,
|
|
599
|
-
});
|
|
600
|
-
this.tryWsSend(wsKey, wsMessage);
|
|
358
|
+
return (0, util_1.isPrivateWsTopic)(topicName);
|
|
601
359
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (!wsKey) {
|
|
607
|
-
throw new Error('Cannot send message due to no known websocket for this wsKey');
|
|
608
|
-
}
|
|
609
|
-
const ws = this.getWs(wsKey);
|
|
610
|
-
if (!ws) {
|
|
611
|
-
throw new Error(`${wsKey} socket not connected yet, call "connect(${wsKey}) first then try again when the "open" event arrives`);
|
|
612
|
-
}
|
|
613
|
-
ws.send(wsMessage);
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
361
|
+
isWsPing(msg) {
|
|
362
|
+
if (!msg) {
|
|
363
|
+
return false;
|
|
614
364
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
365
|
+
if (typeof (msg === null || msg === void 0 ? void 0 : msg.data) === 'string') {
|
|
366
|
+
if (msg.data.includes('op": "ping')) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
// console.log('isWsPing?', {
|
|
370
|
+
// data: msg.data,
|
|
371
|
+
// });
|
|
372
|
+
return false;
|
|
618
373
|
}
|
|
374
|
+
return false;
|
|
619
375
|
}
|
|
620
|
-
|
|
376
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
377
|
+
isWsPong(msg) {
|
|
621
378
|
var _a;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
return ws;
|
|
630
|
-
}
|
|
631
|
-
onWsOpen(event, wsKey) {
|
|
632
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
633
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) {
|
|
634
|
-
this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, testnet: this.isTestnet(), market: this.options.market }));
|
|
635
|
-
this.emit('open', { wsKey, event });
|
|
636
|
-
}
|
|
637
|
-
else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
|
|
638
|
-
this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
639
|
-
this.emit('reconnected', { wsKey, event });
|
|
640
|
-
}
|
|
641
|
-
this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED);
|
|
642
|
-
// Some websockets require an auth packet to be sent after opening the connection
|
|
643
|
-
if (util_1.WS_AUTH_ON_CONNECT_KEYS.includes(wsKey)) {
|
|
644
|
-
this.logger.info('Sending auth request...');
|
|
645
|
-
yield this.sendAuthRequest(wsKey);
|
|
379
|
+
if (!msg) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
if (typeof (msg === null || msg === void 0 ? void 0 : msg.data) === 'string') {
|
|
383
|
+
// public ws connections
|
|
384
|
+
if (msg.data.includes('ret_msg":"pong')) {
|
|
385
|
+
return true;
|
|
646
386
|
}
|
|
647
|
-
//
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
this.logger.info('Subscribing to topics', Object.assign(Object.assign({}, loggerCategory), { wsKey,
|
|
651
|
-
topics }));
|
|
652
|
-
this.requestSubscribeTopics(wsKey, topics);
|
|
387
|
+
// private ws connections
|
|
388
|
+
if (msg.data.includes('op":"pong')) {
|
|
389
|
+
return true;
|
|
653
390
|
}
|
|
654
|
-
|
|
655
|
-
|
|
391
|
+
// console.log('isWsPong?', {
|
|
392
|
+
// data: msg.data,
|
|
393
|
+
// });
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (((_a = msg.event) === null || _a === void 0 ? void 0 : _a.ret_msg) === 'pong') {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
return (msg === null || msg === void 0 ? void 0 : msg.pong) || (0, util_1.isWsPong)(msg);
|
|
656
400
|
}
|
|
657
|
-
|
|
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
|
+
const results = [];
|
|
658
406
|
try {
|
|
659
|
-
|
|
660
|
-
this.
|
|
661
|
-
|
|
662
|
-
// this.logger.silly('Received event', {
|
|
663
|
-
// ...loggerCategory,
|
|
407
|
+
const parsed = JSON.parse(event.data);
|
|
408
|
+
// this.logger.trace('resolveEmittableEvents', {
|
|
409
|
+
// ...WS_LOGGER_CATEGORY,
|
|
664
410
|
// wsKey,
|
|
665
|
-
//
|
|
411
|
+
// parsed: JSON.stringify(parsed),
|
|
666
412
|
// });
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
413
|
+
// Only applies to the V5 WS topics
|
|
414
|
+
if ((0, util_1.isTopicSubscriptionConfirmation)(parsed) && parsed.req_id) {
|
|
415
|
+
const isTopicSubscriptionSuccessEvent = (0, util_1.isTopicSubscriptionSuccess)(parsed);
|
|
416
|
+
this.updatePendingTopicSubscriptionStatus(wsKey, parsed.req_id, parsed, isTopicSubscriptionSuccessEvent);
|
|
417
|
+
}
|
|
418
|
+
const EVENTS_AUTHENTICATED = ['auth'];
|
|
419
|
+
const EVENTS_RESPONSES = [
|
|
420
|
+
'subscribe',
|
|
421
|
+
'unsubscribe',
|
|
422
|
+
'COMMAND_RESP',
|
|
423
|
+
'ping',
|
|
424
|
+
'pong',
|
|
425
|
+
];
|
|
426
|
+
const eventTopic = parsed === null || parsed === void 0 ? void 0 : parsed.topic;
|
|
427
|
+
const eventOperation = parsed === null || parsed === void 0 ? void 0 : parsed.op;
|
|
428
|
+
// WS API response
|
|
429
|
+
if ((0, util_1.isWSAPIResponse)(parsed)) {
|
|
430
|
+
const retCode = parsed.retCode;
|
|
431
|
+
const reqId = parsed.reqId;
|
|
432
|
+
const isError = retCode !== 0;
|
|
433
|
+
const promiseRef = [parsed.op, reqId].join('_');
|
|
434
|
+
if (!reqId) {
|
|
435
|
+
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:', {
|
|
436
|
+
wsKey,
|
|
437
|
+
promiseRef,
|
|
438
|
+
parsedEvent: parsed,
|
|
439
|
+
});
|
|
674
440
|
}
|
|
675
|
-
|
|
676
|
-
|
|
441
|
+
// WS API Exception
|
|
442
|
+
if (isError) {
|
|
443
|
+
try {
|
|
444
|
+
this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
this.logger.error('Exception trying to reject WSAPI promise', {
|
|
448
|
+
wsKey,
|
|
449
|
+
promiseRef,
|
|
450
|
+
parsedEvent: parsed,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
results.push({
|
|
454
|
+
eventType: 'error',
|
|
455
|
+
event: parsed,
|
|
456
|
+
isWSAPIResponse: true,
|
|
457
|
+
});
|
|
458
|
+
return results;
|
|
677
459
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
(msg === null || msg === void 0 ? void 0 : msg.code) ||
|
|
689
|
-
// spot v3
|
|
690
|
-
(msg === null || msg === void 0 ? void 0 : msg.type) === 'error' ||
|
|
691
|
-
// usdc options
|
|
692
|
-
(msg === null || msg === void 0 ? void 0 : msg.success) === false) {
|
|
693
|
-
return this.emit('error', Object.assign(Object.assign({}, msg), { wsKey }));
|
|
694
|
-
}
|
|
695
|
-
this.logger.warning('Unhandled/unrecognised ws event message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
|
|
696
|
-
wsKey }));
|
|
697
|
-
}
|
|
698
|
-
catch (e) {
|
|
699
|
-
this.logger.error('Failed to parse ws event message', Object.assign(Object.assign({}, loggerCategory), { error: e, event,
|
|
700
|
-
wsKey }));
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
updatePendingTopicSubscriptionStatus(wsKey, msg) {
|
|
704
|
-
const requestsIds = msg.req_id;
|
|
705
|
-
const pendingTopicsSubscriptions = this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
|
|
706
|
-
if (!pendingTopicsSubscriptions)
|
|
707
|
-
return;
|
|
708
|
-
const splitRequestsIds = requestsIds.split(',');
|
|
709
|
-
if (!(0, util_1.isTopicSubscriptionSuccess)(msg)) {
|
|
710
|
-
splitRequestsIds.forEach((req_id) => pendingTopicsSubscriptions.failedTopicsSubscriptions.add(req_id));
|
|
711
|
-
}
|
|
712
|
-
splitRequestsIds.forEach((req_id) => {
|
|
713
|
-
this.removeTopicPendingSubscription(wsKey, req_id);
|
|
714
|
-
if (!pendingTopicsSubscriptions.pendingTopicsSubscriptions.size &&
|
|
715
|
-
!pendingTopicsSubscriptions.failedTopicsSubscriptions.size) {
|
|
716
|
-
// all topics have been subscribed successfully, so we can resolve the subscription request
|
|
717
|
-
pendingTopicsSubscriptions.resolver();
|
|
718
|
-
this.clearTopicsPendingSubscriptions(wsKey);
|
|
719
|
-
}
|
|
720
|
-
if (!pendingTopicsSubscriptions.pendingTopicsSubscriptions.size &&
|
|
721
|
-
pendingTopicsSubscriptions.failedTopicsSubscriptions.size) {
|
|
722
|
-
// not all topics have been subscribed successfully, so we reject the subscription request
|
|
723
|
-
// and let the caller handle the situation by providing the list of failed subscriptions requests
|
|
724
|
-
const failedSubscriptionsMessage = `(${[
|
|
725
|
-
...pendingTopicsSubscriptions.failedTopicsSubscriptions,
|
|
726
|
-
].toString()}) failed to subscribe`;
|
|
727
|
-
pendingTopicsSubscriptions.rejector(failedSubscriptionsMessage);
|
|
728
|
-
this.clearTopicsPendingSubscriptions(wsKey);
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
onWsClose(event, wsKey) {
|
|
733
|
-
this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
734
|
-
if (this.wsStore.getConnectionState(wsKey) !== util_1.WsConnectionStateEnum.CLOSING) {
|
|
735
|
-
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
|
736
|
-
this.emit('reconnect', { wsKey, event });
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
this.setWsState(wsKey, util_1.WsConnectionStateEnum.INITIAL);
|
|
740
|
-
this.emit('close', { wsKey, event });
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
getWs(wsKey) {
|
|
744
|
-
return this.wsStore.getWs(wsKey);
|
|
745
|
-
}
|
|
746
|
-
setWsState(wsKey, state) {
|
|
747
|
-
this.wsStore.setConnectionState(wsKey, state);
|
|
748
|
-
}
|
|
749
|
-
wrongMarketError(market) {
|
|
750
|
-
return new Error(`This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}'" to listen to ${market} topics`);
|
|
751
|
-
}
|
|
752
|
-
/** @deprecated use "market: 'spotv3" client */
|
|
753
|
-
subscribePublicSpotTrades(symbol, binary) {
|
|
754
|
-
if (this.options.market !== 'spot') {
|
|
755
|
-
throw this.wrongMarketError('spot');
|
|
756
|
-
}
|
|
757
|
-
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
|
|
758
|
-
topic: 'trade',
|
|
759
|
-
event: 'sub',
|
|
760
|
-
symbol,
|
|
761
|
-
params: {
|
|
762
|
-
binary: !!binary,
|
|
763
|
-
},
|
|
764
|
-
}));
|
|
765
|
-
}
|
|
766
|
-
/** @deprecated use "market: 'spotv3" client */
|
|
767
|
-
subscribePublicSpotTradingPair(symbol, binary) {
|
|
768
|
-
if (this.options.market !== 'spot') {
|
|
769
|
-
throw this.wrongMarketError('spot');
|
|
770
|
-
}
|
|
771
|
-
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
|
|
772
|
-
symbol,
|
|
773
|
-
topic: 'realtimes',
|
|
774
|
-
event: 'sub',
|
|
775
|
-
params: {
|
|
776
|
-
binary: !!binary,
|
|
777
|
-
},
|
|
778
|
-
}));
|
|
779
|
-
}
|
|
780
|
-
/** @deprecated use "market: 'spotv3" client */
|
|
781
|
-
subscribePublicSpotV1Kline(symbol, candleSize, binary) {
|
|
782
|
-
if (this.options.market !== 'spot') {
|
|
783
|
-
throw this.wrongMarketError('spot');
|
|
784
|
-
}
|
|
785
|
-
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
|
|
786
|
-
symbol,
|
|
787
|
-
topic: 'kline_' + candleSize,
|
|
788
|
-
event: 'sub',
|
|
789
|
-
params: {
|
|
790
|
-
binary: !!binary,
|
|
791
|
-
},
|
|
792
|
-
}));
|
|
793
|
-
}
|
|
794
|
-
//ws.send('{"symbol":"BTCUSDT","topic":"depth","event":"sub","params":{"binary":false}}');
|
|
795
|
-
//ws.send('{"symbol":"BTCUSDT","topic":"mergedDepth","event":"sub","params":{"binary":false,"dumpScale":1}}');
|
|
796
|
-
//ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}');
|
|
797
|
-
/** @deprecated use "market: 'spotv3" client */
|
|
798
|
-
subscribePublicSpotOrderbook(symbol, depth, dumpScale, binary) {
|
|
799
|
-
if (this.options.market !== 'spot') {
|
|
800
|
-
throw this.wrongMarketError('spot');
|
|
801
|
-
}
|
|
802
|
-
let topic;
|
|
803
|
-
switch (depth) {
|
|
804
|
-
case 'full': {
|
|
805
|
-
topic = 'depth';
|
|
806
|
-
break;
|
|
807
|
-
}
|
|
808
|
-
case 'merge': {
|
|
809
|
-
topic = 'mergedDepth';
|
|
810
|
-
if (!dumpScale) {
|
|
811
|
-
throw new Error('Dumpscale must be provided for merged orderbooks');
|
|
460
|
+
// WS API Success
|
|
461
|
+
try {
|
|
462
|
+
this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, Object.assign({ wsKey }, parsed), true);
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
this.logger.error('Exception trying to resolve WSAPI promise', {
|
|
466
|
+
wsKey,
|
|
467
|
+
promiseRef,
|
|
468
|
+
parsedEvent: parsed,
|
|
469
|
+
});
|
|
812
470
|
}
|
|
813
|
-
|
|
471
|
+
results.push({
|
|
472
|
+
eventType: 'response',
|
|
473
|
+
event: parsed,
|
|
474
|
+
isWSAPIResponse: true,
|
|
475
|
+
});
|
|
476
|
+
return results;
|
|
477
|
+
}
|
|
478
|
+
// Messages for a subscribed topic all include the "topic" property
|
|
479
|
+
if (typeof eventTopic === 'string') {
|
|
480
|
+
results.push({
|
|
481
|
+
eventType: 'update',
|
|
482
|
+
event: parsed,
|
|
483
|
+
});
|
|
484
|
+
return results;
|
|
485
|
+
}
|
|
486
|
+
// Messages that are a "reply" to a request/command (e.g. subscribe to these topics) typically include the "op" property
|
|
487
|
+
if (typeof eventOperation === 'string') {
|
|
488
|
+
// Failed request
|
|
489
|
+
if (parsed.success === false) {
|
|
490
|
+
results.push({
|
|
491
|
+
eventType: 'error',
|
|
492
|
+
event: parsed,
|
|
493
|
+
});
|
|
494
|
+
return results;
|
|
495
|
+
}
|
|
496
|
+
// These are r equest/reply pattern events (e.g. after subscribing to topics or authenticating)
|
|
497
|
+
if (EVENTS_RESPONSES.includes(eventOperation)) {
|
|
498
|
+
results.push({
|
|
499
|
+
eventType: 'response',
|
|
500
|
+
event: parsed,
|
|
501
|
+
});
|
|
502
|
+
return results;
|
|
503
|
+
}
|
|
504
|
+
// Request/reply pattern for authentication success
|
|
505
|
+
if (EVENTS_AUTHENTICATED.includes(eventOperation)) {
|
|
506
|
+
results.push({
|
|
507
|
+
eventType: 'authenticated',
|
|
508
|
+
event: parsed,
|
|
509
|
+
});
|
|
510
|
+
return results;
|
|
511
|
+
}
|
|
512
|
+
this.logger.error(`!! Unhandled string operation type "${eventOperation}". Defaulting to "update" channel...`, parsed);
|
|
814
513
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
break;
|
|
514
|
+
else {
|
|
515
|
+
this.logger.error(`!! Unhandled non-string event type "${eventOperation}". Defaulting to "update" channel...`, parsed);
|
|
818
516
|
}
|
|
517
|
+
// In case of catastrophic failure, fallback to noisy emit update
|
|
518
|
+
results.push({
|
|
519
|
+
eventType: 'update',
|
|
520
|
+
event: parsed,
|
|
521
|
+
});
|
|
819
522
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
523
|
+
catch (e) {
|
|
524
|
+
results.push({
|
|
525
|
+
event: {
|
|
526
|
+
message: 'Failed to parse event data due to exception',
|
|
527
|
+
exception: e,
|
|
528
|
+
eventData: event.data,
|
|
529
|
+
},
|
|
530
|
+
eventType: 'error',
|
|
531
|
+
});
|
|
532
|
+
this.logger.error('Failed to parse event data due to exception: ', {
|
|
533
|
+
exception: e,
|
|
534
|
+
eventData: event.data,
|
|
535
|
+
});
|
|
830
536
|
}
|
|
831
|
-
return
|
|
537
|
+
return results;
|
|
832
538
|
}
|
|
833
539
|
}
|
|
834
540
|
exports.WebsocketClient = WebsocketClient;
|