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.
Files changed (45) hide show
  1. package/README.md +73 -41
  2. package/lib/index.d.ts +1 -1
  3. package/lib/index.js +1 -1
  4. package/lib/index.js.map +1 -1
  5. package/lib/inverse-client.d.ts +18 -126
  6. package/lib/inverse-client.js.map +1 -1
  7. package/lib/linear-client.d.ts +20 -142
  8. package/lib/linear-client.js.map +1 -1
  9. package/lib/spot-client-v3.d.ts +2 -6
  10. package/lib/spot-client-v3.js.map +1 -1
  11. package/lib/spot-client.js +1 -1
  12. package/lib/spot-client.js.map +1 -1
  13. package/lib/types/request/index.d.ts +2 -1
  14. package/lib/types/request/index.js +2 -1
  15. package/lib/types/request/index.js.map +1 -1
  16. package/lib/types/request/inverse.d.ts +120 -0
  17. package/lib/types/request/{usdt-perp.js → inverse.js} +1 -1
  18. package/lib/types/request/inverse.js.map +1 -0
  19. package/lib/types/request/linear.d.ts +160 -0
  20. package/lib/types/request/linear.js +3 -0
  21. package/lib/types/request/linear.js.map +1 -0
  22. package/lib/types/request/spot.d.ts +5 -0
  23. package/lib/types/shared.d.ts +4 -1
  24. package/lib/types/websockets.d.ts +6 -5
  25. package/lib/util/BaseRestClient.d.ts +4 -8
  26. package/lib/util/BaseRestClient.js +16 -23
  27. package/lib/util/BaseRestClient.js.map +1 -1
  28. package/lib/util/WsStore.d.ts +19 -16
  29. package/lib/util/WsStore.js +1 -0
  30. package/lib/util/WsStore.js.map +1 -1
  31. package/lib/util/node-support.d.ts +1 -0
  32. package/lib/util/node-support.js +1 -0
  33. package/lib/util/node-support.js.map +1 -1
  34. package/lib/util/requestUtils.d.ts +9 -5
  35. package/lib/util/requestUtils.js +21 -12
  36. package/lib/util/requestUtils.js.map +1 -1
  37. package/lib/util/websocket-util.d.ts +38 -8
  38. package/lib/util/websocket-util.js +252 -32
  39. package/lib/util/websocket-util.js.map +1 -1
  40. package/lib/websocket-client.d.ts +63 -30
  41. package/lib/websocket-client.js +343 -206
  42. package/lib/websocket-client.js.map +1 -1
  43. package/package.json +2 -2
  44. package/lib/types/request/usdt-perp.d.ts +0 -20
  45. package/lib/types/request/usdt-perp.js.map +0 -1
