bybit-api 2.3.2 → 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.
Files changed (95) hide show
  1. package/README.md +74 -153
  2. package/lib/account-asset-client.d.ts +42 -0
  3. package/lib/account-asset-client.js +104 -0
  4. package/lib/account-asset-client.js.map +1 -0
  5. package/lib/constants/enum.d.ts +18 -0
  6. package/lib/constants/enum.js +18 -0
  7. package/lib/constants/enum.js.map +1 -1
  8. package/lib/copy-trading-client.d.ts +54 -0
  9. package/lib/copy-trading-client.js +112 -0
  10. package/lib/copy-trading-client.js.map +1 -0
  11. package/lib/index.d.ts +8 -2
  12. package/lib/index.js +8 -2
  13. package/lib/index.js.map +1 -1
  14. package/lib/inverse-client.d.ts +5 -13
  15. package/lib/inverse-client.js +6 -13
  16. package/lib/inverse-client.js.map +1 -1
  17. package/lib/inverse-futures-client.d.ts +4 -12
  18. package/lib/inverse-futures-client.js +5 -12
  19. package/lib/inverse-futures-client.js.map +1 -1
  20. package/lib/linear-client.d.ts +4 -12
  21. package/lib/linear-client.js +5 -12
  22. package/lib/linear-client.js.map +1 -1
  23. package/lib/spot-client-v3.d.ts +105 -0
  24. package/lib/spot-client-v3.js +222 -0
  25. package/lib/spot-client-v3.js.map +1 -0
  26. package/lib/spot-client.d.ts +5 -12
  27. package/lib/spot-client.js +6 -12
  28. package/lib/spot-client.js.map +1 -1
  29. package/lib/types/index.d.ts +1 -1
  30. package/lib/types/index.js +1 -1
  31. package/lib/types/index.js.map +1 -1
  32. package/lib/types/request/account-asset.d.ts +76 -0
  33. package/lib/types/request/account-asset.js +3 -0
  34. package/lib/types/request/account-asset.js.map +1 -0
  35. package/lib/types/request/copy-trading.d.ts +47 -0
  36. package/lib/types/request/copy-trading.js +3 -0
  37. package/lib/types/request/copy-trading.js.map +1 -0
  38. package/lib/types/request/index.d.ts +7 -0
  39. package/lib/types/request/index.js +7 -0
  40. package/lib/types/request/index.js.map +1 -1
  41. package/lib/types/request/spot.d.ts +72 -0
  42. package/lib/types/{spot.js → request/spot.js} +0 -0
  43. package/lib/types/{spot.js.map → request/spot.js.map} +1 -1
  44. package/lib/types/request/unified-margin.d.ts +223 -0
  45. package/lib/types/request/unified-margin.js +3 -0
  46. package/lib/types/request/unified-margin.js.map +1 -0
  47. package/lib/types/request/usdc-options.d.ts +119 -0
  48. package/lib/types/request/usdc-options.js +3 -0
  49. package/lib/types/request/usdc-options.js.map +1 -0
  50. package/lib/types/request/usdc-perp.d.ts +86 -0
  51. package/lib/types/request/usdc-perp.js +3 -0
  52. package/lib/types/request/usdc-perp.js.map +1 -0
  53. package/lib/types/request/usdc-shared.d.ts +29 -0
  54. package/lib/types/request/usdc-shared.js +3 -0
  55. package/lib/types/request/usdc-shared.js.map +1 -0
  56. package/lib/types/shared.d.ts +18 -5
  57. package/lib/types/websockets.d.ts +41 -0
  58. package/lib/types/websockets.js +3 -0
  59. package/lib/types/websockets.js.map +1 -0
  60. package/lib/unified-margin-client.d.ts +137 -0
  61. package/lib/unified-margin-client.js +269 -0
  62. package/lib/unified-margin-client.js.map +1 -0
  63. package/lib/usdc-option-client.d.ts +100 -0
  64. package/lib/usdc-option-client.js +186 -0
  65. package/lib/usdc-option-client.js.map +1 -0
  66. package/lib/usdc-perpetual-client.d.ts +88 -0
  67. package/lib/usdc-perpetual-client.js +181 -0
  68. package/lib/usdc-perpetual-client.js.map +1 -0
  69. package/lib/util/BaseRestClient.d.ts +15 -2
  70. package/lib/util/BaseRestClient.js +93 -29
  71. package/lib/util/BaseRestClient.js.map +1 -1
  72. package/lib/util/WsStore.d.ts +40 -17
  73. package/lib/util/WsStore.js +12 -3
  74. package/lib/util/WsStore.js.map +1 -1
  75. package/lib/util/index.d.ts +5 -0
  76. package/lib/util/index.js +18 -0
  77. package/lib/util/index.js.map +1 -0
  78. package/lib/{logger.d.ts → util/logger.d.ts} +0 -0
  79. package/lib/{logger.js → util/logger.js} +0 -0
  80. package/lib/util/logger.js.map +1 -0
  81. package/lib/util/node-support.d.ts +1 -0
  82. package/lib/util/node-support.js +1 -0
  83. package/lib/util/node-support.js.map +1 -1
  84. package/lib/util/requestUtils.d.ts +6 -2
  85. package/lib/util/requestUtils.js +20 -25
  86. package/lib/util/requestUtils.js.map +1 -1
  87. package/lib/util/websocket-util.d.ts +34 -0
  88. package/lib/util/websocket-util.js +194 -0
  89. package/lib/util/websocket-util.js.map +1 -0
  90. package/lib/websocket-client.d.ts +56 -82
  91. package/lib/websocket-client.js +308 -264
  92. package/lib/websocket-client.js.map +1 -1
  93. package/package.json +1 -1
  94. package/lib/logger.js.map +0 -1
  95. package/lib/types/spot.d.ts +0 -30
