bybit-api 2.4.0-beta.1 → 2.4.0-beta.2
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 +16 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/types/shared.d.ts +3 -1
- package/lib/types/websockets.d.ts +6 -5
- package/lib/util/WsStore.d.ts +18 -15
- package/lib/util/WsStore.js +1 -0
- package/lib/util/WsStore.js.map +1 -1
- package/lib/util/node-support.d.ts +1 -0
- package/lib/util/node-support.js +1 -0
- package/lib/util/node-support.js.map +1 -1
- package/lib/util/requestUtils.d.ts +1 -1
- package/lib/util/requestUtils.js +12 -6
- package/lib/util/requestUtils.js.map +1 -1
- package/lib/util/websocket-util.d.ts +34 -8
- package/lib/util/websocket-util.js +187 -32
- package/lib/util/websocket-util.js.map +1 -1
- package/lib/websocket-client.d.ts +55 -29
- package/lib/websocket-client.js +293 -198
- package/lib/websocket-client.js.map +1 -1
- package/package.json +1 -1
package/lib/websocket-client.js
CHANGED
|
@@ -19,106 +19,59 @@ const inverse_client_1 = require("./inverse-client");
|
|
|
19
19
|
const linear_client_1 = require("./linear-client");
|
|
20
20
|
const spot_client_v3_1 = require("./spot-client-v3");
|
|
21
21
|
const spot_client_1 = require("./spot-client");
|
|
22
|
-
const logger_1 = require("./util/logger");
|
|
23
22
|
const node_support_1 = require("./util/node-support");
|
|
24
23
|
const WsStore_1 = __importDefault(require("./util/WsStore"));
|
|
25
24
|
const util_1 = require("./util");
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
testnet: 'wss://stream-testnet.bybit.com/realtime',
|
|
29
|
-
};
|
|
30
|
-
const linearEndpoints = {
|
|
31
|
-
private: {
|
|
32
|
-
livenet: 'wss://stream.bybit.com/realtime_private',
|
|
33
|
-
livenet2: 'wss://stream.bytick.com/realtime_private',
|
|
34
|
-
testnet: 'wss://stream-testnet.bybit.com/realtime_private',
|
|
35
|
-
},
|
|
36
|
-
public: {
|
|
37
|
-
livenet: 'wss://stream.bybit.com/realtime_public',
|
|
38
|
-
livenet2: 'wss://stream.bytick.com/realtime_public',
|
|
39
|
-
testnet: 'wss://stream-testnet.bybit.com/realtime_public',
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
const spotEndpoints = {
|
|
43
|
-
private: {
|
|
44
|
-
livenet: 'wss://stream.bybit.com/spot/ws',
|
|
45
|
-
testnet: 'wss://stream-testnet.bybit.com/spot/ws',
|
|
46
|
-
},
|
|
47
|
-
public: {
|
|
48
|
-
livenet: 'wss://stream.bybit.com/spot/quote/ws/v1',
|
|
49
|
-
livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2',
|
|
50
|
-
testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1',
|
|
51
|
-
testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2',
|
|
52
|
-
},
|
|
53
|
-
};
|
|
25
|
+
const usdc_option_client_1 = require("./usdc-option-client");
|
|
26
|
+
const usdc_perpetual_client_1 = require("./usdc-perpetual-client");
|
|
54
27
|
const loggerCategory = { category: 'bybit-ws' };
|
|
55
28
|
class WebsocketClient extends events_1.EventEmitter {
|
|
56
29
|
constructor(options, logger) {
|
|
57
30
|
super();
|
|
58
|
-
this.logger = logger ||
|
|
31
|
+
this.logger = logger || util_1.DefaultLogger;
|
|
59
32
|
this.wsStore = new WsStore_1.default(this.logger);
|
|
60
|
-
this.options = Object.assign({
|
|
61
|
-
|
|
62
|
-
this.restClient = new spot_client_v3_1.SpotClientV3(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
|
63
|
-
}
|
|
64
|
-
else if (this.isLinear()) {
|
|
65
|
-
this.restClient = new linear_client_1.LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
|
66
|
-
}
|
|
67
|
-
else if (this.isSpot()) {
|
|
68
|
-
this.restClient = new spot_client_1.SpotClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
|
69
|
-
this.connectPublic();
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
this.restClient = new inverse_client_1.InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
isLivenet() {
|
|
76
|
-
return this.options.livenet === true;
|
|
77
|
-
}
|
|
78
|
-
isLinear() {
|
|
79
|
-
return this.options.market === 'linear';
|
|
80
|
-
}
|
|
81
|
-
isSpot() {
|
|
82
|
-
return this.options.market === 'spot';
|
|
83
|
-
}
|
|
84
|
-
isInverse() {
|
|
85
|
-
return this.options.market === 'inverse';
|
|
86
|
-
}
|
|
87
|
-
/** USDC, spot v3, unified margin, account asset */
|
|
88
|
-
isV3() {
|
|
89
|
-
return this.options.market === 'v3';
|
|
33
|
+
this.options = Object.assign({ testnet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options);
|
|
34
|
+
this.prepareRESTClient();
|
|
90
35
|
}
|
|
91
36
|
/**
|
|
92
|
-
*
|
|
37
|
+
* Only used if we fetch exchange time before attempting auth.
|
|
38
|
+
* Disabled by default.
|
|
39
|
+
* I've removed this for ftx and it's working great, tempted to remove this here
|
|
93
40
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// if connected, send subscription request
|
|
100
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
101
|
-
return this.requestSubscribeTopics(wsKey, topics);
|
|
41
|
+
prepareRESTClient() {
|
|
42
|
+
switch (this.options.market) {
|
|
43
|
+
case 'inverse': {
|
|
44
|
+
this.restClient = new inverse_client_1.InverseClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
|
|
45
|
+
break;
|
|
102
46
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return this.connect(wsKey);
|
|
47
|
+
case 'linear': {
|
|
48
|
+
this.restClient = new linear_client_1.LinearClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
|
|
49
|
+
break;
|
|
107
50
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
*/
|
|
113
|
-
unsubscribe(wsTopics) {
|
|
114
|
-
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
115
|
-
topics.forEach((topic) => this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic));
|
|
116
|
-
this.wsStore.getKeys().forEach((wsKey) => {
|
|
117
|
-
// unsubscribe request only necessary if active connection exists
|
|
118
|
-
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
119
|
-
this.requestUnsubscribeTopics(wsKey, topics);
|
|
51
|
+
case 'spot': {
|
|
52
|
+
this.restClient = new spot_client_1.SpotClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
|
|
53
|
+
this.connectPublic();
|
|
54
|
+
break;
|
|
120
55
|
}
|
|
121
|
-
|
|
56
|
+
case 'spotv3': {
|
|
57
|
+
this.restClient = new spot_client_v3_1.SpotClientV3(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'usdcOption': {
|
|
61
|
+
this.restClient = new usdc_option_client_1.USDCOptionClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'usdcPerp': {
|
|
65
|
+
this.restClient = new usdc_perpetual_client_1.USDCPerpetualClient(undefined, undefined, !this.isTestnet(), this.options.restOptions, this.options.requestOptions);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
default: {
|
|
69
|
+
throw util_1.neverGuard(this.options.market, `prepareRESTClient(): Unhandled market`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
isTestnet() {
|
|
74
|
+
return this.options.testnet === true;
|
|
122
75
|
}
|
|
123
76
|
close(wsKey) {
|
|
124
77
|
var _a;
|
|
@@ -127,43 +80,82 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
127
80
|
this.clearTimers(wsKey);
|
|
128
81
|
(_a = this.getWs(wsKey)) === null || _a === void 0 ? void 0 : _a.close();
|
|
129
82
|
}
|
|
83
|
+
closeAll() {
|
|
84
|
+
const keys = this.wsStore.getKeys();
|
|
85
|
+
keys.forEach((key) => {
|
|
86
|
+
this.close(key);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
130
89
|
/**
|
|
131
90
|
* Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library
|
|
132
91
|
*/
|
|
133
92
|
connectAll() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
93
|
+
switch (this.options.market) {
|
|
94
|
+
case 'inverse': {
|
|
95
|
+
// only one for inverse
|
|
96
|
+
return [this.connectPublic()];
|
|
97
|
+
}
|
|
98
|
+
// these all have separate public & private ws endpoints
|
|
99
|
+
case 'linear':
|
|
100
|
+
case 'spot':
|
|
101
|
+
case 'spotv3':
|
|
102
|
+
case 'usdcOption':
|
|
103
|
+
case 'usdcPerp': {
|
|
104
|
+
return [this.connectPublic(), this.connectPrivate()];
|
|
105
|
+
}
|
|
106
|
+
default: {
|
|
107
|
+
throw util_1.neverGuard(this.options.market, `connectAll(): Unhandled market`);
|
|
108
|
+
}
|
|
145
109
|
}
|
|
146
110
|
}
|
|
147
111
|
connectPublic() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
112
|
+
switch (this.options.market) {
|
|
113
|
+
case 'inverse': {
|
|
114
|
+
return this.connect(util_1.WS_KEY_MAP.inverse);
|
|
115
|
+
}
|
|
116
|
+
case 'linear': {
|
|
117
|
+
return this.connect(util_1.WS_KEY_MAP.linearPublic);
|
|
118
|
+
}
|
|
119
|
+
case 'spot': {
|
|
120
|
+
return this.connect(util_1.WS_KEY_MAP.spotPublic);
|
|
121
|
+
}
|
|
122
|
+
case 'spotv3': {
|
|
123
|
+
return this.connect(util_1.WS_KEY_MAP.spotV3Public);
|
|
124
|
+
}
|
|
125
|
+
case 'usdcOption': {
|
|
126
|
+
return this.connect(util_1.WS_KEY_MAP.usdcOptionPublic);
|
|
127
|
+
}
|
|
128
|
+
case 'usdcPerp': {
|
|
129
|
+
return this.connect(util_1.WS_KEY_MAP.usdcPerpPublic);
|
|
130
|
+
}
|
|
131
|
+
default: {
|
|
132
|
+
throw util_1.neverGuard(this.options.market, `connectPublic(): Unhandled market`);
|
|
133
|
+
}
|
|
156
134
|
}
|
|
157
135
|
}
|
|
158
136
|
connectPrivate() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
137
|
+
switch (this.options.market) {
|
|
138
|
+
case 'inverse': {
|
|
139
|
+
return this.connect(util_1.WS_KEY_MAP.inverse);
|
|
140
|
+
}
|
|
141
|
+
case 'linear': {
|
|
142
|
+
return this.connect(util_1.WS_KEY_MAP.linearPrivate);
|
|
143
|
+
}
|
|
144
|
+
case 'spot': {
|
|
145
|
+
return this.connect(util_1.WS_KEY_MAP.spotPrivate);
|
|
146
|
+
}
|
|
147
|
+
case 'spotv3': {
|
|
148
|
+
return this.connect(util_1.WS_KEY_MAP.spotV3Private);
|
|
149
|
+
}
|
|
150
|
+
case 'usdcOption': {
|
|
151
|
+
return this.connect(util_1.WS_KEY_MAP.usdcOptionPrivate);
|
|
152
|
+
}
|
|
153
|
+
case 'usdcPerp': {
|
|
154
|
+
return this.connect(util_1.WS_KEY_MAP.usdcPerpPrivate);
|
|
155
|
+
}
|
|
156
|
+
default: {
|
|
157
|
+
throw util_1.neverGuard(this.options.market, `connectPrivate(): Unhandled market`);
|
|
158
|
+
}
|
|
167
159
|
}
|
|
168
160
|
}
|
|
169
161
|
connect(wsKey) {
|
|
@@ -195,6 +187,7 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
195
187
|
parseWsError(context, error, wsKey) {
|
|
196
188
|
if (!error.message) {
|
|
197
189
|
this.logger.error(`${context} due to unexpected error: `, error);
|
|
190
|
+
this.emit('errorEvent', error);
|
|
198
191
|
return;
|
|
199
192
|
}
|
|
200
193
|
switch (error.message) {
|
|
@@ -205,35 +198,66 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
205
198
|
this.logger.error(`{context} due to unexpected response error: ${error.msg}`, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
206
199
|
break;
|
|
207
200
|
}
|
|
201
|
+
this.emit('errorEvent', error);
|
|
208
202
|
}
|
|
209
203
|
/**
|
|
210
204
|
* Return params required to make authorized request
|
|
211
205
|
*/
|
|
212
206
|
getAuthParams(wsKey) {
|
|
213
207
|
return __awaiter(this, void 0, void 0, function* () {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
? yield this.restClient.fetchTimeOffset()
|
|
222
|
-
: 0;
|
|
223
|
-
const params = {
|
|
208
|
+
if (util_1.PUBLIC_WS_KEYS.includes(wsKey)) {
|
|
209
|
+
this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
210
|
+
return '';
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey);
|
|
214
|
+
const authParams = {
|
|
224
215
|
api_key: this.options.key,
|
|
225
|
-
expires:
|
|
216
|
+
expires: expiresAt,
|
|
217
|
+
signature,
|
|
226
218
|
};
|
|
227
|
-
|
|
228
|
-
return '?' + util_1.serializeParams(params);
|
|
219
|
+
return '?' + util_1.serializeParams(authParams);
|
|
229
220
|
}
|
|
230
|
-
|
|
231
|
-
this.logger.
|
|
221
|
+
catch (e) {
|
|
222
|
+
this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
223
|
+
return '';
|
|
232
224
|
}
|
|
233
|
-
|
|
234
|
-
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
sendAuthRequest(wsKey) {
|
|
228
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
229
|
+
try {
|
|
230
|
+
const { signature, expiresAt } = yield this.getWsAuthSignature(wsKey);
|
|
231
|
+
const request = {
|
|
232
|
+
op: 'auth',
|
|
233
|
+
args: [this.options.key, expiresAt, signature],
|
|
234
|
+
req_id: `${wsKey}-auth`,
|
|
235
|
+
};
|
|
236
|
+
return this.tryWsSend(wsKey, JSON.stringify(request));
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
235
240
|
}
|
|
236
|
-
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
getWsAuthSignature(wsKey) {
|
|
244
|
+
var _a;
|
|
245
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
246
|
+
const { key, secret } = this.options;
|
|
247
|
+
if (!key || !secret) {
|
|
248
|
+
this.logger.warning('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
249
|
+
throw new Error(`Cannot auth - missing api or secret in config`);
|
|
250
|
+
}
|
|
251
|
+
this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
252
|
+
const timeOffset = this.options.fetchTimeOffsetBeforeAuth
|
|
253
|
+
? (yield ((_a = this.restClient) === null || _a === void 0 ? void 0 : _a.fetchTimeOffset())) || 0
|
|
254
|
+
: 0;
|
|
255
|
+
const signatureExpiresAt = Date.now() + timeOffset + 5000;
|
|
256
|
+
const signature = yield node_support_1.signMessage('GET/realtime' + signatureExpiresAt, secret);
|
|
257
|
+
return {
|
|
258
|
+
expiresAt: signatureExpiresAt,
|
|
259
|
+
signature,
|
|
260
|
+
};
|
|
237
261
|
});
|
|
238
262
|
}
|
|
239
263
|
reconnectWithDelay(wsKey, connectionDelayMs) {
|
|
@@ -242,7 +266,7 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
242
266
|
util_1.WsConnectionStateEnum.CONNECTING) {
|
|
243
267
|
this.setWsState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING);
|
|
244
268
|
}
|
|
245
|
-
setTimeout(() => {
|
|
269
|
+
this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
|
|
246
270
|
this.logger.info('Reconnecting to websocket', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
247
271
|
this.connect(wsKey);
|
|
248
272
|
}, connectionDelayMs);
|
|
@@ -260,6 +284,10 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
260
284
|
clearTimers(wsKey) {
|
|
261
285
|
this.clearPingTimer(wsKey);
|
|
262
286
|
this.clearPongTimer(wsKey);
|
|
287
|
+
const wsState = this.wsStore.get(wsKey);
|
|
288
|
+
if (wsState === null || wsState === void 0 ? void 0 : wsState.activeReconnectTimer) {
|
|
289
|
+
clearTimeout(wsState.activeReconnectTimer);
|
|
290
|
+
}
|
|
263
291
|
}
|
|
264
292
|
// Send a ping at intervals
|
|
265
293
|
clearPingTimer(wsKey) {
|
|
@@ -285,6 +313,7 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
285
313
|
return;
|
|
286
314
|
}
|
|
287
315
|
const wsMessage = JSON.stringify({
|
|
316
|
+
req_id: topics.join(','),
|
|
288
317
|
op: 'subscribe',
|
|
289
318
|
args: topics,
|
|
290
319
|
});
|
|
@@ -333,33 +362,60 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
333
362
|
return ws;
|
|
334
363
|
}
|
|
335
364
|
onWsOpen(event, wsKey) {
|
|
336
|
-
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
this.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
365
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
366
|
+
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) {
|
|
367
|
+
this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, testnet: this.isTestnet(), market: this.options.market }));
|
|
368
|
+
this.emit('open', { wsKey, event });
|
|
369
|
+
}
|
|
370
|
+
else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
|
|
371
|
+
this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
372
|
+
this.emit('reconnected', { wsKey, event });
|
|
373
|
+
}
|
|
374
|
+
this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED);
|
|
375
|
+
// Some websockets require an auth packet to be sent after opening the connection
|
|
376
|
+
if (util_1.WS_AUTH_ON_CONNECT_KEYS.includes(wsKey)) {
|
|
377
|
+
this.logger.info(`Sending auth request...`);
|
|
378
|
+
yield this.sendAuthRequest(wsKey);
|
|
379
|
+
}
|
|
380
|
+
// TODO: persistence not working yet for spot v1 topics
|
|
381
|
+
if (wsKey !== util_1.WS_KEY_MAP.spotPublic && wsKey !== util_1.WS_KEY_MAP.spotPrivate) {
|
|
382
|
+
this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]);
|
|
383
|
+
}
|
|
384
|
+
this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval);
|
|
385
|
+
});
|
|
350
386
|
}
|
|
351
387
|
onWsMessage(event, wsKey) {
|
|
352
388
|
try {
|
|
353
389
|
// any message can clear the pong timer - wouldn't get a message if the ws dropped
|
|
354
390
|
this.clearPongTimer(wsKey);
|
|
355
391
|
const msg = JSON.parse((event && event.data) || event);
|
|
356
|
-
|
|
357
|
-
|
|
392
|
+
this.logger.silly('Received event', Object.assign(Object.assign({}, this.logger), { wsKey, msg: JSON.stringify(msg, null, 2) }));
|
|
393
|
+
// TODO: cleanme
|
|
394
|
+
if (msg['success'] || (msg === null || msg === void 0 ? void 0 : msg.pong) || util_1.isWsPong(msg)) {
|
|
395
|
+
if (util_1.isWsPong(msg)) {
|
|
396
|
+
this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
this.emit('response', msg);
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
358
402
|
}
|
|
359
|
-
if (msg
|
|
403
|
+
if (msg['finalFragment']) {
|
|
404
|
+
return this.emit('response', msg);
|
|
405
|
+
}
|
|
406
|
+
if (msg === null || msg === void 0 ? void 0 : msg.topic) {
|
|
360
407
|
return this.emit('update', msg);
|
|
361
408
|
}
|
|
362
|
-
|
|
409
|
+
if (
|
|
410
|
+
// spot v1
|
|
411
|
+
(msg === null || msg === void 0 ? void 0 : msg.code) ||
|
|
412
|
+
// spot v3
|
|
413
|
+
(msg === null || msg === void 0 ? void 0 : msg.type) === 'error' ||
|
|
414
|
+
// usdc options
|
|
415
|
+
(msg === null || msg === void 0 ? void 0 : msg.success) === false) {
|
|
416
|
+
return this.emit('errorEvent', msg);
|
|
417
|
+
}
|
|
418
|
+
this.logger.warning('Unhandled/unrecognised ws event message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
|
|
363
419
|
wsKey }));
|
|
364
420
|
}
|
|
365
421
|
catch (e) {
|
|
@@ -370,26 +426,18 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
370
426
|
onWsError(error, wsKey) {
|
|
371
427
|
this.parseWsError('Websocket error', error, wsKey);
|
|
372
428
|
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
373
|
-
this.emit('
|
|
429
|
+
this.emit('errorEvent', error);
|
|
374
430
|
}
|
|
375
431
|
}
|
|
376
432
|
onWsClose(event, wsKey) {
|
|
377
433
|
this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
378
434
|
if (this.wsStore.getConnectionState(wsKey) !== util_1.WsConnectionStateEnum.CLOSING) {
|
|
379
435
|
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
|
380
|
-
this.emit('reconnect', { wsKey });
|
|
436
|
+
this.emit('reconnect', { wsKey, event });
|
|
381
437
|
}
|
|
382
438
|
else {
|
|
383
439
|
this.setWsState(wsKey, util_1.WsConnectionStateEnum.INITIAL);
|
|
384
|
-
this.emit('close', { wsKey });
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
onWsMessageResponse(response, wsKey) {
|
|
388
|
-
if (util_1.isWsPong(response)) {
|
|
389
|
-
this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
this.emit('response', response);
|
|
440
|
+
this.emit('close', { wsKey, event });
|
|
393
441
|
}
|
|
394
442
|
}
|
|
395
443
|
getWs(wsKey) {
|
|
@@ -402,49 +450,93 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
402
450
|
if (this.options.wsUrl) {
|
|
403
451
|
return this.options.wsUrl;
|
|
404
452
|
}
|
|
405
|
-
const networkKey = this.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return linearEndpoints.public[networkKey];
|
|
453
|
+
const networkKey = this.isTestnet() ? 'testnet' : 'livenet';
|
|
454
|
+
switch (wsKey) {
|
|
455
|
+
case util_1.WS_KEY_MAP.linearPublic: {
|
|
456
|
+
return util_1.WS_BASE_URL_MAP.linear.public[networkKey];
|
|
410
457
|
}
|
|
411
|
-
|
|
412
|
-
return
|
|
458
|
+
case util_1.WS_KEY_MAP.linearPrivate: {
|
|
459
|
+
return util_1.WS_BASE_URL_MAP.linear.private[networkKey];
|
|
413
460
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
if (this.isSpot() || wsKey.startsWith('spot')) {
|
|
418
|
-
if (wsKey === util_1.wsKeySpotPublic) {
|
|
419
|
-
return spotEndpoints.public[networkKey];
|
|
461
|
+
case util_1.WS_KEY_MAP.spotPublic: {
|
|
462
|
+
return util_1.WS_BASE_URL_MAP.spot.public[networkKey];
|
|
420
463
|
}
|
|
421
|
-
|
|
422
|
-
return
|
|
464
|
+
case util_1.WS_KEY_MAP.spotPrivate: {
|
|
465
|
+
return util_1.WS_BASE_URL_MAP.spot.private[networkKey];
|
|
466
|
+
}
|
|
467
|
+
case util_1.WS_KEY_MAP.spotV3Public: {
|
|
468
|
+
return util_1.WS_BASE_URL_MAP.spotv3.public[networkKey];
|
|
469
|
+
}
|
|
470
|
+
case util_1.WS_KEY_MAP.spotV3Private: {
|
|
471
|
+
return util_1.WS_BASE_URL_MAP.spotv3.private[networkKey];
|
|
472
|
+
}
|
|
473
|
+
case util_1.WS_KEY_MAP.inverse: {
|
|
474
|
+
// private and public are on the same WS connection
|
|
475
|
+
return util_1.WS_BASE_URL_MAP.inverse.public[networkKey];
|
|
476
|
+
}
|
|
477
|
+
case util_1.WS_KEY_MAP.usdcOptionPublic: {
|
|
478
|
+
return util_1.WS_BASE_URL_MAP.usdcOption.public[networkKey];
|
|
479
|
+
}
|
|
480
|
+
case util_1.WS_KEY_MAP.usdcOptionPrivate: {
|
|
481
|
+
return util_1.WS_BASE_URL_MAP.usdcOption.private[networkKey];
|
|
482
|
+
}
|
|
483
|
+
case util_1.WS_KEY_MAP.usdcPerpPublic: {
|
|
484
|
+
return util_1.WS_BASE_URL_MAP.usdcPerp.public[networkKey];
|
|
485
|
+
}
|
|
486
|
+
case util_1.WS_KEY_MAP.usdcPerpPrivate: {
|
|
487
|
+
return util_1.WS_BASE_URL_MAP.usdcPerp.private[networkKey];
|
|
488
|
+
}
|
|
489
|
+
default: {
|
|
490
|
+
this.logger.error('getWsUrl(): Unhandled wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
491
|
+
throw util_1.neverGuard(wsKey, `getWsUrl(): Unhandled wsKey`);
|
|
423
492
|
}
|
|
424
|
-
this.logger.error('Unhandled spot wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
|
|
425
|
-
return spotEndpoints[networkKey];
|
|
426
|
-
}
|
|
427
|
-
// fallback to inverse
|
|
428
|
-
return inverseEndpoints[networkKey];
|
|
429
|
-
}
|
|
430
|
-
getWsKeyForTopic(topic) {
|
|
431
|
-
if (this.isInverse()) {
|
|
432
|
-
return util_1.wsKeyInverse;
|
|
433
|
-
}
|
|
434
|
-
if (this.isLinear()) {
|
|
435
|
-
return util_1.getLinearWsKeyForTopic(topic);
|
|
436
493
|
}
|
|
437
|
-
return util_1.getSpotWsKeyForTopic(topic);
|
|
438
494
|
}
|
|
439
495
|
wrongMarketError(market) {
|
|
440
496
|
return new Error(`This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}' to listen to spot topics`);
|
|
441
497
|
}
|
|
442
|
-
|
|
498
|
+
/**
|
|
499
|
+
* Add topic/topics to WS subscription list
|
|
500
|
+
* @param wsTopics topic or list of topics
|
|
501
|
+
* @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)
|
|
502
|
+
*/
|
|
503
|
+
subscribe(wsTopics, isPrivateTopic) {
|
|
504
|
+
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
505
|
+
topics.forEach((topic) => this.wsStore.addTopic(util_1.getWsKeyForTopic(this.options.market, topic, isPrivateTopic), topic));
|
|
506
|
+
// attempt to send subscription topic per websocket
|
|
507
|
+
this.wsStore.getKeys().forEach((wsKey) => {
|
|
508
|
+
// if connected, send subscription request
|
|
509
|
+
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
510
|
+
return this.requestSubscribeTopics(wsKey, topics);
|
|
511
|
+
}
|
|
512
|
+
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
|
|
513
|
+
if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) &&
|
|
514
|
+
!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
|
|
515
|
+
return this.connect(wsKey);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Remove topic/topics from WS subscription list
|
|
521
|
+
* @param wsTopics topic or list of topics
|
|
522
|
+
* @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)
|
|
523
|
+
*/
|
|
524
|
+
unsubscribe(wsTopics, isPrivateTopic) {
|
|
525
|
+
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
|
|
526
|
+
topics.forEach((topic) => this.wsStore.deleteTopic(util_1.getWsKeyForTopic(this.options.market, topic, isPrivateTopic), topic));
|
|
527
|
+
this.wsStore.getKeys().forEach((wsKey) => {
|
|
528
|
+
// unsubscribe request only necessary if active connection exists
|
|
529
|
+
if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
|
|
530
|
+
this.requestUnsubscribeTopics(wsKey, topics);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/** @deprecated use "market: 'spotv3" client */
|
|
443
535
|
subscribePublicSpotTrades(symbol, binary) {
|
|
444
|
-
if (
|
|
536
|
+
if (this.options.market !== 'spot') {
|
|
445
537
|
throw this.wrongMarketError('spot');
|
|
446
538
|
}
|
|
447
|
-
return this.tryWsSend(util_1.
|
|
539
|
+
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
|
|
448
540
|
topic: 'trade',
|
|
449
541
|
event: 'sub',
|
|
450
542
|
symbol,
|
|
@@ -453,11 +545,12 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
453
545
|
},
|
|
454
546
|
}));
|
|
455
547
|
}
|
|
548
|
+
/** @deprecated use "market: 'spotv3" client */
|
|
456
549
|
subscribePublicSpotTradingPair(symbol, binary) {
|
|
457
|
-
if (
|
|
550
|
+
if (this.options.market !== 'spot') {
|
|
458
551
|
throw this.wrongMarketError('spot');
|
|
459
552
|
}
|
|
460
|
-
return this.tryWsSend(util_1.
|
|
553
|
+
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
|
|
461
554
|
symbol,
|
|
462
555
|
topic: 'realtimes',
|
|
463
556
|
event: 'sub',
|
|
@@ -466,11 +559,12 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
466
559
|
},
|
|
467
560
|
}));
|
|
468
561
|
}
|
|
562
|
+
/** @deprecated use "market: 'spotv3" client */
|
|
469
563
|
subscribePublicSpotV1Kline(symbol, candleSize, binary) {
|
|
470
|
-
if (
|
|
564
|
+
if (this.options.market !== 'spot') {
|
|
471
565
|
throw this.wrongMarketError('spot');
|
|
472
566
|
}
|
|
473
|
-
return this.tryWsSend(util_1.
|
|
567
|
+
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
|
|
474
568
|
symbol,
|
|
475
569
|
topic: 'kline_' + candleSize,
|
|
476
570
|
event: 'sub',
|
|
@@ -482,8 +576,9 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
482
576
|
//ws.send('{"symbol":"BTCUSDT","topic":"depth","event":"sub","params":{"binary":false}}');
|
|
483
577
|
//ws.send('{"symbol":"BTCUSDT","topic":"mergedDepth","event":"sub","params":{"binary":false,"dumpScale":1}}');
|
|
484
578
|
//ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}');
|
|
579
|
+
/** @deprecated use "market: 'spotv3" client */
|
|
485
580
|
subscribePublicSpotOrderbook(symbol, depth, dumpScale, binary) {
|
|
486
|
-
if (
|
|
581
|
+
if (this.options.market !== 'spot') {
|
|
487
582
|
throw this.wrongMarketError('spot');
|
|
488
583
|
}
|
|
489
584
|
let topic;
|
|
@@ -515,7 +610,7 @@ class WebsocketClient extends events_1.EventEmitter {
|
|
|
515
610
|
if (dumpScale) {
|
|
516
611
|
msg.params.dumpScale = dumpScale;
|
|
517
612
|
}
|
|
518
|
-
return this.tryWsSend(util_1.
|
|
613
|
+
return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify(msg));
|
|
519
614
|
}
|
|
520
615
|
}
|
|
521
616
|
exports.WebsocketClient = WebsocketClient;
|