@@ -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 inverseEndpoints = {
27
- livenet: 'wss://stream.bybit.com/realtime',
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");
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 || logger_1.DefaultLogger;
32
+ this.logger = logger || util_1.DefaultLogger;
59
33
  this.wsStore = new WsStore_1.default(this.logger);
60
- this.options = Object.assign({ livenet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options);
61
- if (this.isV3()) {
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';
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
- * Add topic/topics to WS subscription list
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
- subscribe(wsTopics) {
95
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
96
- topics.forEach((topic) => this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic));
97
- // attempt to send subscription topic per websocket
98
- this.wsStore.getKeys().forEach((wsKey) => {
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
- // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
104
- if (!this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING) &&
105
- !this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
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
- * Remove topic/topics from WS subscription list
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
- close(wsKey) {
124
- var _a;
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
- (_a = this.getWs(wsKey)) === null || _a === void 0 ? void 0 : _a.close();
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
- if (this.isInverse()) {
135
- return [this.connect(util_1.wsKeyInverse)];
136
- }
137
- if (this.isLinear()) {
138
- return [
139
- this.connect(util_1.wsKeyLinearPublic),
140
- this.connect(util_1.wsKeyLinearPrivate),
141
- ];
142
- }
143
- if (this.isSpot()) {
144
- return [this.connect(util_1.wsKeySpotPublic), this.connect(util_1.wsKeySpotPrivate)];
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
- if (this.isInverse()) {
149
- return this.connect(util_1.wsKeyInverse);
150
- }
151
- if (this.isLinear()) {
152
- return this.connect(util_1.wsKeyLinearPublic);
153
- }
154
- if (this.isSpot()) {
155
- return this.connect(util_1.wsKeySpotPublic);
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
- if (this.isInverse()) {
160
- return this.connect(util_1.wsKeyInverse);
161
- }
162
- if (this.isLinear()) {
163
- return this.connect(util_1.wsKeyLinearPrivate);
164
- }
165
- if (this.isSpot()) {
166
- return this.connect(util_1.wsKeySpotPrivate);
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(`{context} due to unexpected response error: ${error.msg}`, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
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
- const { key, secret } = this.options;
215
- if (key &&
216
- secret &&
217
- wsKey !== util_1.wsKeyLinearPublic &&
218
- wsKey !== util_1.wsKeySpotPublic) {
219
- this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey }));
220
- const timeOffset = this.options.fetchTimeOffsetBeforeAuth
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: Date.now() + timeOffset + 5000,
241
+ expires: expiresAt,
242
+ signature,
226
243
  };
227
- params.signature = yield node_support_1.signMessage('GET/realtime' + params.expires, secret);
228
- return '?' + util_1.serializeParams(params);
244
+ return '?' + util_1.serializeParams(authParams);
229
245
  }
230
- else if (!key || !secret) {
231
- this.logger.warning('Cannot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
246
+ catch (e) {
247
+ this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
248
+ return '';
232
249
  }
233
- else {
234
- this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
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
- return '';
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.close();
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
- if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) {
337
- this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, livenet: this.isLivenet(), linear: this.isLinear(), spot: this.isSpot() }));
338
- this.emit('open', { wsKey, event });
339
- }
340
- else if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING)) {
341
- this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
342
- this.emit('reconnected', { wsKey, event });
343
- }
344
- this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTED);
345
- // TODO: persistence not working yet for spot topics
346
- if (wsKey !== 'spotPublic' && wsKey !== 'spotPrivate') {
347
- this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]);
348
- }
349
- this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval);
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
- if (msg['success'] || (msg === null || msg === void 0 ? void 0 : msg.pong)) {
357
- return this.onWsMessageResponse(msg, wsKey);
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.topic) {
360
- return this.emit('update', msg);
432
+ if (msg['finalFragment']) {
433
+ return this.emit('response', Object.assign(Object.assign({}, msg), { wsKey }));
361
434
  }
362
- this.logger.warning('Got unhandled ws message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
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.isLivenet() ? 'livenet' : 'testnet';
406
- // TODO: repetitive
407
- if (this.isLinear() || wsKey.startsWith('linear')) {
408
- if (wsKey === util_1.wsKeyLinearPublic) {
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
- if (wsKey === util_1.wsKeyLinearPrivate) {
412
- return linearEndpoints.private[networkKey];
484
+ case util_1.WS_KEY_MAP.linearPrivate: {
485
+ return util_1.WS_BASE_URL_MAP.linear.private[networkKey];
413
486
  }
414
- this.logger.error('Unhandled linear wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
415
- return linearEndpoints[networkKey];
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
- if (wsKey === util_1.wsKeySpotPrivate) {
422
- return spotEndpoints.private[networkKey];
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
- // TODO: persistance for subbed topics. Look at ftx-api implementation.
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 (!this.isSpot()) {
578
+ if (this.options.market !== 'spot') {
445
579
  throw this.wrongMarketError('spot');
446
580
  }
447
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({
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 (!this.isSpot()) {
592
+ if (this.options.market !== 'spot') {
458
593
  throw this.wrongMarketError('spot');
459
594
  }
460
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({
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 (!this.isSpot()) {
606
+ if (this.options.market !== 'spot') {
471
607
  throw this.wrongMarketError('spot');
472
608
  }
473
- return this.tryWsSend(util_1.wsKeySpotPublic, JSON.stringify({
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 (!this.isSpot()) {
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.wsKeySpotPublic, JSON.stringify(msg));
655
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify(msg));
519
656
  }
520
657
  }
521
658
  exports.WebsocketClient = WebsocketClient;