@@ -12,205 +12,150 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.WebsocketClient = exports.wsKeySpotPublic = exports.wsKeySpotPrivate = exports.wsKeyLinearPublic = exports.wsKeyLinearPrivate = exports.wsKeyInverse = exports.WsConnectionState = void 0;
15
+ exports.WebsocketClient = void 0;
16
16
  const events_1 = require("events");
17
17
  const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
18
18
  const inverse_client_1 = require("./inverse-client");
19
19
  const linear_client_1 = require("./linear-client");
20
- const logger_1 = require("./logger");
20
+ const spot_client_v3_1 = require("./spot-client-v3");
21
21
  const spot_client_1 = require("./spot-client");
22
22
  const node_support_1 = require("./util/node-support");
23
- const requestUtils_1 = require("./util/requestUtils");
24
23
  const WsStore_1 = __importDefault(require("./util/WsStore"));
25
- const inverseEndpoints = {
26
- livenet: 'wss://stream.bybit.com/realtime',
27
- testnet: 'wss://stream-testnet.bybit.com/realtime',
28
- };
29
- const linearEndpoints = {
30
- private: {
31
- livenet: 'wss://stream.bybit.com/realtime_private',
32
- livenet2: 'wss://stream.bytick.com/realtime_private',
33
- testnet: 'wss://stream-testnet.bybit.com/realtime_private',
34
- },
35
- public: {
36
- livenet: 'wss://stream.bybit.com/realtime_public',
37
- livenet2: 'wss://stream.bytick.com/realtime_public',
38
- testnet: 'wss://stream-testnet.bybit.com/realtime_public',
39
- },
40
- };
41
- const spotEndpoints = {
42
- private: {
43
- livenet: 'wss://stream.bybit.com/spot/ws',
44
- testnet: 'wss://stream-testnet.bybit.com/spot/ws',
45
- },
46
- public: {
47
- livenet: 'wss://stream.bybit.com/spot/quote/ws/v1',
48
- livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2',
49
- testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1',
50
- testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2',
51
- },
52
- };
24
+ const util_1 = require("./util");
25
+ const usdc_option_client_1 = require("./usdc-option-client");
26
+ const usdc_perpetual_client_1 = require("./usdc-perpetual-client");
53
27
  const loggerCategory = { category: 'bybit-ws' };
