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