54
- const READY_STATE_INITIAL = 0;
55
- const READY_STATE_CONNECTING = 1;
56
- const READY_STATE_CONNECTED = 2;
57
- const READY_STATE_CLOSING = 3;
58
- const READY_STATE_RECONNECTING = 4;
59
- var WsConnectionState;
60
- (function (WsConnectionState) {
61
- WsConnectionState[WsConnectionState["READY_STATE_INITIAL"] = 0] = "READY_STATE_INITIAL";
62
- WsConnectionState[WsConnectionState["READY_STATE_CONNECTING"] = 1] = "READY_STATE_CONNECTING";
63
- WsConnectionState[WsConnectionState["READY_STATE_CONNECTED"] = 2] = "READY_STATE_CONNECTED";
64
- WsConnectionState[WsConnectionState["READY_STATE_CLOSING"] = 3] = "READY_STATE_CLOSING";
65
- WsConnectionState[WsConnectionState["READY_STATE_RECONNECTING"] = 4] = "READY_STATE_RECONNECTING";
66
- })(WsConnectionState = exports.WsConnectionState || (exports.WsConnectionState = {}));
67
- exports.wsKeyInverse = 'inverse';
68
- exports.wsKeyLinearPrivate = 'linearPrivate';
69
- exports.wsKeyLinearPublic = 'linearPublic';
70
- exports.wsKeySpotPrivate = 'spotPrivate';
71
- exports.wsKeySpotPublic = 'spotPublic';
72
- const getLinearWsKeyForTopic = (topic) => {
73
- const privateLinearTopics = [
74
- 'position',
75
- 'execution',
76
- 'order',
77
- 'stop_order',
78
- 'wallet',
79
- ];
80
- if (privateLinearTopics.includes(topic)) {
81
- return exports.wsKeyLinearPrivate;
82
- }
83
- return exports.wsKeyLinearPublic;
84
- };
85
- const getSpotWsKeyForTopic = (topic) => {
86
- const privateLinearTopics = [
87
- 'position',
88
- 'execution',
89
- 'order',
90
- 'stop_order',
91
- 'outboundAccountInfo',
92
- 'executionReport',
93
- 'ticketInfo',
94
- ];
95
- if (privateLinearTopics.includes(topic)) {
96
- return exports.wsKeySpotPrivate;
97
- }
98
- return exports.wsKeySpotPublic;
99
- };
100
- function resolveMarket(options) {
101
- if (options.linear) {
102
- return 'linear';
103
- }
104
- return 'inverse';
105
- }
106
28
  class WebsocketClient extends events_1.EventEmitter {
107
29
  constructor(options, logger) {
108
30
  super();
109
- this.logger = logger || logger_1.DefaultLogger;
31
+ this.logger = logger || util_1.DefaultLogger;
110
32
  this.wsStore = new WsStore_1.default(this.logger);
111
- this.options = Object.assign({ livenet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options);
112
- if (!this.options.market) {
113
- this.options.market = resolveMarket(this.options);
114
- }
115
- if (this.isLinear()) {
116
- this.restClient = new linear_client_1.LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
117
- }
118
- else if (this.isSpot()) {
119
- this.restClient = new spot_client_1.SpotClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
120
- this.connectPublic();
121
- }
122
- else {
123
- this.restClient = new inverse_client_1.InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
124
- }
125
- }
126
- isLivenet() {
127
- return this.options.livenet === true;
128
- }
129
- isLinear() {
130
- return this.options.market === 'linear';
131
- }
132
- isSpot() {
133
- return this.options.market === 'spot';
134
- }
135
- isInverse() {
136
- return !this.isLinear() && !this.isSpot();
33
+ this.options = Object.assign({ testnet: false, pongTimeout: 1000, pingInterval: 10000, reconnectTimeout: 500, fetchTimeOffsetBeforeAuth: false }, options);
34
+ this.prepareRESTClient();
137
35
  }
138
36
  /**
139
- * Add topic/topics to WS subscription list
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
140
40
  */
141
- subscribe(wsTopics) {
142
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
143
- topics.forEach((topic) => this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic));
144
- // attempt to send subscription topic per websocket
145
- this.wsStore.getKeys().forEach((wsKey) => {
146
- // if connected, send subscription request
147
- if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTED)) {
148
- 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;
149
46
  }
150
- // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
151
- if (!this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTING) &&
152
- !this.wsStore.isConnectionState(wsKey, READY_STATE_RECONNECTING)) {
153
- 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;
154
50
  }
155
- });
156
- }
157
- /**
158
- * Remove topic/topics from WS subscription list
159
- */
160
- unsubscribe(wsTopics) {
161
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
162
- topics.forEach((topic) => this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic));
163
- this.wsStore.getKeys().forEach((wsKey) => {
164
- // unsubscribe request only necessary if active connection exists
165
- if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTED)) {
166
- 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;
167
55
  }
168
- });
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;
169
75
  }
170
76
  close(wsKey) {
171
77
  var _a;
172
78
  this.logger.info('Closing connection', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
173
- this.setWsState(wsKey, READY_STATE_CLOSING);
79
+ this.setWsState(wsKey, util_1.WsConnectionStateEnum.CLOSING);
174
80
  this.clearTimers(wsKey);
175
81
  (_a = this.getWs(wsKey)) === null || _a === void 0 ? void 0 : _a.close();
176
82
  }
83
+ closeAll() {
84
+ const keys = this.wsStore.getKeys();
85
+ keys.forEach((key) => {
86
+ this.close(key);
87
+ });
88
+ }
177
89
  /**
178
- * Request connection of all dependent websockets, instead of waiting for automatic connection by library
90
+ * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library
179
91
  */
180
92
  connectAll() {
181
- if (this.isInverse()) {
182
- return [this.connect(exports.wsKeyInverse)];
183
- }
184
- if (this.isLinear()) {
185
- return [
186
- this.connect(exports.wsKeyLinearPublic),
187
- this.connect(exports.wsKeyLinearPrivate),
188
- ];
189
- }
190
- if (this.isSpot()) {
191
- return [this.connect(exports.wsKeySpotPublic), this.connect(exports.wsKeySpotPrivate)];
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
+ }
192
109
  }
193
110
  }
194
111
  connectPublic() {
195
- if (this.isInverse()) {
196
- return this.connect(exports.wsKeyInverse);
197
- }
198
- if (this.isLinear()) {
199
- return this.connect(exports.wsKeyLinearPublic);
200
- }
201
- if (this.isSpot()) {
202
- return this.connect(exports.wsKeySpotPublic);
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
+ }
203
134
  }
204
135
  }
205
136
  connectPrivate() {
206
- if (this.isInverse()) {
207
- return this.connect(exports.wsKeyInverse);
208
- }
209
- if (this.isLinear()) {
210
- return this.connect(exports.wsKeyLinearPrivate);
211
- }
212
- if (this.isSpot()) {
213
- return this.connect(exports.wsKeySpotPrivate);
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
+ }
214
159
  }
215
160
  }
216
161
  connect(wsKey) {
@@ -220,13 +165,13 @@ class WebsocketClient extends events_1.EventEmitter {
220
165
  this.logger.error('Refused to connect to ws with existing active connection', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
221
166
  return this.wsStore.getWs(wsKey);
222
167
  }
223
- if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTING)) {
168
+ if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTING)) {
224
169
  this.logger.error('Refused to connect to ws, connection attempt already active', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
225
170
  return;
226
171
  }
227
172
  if (!this.wsStore.getConnectionState(wsKey) ||
228
- this.wsStore.isConnectionState(wsKey, READY_STATE_INITIAL)) {
229
- this.setWsState(wsKey, READY_STATE_CONNECTING);
173
+ this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.INITIAL)) {
174
+ this.setWsState(wsKey, util_1.WsConnectionStateEnum.CONNECTING);
230
175
  }
231
176
  const authParams = yield this.getAuthParams(wsKey);
232
177
  const url = this.getWsUrl(wsKey) + authParams;
@@ -242,6 +187,7 @@ class WebsocketClient extends events_1.EventEmitter {
242
187
  parseWsError(context, error, wsKey) {
243
188
  if (!error.message) {
244
189
  this.logger.error(`${context} due to unexpected error: `, error);
190
+ this.emit('errorEvent', error);
245
191
  return;
246
192
  }
247
193
  switch (error.message) {
@@ -252,43 +198,75 @@ class WebsocketClient extends events_1.EventEmitter {
252
198
  this.logger.error(`{context} due to unexpected response error: ${error.msg}`, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
253
199
  break;
254
200
  }
201
+ this.emit('errorEvent', error);
255
202
  }
256
203
  /**
257
204
  * Return params required to make authorized request
258
205
  */
259
206
  getAuthParams(wsKey) {
260
207
  return __awaiter(this, void 0, void 0, function* () {
261
- const { key, secret } = this.options;
262
- if (key &&
263
- secret &&
264
- wsKey !== exports.wsKeyLinearPublic &&
265
- wsKey !== exports.wsKeySpotPublic) {
266
- this.logger.debug("Getting auth'd request params", Object.assign(Object.assign({}, loggerCategory), { wsKey }));
267
- const timeOffset = this.options.fetchTimeOffsetBeforeAuth
268
- ? yield this.restClient.fetchTimeOffset()
269
- : 0;
270
- 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 = {
271
215
  api_key: this.options.key,
272
- expires: Date.now() + timeOffset + 5000,
216
+ expires: expiresAt,
217
+ signature,
273
218
  };
274
- params.signature = yield node_support_1.signMessage('GET/realtime' + params.expires, secret);
275
- return '?' + requestUtils_1.serializeParams(params);
219
+ return '?' + util_1.serializeParams(authParams);
276
220
  }
277
- else if (!key || !secret) {
278
- this.logger.warning('Connot authenticate websocket, either api or private keys missing.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
221
+ catch (e) {
222
+ this.logger.error(e, Object.assign(Object.assign({}, loggerCategory), { wsKey }));
223
+ return '';
279
224
  }
280
- else {
281
- this.logger.debug('Starting public only websocket client.', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
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 }));
282
240
  }
283
- return '';
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
+ };
284
261
  });
285
262
  }
286
263
  reconnectWithDelay(wsKey, connectionDelayMs) {
287
264
  this.clearTimers(wsKey);
288
- if (this.wsStore.getConnectionState(wsKey) !== READY_STATE_CONNECTING) {
289
- this.setWsState(wsKey, READY_STATE_RECONNECTING);
265
+ if (this.wsStore.getConnectionState(wsKey) !==
266
+ util_1.WsConnectionStateEnum.CONNECTING) {
267
+ this.setWsState(wsKey, util_1.WsConnectionStateEnum.RECONNECTING);
290
268
  }
291
- setTimeout(() => {
269
+ this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
292
270
  this.logger.info('Reconnecting to websocket', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
293
271
  this.connect(wsKey);
294
272
  }, connectionDelayMs);
@@ -306,6 +284,10 @@ class WebsocketClient extends events_1.EventEmitter {
306
284
  clearTimers(wsKey) {
307
285
  this.clearPingTimer(wsKey);
308
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
+ }
309
291
  }
310
292
  // Send a ping at intervals
311
293
  clearPingTimer(wsKey) {
@@ -331,6 +313,7 @@ class WebsocketClient extends events_1.EventEmitter {
331
313
  return;
332
314
  }
333
315
  const wsMessage = JSON.stringify({
316
+ req_id: topics.join(','),
334
317
  op: 'subscribe',
335
318
  args: topics,
336
319
  });
@@ -379,36 +362,61 @@ class WebsocketClient extends events_1.EventEmitter {
379
362
  return ws;
380
363
  }
381
364
  onWsOpen(event, wsKey) {
382
- if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTING)) {
383
- this.logger.info('Websocket connected', Object.assign(Object.assign({}, loggerCategory), { wsKey, livenet: this.isLivenet(), linear: this.isLinear(), spot: this.isSpot() }));
384
- this.emit('open', { wsKey, event });
385
- }
386
- else if (this.wsStore.isConnectionState(wsKey, READY_STATE_RECONNECTING)) {
387
- this.logger.info('Websocket reconnected', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
388
- this.emit('reconnected', { wsKey, event });
389
- }
390
- this.setWsState(wsKey, READY_STATE_CONNECTED);
391
- // TODO: persistence not working yet for spot topics
392
- if (wsKey !== 'spotPublic' && wsKey !== 'spotPrivate') {
393
- this.requestSubscribeTopics(wsKey, [...this.wsStore.getTopics(wsKey)]);
394
- }
395
- this.wsStore.get(wsKey, true).activePingTimer = setInterval(() => this.ping(wsKey), this.options.pingInterval);
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
+ });
396
386
  }
397
387
  onWsMessage(event, wsKey) {
398
388
  try {
399
389
  // any message can clear the pong timer - wouldn't get a message if the ws dropped
400
390
  this.clearPongTimer(wsKey);
401
391
  const msg = JSON.parse((event && event.data) || event);
402
- if ('success' in msg || (msg === null || msg === void 0 ? void 0 : msg.pong)) {
403
- this.onWsMessageResponse(msg, wsKey);
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;
402
+ }
403
+ if (msg['finalFragment']) {
404
+ return this.emit('response', msg);
404
405
  }
405
- else if (msg.topic) {
406
- this.onWsMessageUpdate(msg);
406
+ if (msg === null || msg === void 0 ? void 0 : msg.topic) {
407
+ return this.emit('update', msg);
407
408
  }
408
- else {
409
- this.logger.warning('Got unhandled ws message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
410
- wsKey }));
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);
411
417
  }
418
+ this.logger.warning('Unhandled/unrecognised ws event message', Object.assign(Object.assign({}, loggerCategory), { message: msg, event,
419
+ wsKey }));
412
420
  }
413
421
  catch (e) {
414
422
  this.logger.error('Failed to parse ws event message', Object.assign(Object.assign({}, loggerCategory), { error: e, event,
@@ -417,32 +425,21 @@ class WebsocketClient extends events_1.EventEmitter {
417
425
  }
418
426
  onWsError(error, wsKey) {
419
427
  this.parseWsError('Websocket error', error, wsKey);
420
- if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTED)) {
421
- this.emit('error', error);
428
+ if (this.wsStore.isConnectionState(wsKey, util_1.WsConnectionStateEnum.CONNECTED)) {
429
+ this.emit('errorEvent', error);
422
430
  }
423
431
  }
424
432
  onWsClose(event, wsKey) {
425
433
  this.logger.info('Websocket connection closed', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
426
- if (this.wsStore.getConnectionState(wsKey) !== READY_STATE_CLOSING) {
434
+ if (this.wsStore.getConnectionState(wsKey) !== util_1.WsConnectionStateEnum.CLOSING) {
427
435
  this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
428
- this.emit('reconnect', { wsKey });
436
+ this.emit('reconnect', { wsKey, event });
429
437
  }
430
438
  else {
431
- this.setWsState(wsKey, READY_STATE_INITIAL);
432
- this.emit('close', { wsKey });
439
+ this.setWsState(wsKey, util_1.WsConnectionStateEnum.INITIAL);
440
+ this.emit('close', { wsKey, event });
433
441
  }
434
442
  }
435
- onWsMessageResponse(response, wsKey) {
436
- if (requestUtils_1.isWsPong(response)) {
437
- this.logger.silly('Received pong', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
438
- }
439
- else {
440
- this.emit('response', response);
441
- }
442
- }
443
- onWsMessageUpdate(message) {
444
- this.emit('update', message);
445
- }
446
443
  getWs(wsKey) {
447
444
  return this.wsStore.getWs(wsKey);
448
445
  }
@@ -453,49 +450,93 @@ class WebsocketClient extends events_1.EventEmitter {
453
450
  if (this.options.wsUrl) {
454
451
  return this.options.wsUrl;
455
452
  }
456
- const networkKey = this.isLivenet() ? 'livenet' : 'testnet';
457
- // TODO: reptitive
458
- if (this.isLinear() || wsKey.startsWith('linear')) {
459
- if (wsKey === exports.wsKeyLinearPublic) {
460
- 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];
461
457
  }
462
- if (wsKey === exports.wsKeyLinearPrivate) {
463
- return linearEndpoints.private[networkKey];
458
+ case util_1.WS_KEY_MAP.linearPrivate: {
459
+ return util_1.WS_BASE_URL_MAP.linear.private[networkKey];
464
460
  }
465
- this.logger.error('Unhandled linear wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
466
- return linearEndpoints[networkKey];
467
- }
468
- if (this.isSpot() || wsKey.startsWith('spot')) {
469
- if (wsKey === exports.wsKeySpotPublic) {
470
- return spotEndpoints.public[networkKey];
461
+ case util_1.WS_KEY_MAP.spotPublic: {
462
+ return util_1.WS_BASE_URL_MAP.spot.public[networkKey];
471
463
  }
472
- if (wsKey === exports.wsKeySpotPrivate) {
473
- return spotEndpoints.private[networkKey];
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`);
474
492
  }
475
- this.logger.error('Unhandled spot wsKey: ', Object.assign(Object.assign({}, loggerCategory), { wsKey }));
476
- return spotEndpoints[networkKey];
477
- }
478
- // fallback to inverse
479
- return inverseEndpoints[networkKey];
480
- }
481
- getWsKeyForTopic(topic) {
482
- if (this.isInverse()) {
483
- return exports.wsKeyInverse;
484
- }
485
- if (this.isLinear()) {
486
- return getLinearWsKeyForTopic(topic);
487
493
  }
488
- return getSpotWsKeyForTopic(topic);
489
494
  }
490
495
  wrongMarketError(market) {
491
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`);
492
497
  }
493
- // TODO: persistance for subbed topics. Look at ftx-api implementation.
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 */
494
535
  subscribePublicSpotTrades(symbol, binary) {
495
- if (!this.isSpot()) {
536
+ if (this.options.market !== 'spot') {
496
537
  throw this.wrongMarketError('spot');
497
538
  }
498
- return this.tryWsSend(exports.wsKeySpotPublic, JSON.stringify({
539
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
499
540
  topic: 'trade',
500
541
  event: 'sub',
501
542
  symbol,
@@ -504,11 +545,12 @@ class WebsocketClient extends events_1.EventEmitter {
504
545
  },
505
546
  }));
506
547
  }
548
+ /** @deprecated use "market: 'spotv3" client */
507
549
  subscribePublicSpotTradingPair(symbol, binary) {
508
- if (!this.isSpot()) {
550
+ if (this.options.market !== 'spot') {
509
551
  throw this.wrongMarketError('spot');
510
552
  }
511
- return this.tryWsSend(exports.wsKeySpotPublic, JSON.stringify({
553
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
512
554
  symbol,
513
555
  topic: 'realtimes',
514
556
  event: 'sub',
@@ -517,11 +559,12 @@ class WebsocketClient extends events_1.EventEmitter {
517
559
  },
518
560
  }));
519
561
  }
562
+ /** @deprecated use "market: 'spotv3" client */
520
563
  subscribePublicSpotV1Kline(symbol, candleSize, binary) {
521
- if (!this.isSpot()) {
564
+ if (this.options.market !== 'spot') {
522
565
  throw this.wrongMarketError('spot');
523
566
  }
524
- return this.tryWsSend(exports.wsKeySpotPublic, JSON.stringify({
567
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify({
525
568
  symbol,
526
569
  topic: 'kline_' + candleSize,
527
570
  event: 'sub',
@@ -533,8 +576,9 @@ class WebsocketClient extends events_1.EventEmitter {
533
576
  //ws.send('{"symbol":"BTCUSDT","topic":"depth","event":"sub","params":{"binary":false}}');
534
577
  //ws.send('{"symbol":"BTCUSDT","topic":"mergedDepth","event":"sub","params":{"binary":false,"dumpScale":1}}');
535
578
  //ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}');
579
+ /** @deprecated use "market: 'spotv3" client */
536
580
  subscribePublicSpotOrderbook(symbol, depth, dumpScale, binary) {
537
- if (!this.isSpot()) {
581
+ if (this.options.market !== 'spot') {
538
582
  throw this.wrongMarketError('spot');
539
583
  }
540
584
  let topic;
@@ -566,7 +610,7 @@ class WebsocketClient extends events_1.EventEmitter {
566
610
  if (dumpScale) {
567
611
  msg.params.dumpScale = dumpScale;
568
612
  }
569
- return this.tryWsSend(exports.wsKeySpotPublic, JSON.stringify(msg));
613
+ return this.tryWsSend(util_1.WS_KEY_MAP.spotPublic, JSON.stringify(msg));
570
614
  }
571
615
  }
572
616
  exports.WebsocketClient = WebsocketClient;