bybit-api 3.7.1-beta.0 → 3.7.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 +34 -6
- package/lib/rest-client-v5.d.ts +3 -3
- package/lib/rest-client-v5.js.map +1 -1
- package/lib/types/request/v5-trade.d.ts +2 -2
- package/package.json +1 -13
- package/src/account-asset-client-v3.ts +0 -310
- package/src/account-asset-client.ts +0 -149
- package/src/constants/enum.ts +0 -105
- package/src/contract-client.ts +0 -360
- package/src/copy-trading-client.ts +0 -159
- package/src/index.ts +0 -18
- package/src/inverse-client.ts +0 -337
- package/src/inverse-futures-client.ts +0 -403
- package/src/linear-client.ts +0 -387
- package/src/rest-client-v5.ts +0 -1638
- package/src/spot-client-v3.ts +0 -303
- package/src/spot-client.ts +0 -180
- package/src/types/index.ts +0 -5
- package/src/types/request/account-asset.ts +0 -258
- package/src/types/request/contract.ts +0 -139
- package/src/types/request/copy-trading.ts +0 -53
- package/src/types/request/index.ts +0 -18
- package/src/types/request/inverse.ts +0 -135
- package/src/types/request/linear.ts +0 -201
- package/src/types/request/spot.ts +0 -89
- package/src/types/request/unified-margin.ts +0 -247
- package/src/types/request/usdc-options.ts +0 -134
- package/src/types/request/usdc-perp.ts +0 -100
- package/src/types/request/usdc-shared.ts +0 -39
- package/src/types/request/v5-account.ts +0 -40
- package/src/types/request/v5-asset.ts +0 -133
- package/src/types/request/v5-market.ts +0 -127
- package/src/types/request/v5-position.ts +0 -107
- package/src/types/request/v5-pre-upgrade.ts +0 -37
- package/src/types/request/v5-spot-leverage-token.ts +0 -23
- package/src/types/request/v5-trade.ts +0 -133
- package/src/types/request/v5-user.ts +0 -21
- package/src/types/response/account-asset.ts +0 -236
- package/src/types/response/contract.ts +0 -74
- package/src/types/response/index.ts +0 -13
- package/src/types/response/shared.ts +0 -19
- package/src/types/response/spot.ts +0 -17
- package/src/types/response/unified-margin.ts +0 -71
- package/src/types/response/usdt-perp.ts +0 -58
- package/src/types/response/v5-account.ts +0 -122
- package/src/types/response/v5-asset.ts +0 -170
- package/src/types/response/v5-market.ts +0 -271
- package/src/types/response/v5-position.ts +0 -118
- package/src/types/response/v5-spot-leverage-token.ts +0 -70
- package/src/types/response/v5-trade.ts +0 -103
- package/src/types/response/v5-user.ts +0 -58
- package/src/types/shared.ts +0 -175
- package/src/types/v5-shared.ts +0 -213
- package/src/types/websockets.ts +0 -115
- package/src/types/ws-events/failed-topic-subscription-confirmation.ts +0 -6
- package/src/types/ws-events/succeeded-topic-subscription-confirmation.ts +0 -6
- package/src/types/ws-events/topic-subscription-confirmation.ts +0 -7
- package/src/unified-margin-client.ts +0 -400
- package/src/usdc-option-client.ts +0 -333
- package/src/usdc-perpetual-client.ts +0 -312
- package/src/util/BaseRestClient.ts +0 -502
- package/src/util/WsStore.ts +0 -160
- package/src/util/browser-support.ts +0 -29
- package/src/util/index.ts +0 -5
- package/src/util/logger.ts +0 -26
- package/src/util/node-support.ts +0 -9
- package/src/util/requestUtils.ts +0 -171
- package/src/util/websocket-util.ts +0 -550
- package/src/websocket-client.ts +0 -1262
|
@@ -1,502 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
APIID,
|
|
6
|
-
REST_CLIENT_TYPE_ENUM,
|
|
7
|
-
RestClientOptions,
|
|
8
|
-
RestClientType,
|
|
9
|
-
getRestBaseUrl,
|
|
10
|
-
serializeParams,
|
|
11
|
-
} from './requestUtils';
|
|
12
|
-
import { signMessage } from './node-support';
|
|
13
|
-
|
|
14
|
-
const ENABLE_HTTP_TRACE =
|
|
15
|
-
typeof process === 'object' &&
|
|
16
|
-
typeof process.env === 'object' &&
|
|
17
|
-
process.env.BYBITTRACE;
|
|
18
|
-
|
|
19
|
-
if (ENABLE_HTTP_TRACE) {
|
|
20
|
-
// axios.interceptors.request.use((request) => {
|
|
21
|
-
// console.log(
|
|
22
|
-
// new Date(),
|
|
23
|
-
// 'Starting Request',
|
|
24
|
-
// JSON.stringify(request, null, 2)
|
|
25
|
-
// );
|
|
26
|
-
// return request;
|
|
27
|
-
// });
|
|
28
|
-
axios.interceptors.response.use((response) => {
|
|
29
|
-
console.log(new Date(), 'Response:', {
|
|
30
|
-
request: {
|
|
31
|
-
url: response.config.url,
|
|
32
|
-
method: response.config.method,
|
|
33
|
-
data: response.config.data,
|
|
34
|
-
headers: response.config.headers,
|
|
35
|
-
params: response.config.params,
|
|
36
|
-
},
|
|
37
|
-
response: {
|
|
38
|
-
status: response.status,
|
|
39
|
-
statusText: response.statusText,
|
|
40
|
-
headers: response.headers,
|
|
41
|
-
data: response.data,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
return response;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface SignedRequestContext {
|
|
49
|
-
timestamp?: number;
|
|
50
|
-
api_key?: string;
|
|
51
|
-
recv_window?: number;
|
|
52
|
-
// spot v1 is diff from the rest...
|
|
53
|
-
recvWindow?: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface SignedRequest<T> {
|
|
57
|
-
originalParams: (T & SignedRequestContext) | SignedRequestContext;
|
|
58
|
-
paramsWithSign?: T & SignedRequestContext & { sign: string };
|
|
59
|
-
serializedParams: string;
|
|
60
|
-
sign: string;
|
|
61
|
-
timestamp: number;
|
|
62
|
-
recvWindow: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface UnsignedRequest<T> {
|
|
66
|
-
originalParams: T;
|
|
67
|
-
paramsWithSign: T;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
type SignMethod = 'v2auth' | 'v5auth';
|
|
71
|
-
|
|
72
|
-
export default abstract class BaseRestClient {
|
|
73
|
-
private timeOffset: number | null = null;
|
|
74
|
-
|
|
75
|
-
private syncTimePromise: null | Promise<any> = null;
|
|
76
|
-
|
|
77
|
-
private options: RestClientOptions;
|
|
78
|
-
|
|
79
|
-
private baseUrl: string;
|
|
80
|
-
|
|
81
|
-
private globalRequestOptions: AxiosRequestConfig;
|
|
82
|
-
|
|
83
|
-
private key: string | undefined;
|
|
84
|
-
|
|
85
|
-
private secret: string | undefined;
|
|
86
|
-
|
|
87
|
-
private clientType: RestClientType;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Function that calls exchange API to query & resolve server time, used by time sync, disabled by default.
|
|
91
|
-
*
|
|
92
|
-
* Note: this should always return server time in seconds
|
|
93
|
-
*/
|
|
94
|
-
abstract fetchServerTime(): Promise<number>;
|
|
95
|
-
|
|
96
|
-
/** Defines the client type (affecting how requests & signatures behave) */
|
|
97
|
-
abstract getClientType(): RestClientType;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create an instance of the REST client. Pass API credentials in the object in the first parameter.
|
|
101
|
-
* @param {RestClientOptions} [restClientOptions={}] options to configure REST API connectivity
|
|
102
|
-
* @param {AxiosRequestConfig} [networkOptions={}] HTTP networking options for axios
|
|
103
|
-
*/
|
|
104
|
-
constructor(
|
|
105
|
-
restOptions: RestClientOptions = {},
|
|
106
|
-
networkOptions: AxiosRequestConfig = {},
|
|
107
|
-
) {
|
|
108
|
-
this.clientType = this.getClientType();
|
|
109
|
-
|
|
110
|
-
this.options = {
|
|
111
|
-
recv_window: 5000,
|
|
112
|
-
/** Throw errors if any request params are empty */
|
|
113
|
-
strict_param_validation: false,
|
|
114
|
-
/** Disable time sync by default */
|
|
115
|
-
enable_time_sync: false,
|
|
116
|
-
/** How often to sync time drift with bybit servers (if time sync is enabled) */
|
|
117
|
-
sync_interval_ms: 3600000,
|
|
118
|
-
/** Determines whether to perform time synchronization before sending private requests */
|
|
119
|
-
syncTimeBeforePrivateRequests: false,
|
|
120
|
-
/** Request parameter values are now URI encoded by default during signing. Set to false to override this behaviour. */
|
|
121
|
-
encodeSerialisedValues: true,
|
|
122
|
-
...restOptions,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
this.globalRequestOptions = {
|
|
126
|
-
// in ms == 5 minutes by default
|
|
127
|
-
timeout: 1000 * 60 * 5,
|
|
128
|
-
// custom request options based on axios specs - see: https://github.com/axios/axios#request-config
|
|
129
|
-
...networkOptions,
|
|
130
|
-
headers: {
|
|
131
|
-
'x-referer': APIID,
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
this.baseUrl = getRestBaseUrl(!!this.options.testnet, restOptions);
|
|
136
|
-
this.key = this.options.key;
|
|
137
|
-
this.secret = this.options.secret;
|
|
138
|
-
|
|
139
|
-
if (this.key && !this.secret) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
'API Key & Secret are both required for private endpoints',
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (this.options.enable_time_sync) {
|
|
146
|
-
this.syncTime();
|
|
147
|
-
setInterval(this.syncTime.bind(this), +this.options.sync_interval_ms!);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private isSpotV1Client() {
|
|
152
|
-
return this.clientType === REST_CLIENT_TYPE_ENUM.spot;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
get(endpoint: string, params?: any) {
|
|
156
|
-
return this._call('GET', endpoint, params, true);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
getPrivate(endpoint: string, params?: any) {
|
|
160
|
-
return this._call('GET', endpoint, params, false);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
post(endpoint: string, params?: any) {
|
|
164
|
-
return this._call('POST', endpoint, params, true);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
postPrivate(endpoint: string, params?: any) {
|
|
168
|
-
return this._call('POST', endpoint, params, false);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
deletePrivate(endpoint: string, params?: any) {
|
|
172
|
-
return this._call('DELETE', endpoint, params, false);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private async prepareSignParams<TParams = any>(
|
|
176
|
-
method: Method,
|
|
177
|
-
signMethod: SignMethod,
|
|
178
|
-
params?: TParams,
|
|
179
|
-
isPublicApi?: true,
|
|
180
|
-
): Promise<UnsignedRequest<TParams>>;
|
|
181
|
-
|
|
182
|
-
private async prepareSignParams<TParams = any>(
|
|
183
|
-
method: Method,
|
|
184
|
-
signMethod: SignMethod,
|
|
185
|
-
params?: TParams,
|
|
186
|
-
isPublicApi?: false | undefined,
|
|
187
|
-
): Promise<SignedRequest<TParams>>;
|
|
188
|
-
|
|
189
|
-
private async prepareSignParams<TParams extends SignedRequestContext = any>(
|
|
190
|
-
method: Method,
|
|
191
|
-
signMethod: SignMethod,
|
|
192
|
-
params?: TParams,
|
|
193
|
-
isPublicApi?: boolean,
|
|
194
|
-
) {
|
|
195
|
-
if (isPublicApi) {
|
|
196
|
-
return {
|
|
197
|
-
originalParams: params,
|
|
198
|
-
paramsWithSign: params,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!this.key || !this.secret) {
|
|
203
|
-
throw new Error('Private endpoints require api and private keys set');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (this.timeOffset === null) {
|
|
207
|
-
await this.syncTime();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (this.options.syncTimeBeforePrivateRequests) {
|
|
211
|
-
this.timeOffset = await this.fetchTimeOffset();
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return this.signRequest(params || {}, method, signMethod);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Returns an axios request object. Handles signing process automatically if this is a private API call */
|
|
218
|
-
private async buildRequest(
|
|
219
|
-
method: Method,
|
|
220
|
-
url: string,
|
|
221
|
-
params?: any,
|
|
222
|
-
isPublicApi?: boolean,
|
|
223
|
-
): Promise<AxiosRequestConfig> {
|
|
224
|
-
const options: AxiosRequestConfig = {
|
|
225
|
-
...this.globalRequestOptions,
|
|
226
|
-
url: url,
|
|
227
|
-
method: method,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
for (const key in params) {
|
|
231
|
-
if (typeof params[key] === 'undefined') {
|
|
232
|
-
delete params[key];
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (isPublicApi) {
|
|
237
|
-
return {
|
|
238
|
-
...options,
|
|
239
|
-
params: params,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// USDC endpoints, unified margin and a few others use a different way of authenticating requests (headers instead of params)
|
|
244
|
-
if (this.clientType === REST_CLIENT_TYPE_ENUM.v3) {
|
|
245
|
-
const signResult = await this.prepareSignParams(
|
|
246
|
-
method,
|
|
247
|
-
'v5auth',
|
|
248
|
-
params,
|
|
249
|
-
isPublicApi,
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
const headers = {
|
|
253
|
-
'X-BAPI-SIGN-TYPE': 2,
|
|
254
|
-
'X-BAPI-API-KEY': this.key,
|
|
255
|
-
'X-BAPI-TIMESTAMP': signResult.timestamp,
|
|
256
|
-
'X-BAPI-SIGN': signResult.sign,
|
|
257
|
-
'X-BAPI-RECV-WINDOW': signResult.recvWindow,
|
|
258
|
-
...options.headers,
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
if (method === 'GET') {
|
|
262
|
-
return {
|
|
263
|
-
...options,
|
|
264
|
-
headers,
|
|
265
|
-
params: signResult.originalParams,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
...options,
|
|
271
|
-
headers,
|
|
272
|
-
data: signResult.originalParams,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const signResult = await this.prepareSignParams(
|
|
277
|
-
method,
|
|
278
|
-
'v2auth',
|
|
279
|
-
params,
|
|
280
|
-
isPublicApi,
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
if (method === 'GET' || this.isSpotV1Client()) {
|
|
284
|
-
return {
|
|
285
|
-
...options,
|
|
286
|
-
params: signResult.paramsWithSign,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
...options,
|
|
292
|
-
data: signResult.paramsWithSign,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* @private Make a HTTP request to a specific endpoint. Private endpoints are automatically signed.
|
|
298
|
-
*/
|
|
299
|
-
private async _call(
|
|
300
|
-
method: Method,
|
|
301
|
-
endpoint: string,
|
|
302
|
-
params?: any,
|
|
303
|
-
isPublicApi?: boolean,
|
|
304
|
-
): Promise<any> {
|
|
305
|
-
// Sanity check to make sure it's only ever prefixed by one forward slash
|
|
306
|
-
const requestUrl = [this.baseUrl, endpoint].join(
|
|
307
|
-
endpoint.startsWith('/') ? '' : '/',
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// Build a request and handle signature process
|
|
311
|
-
const options = await this.buildRequest(
|
|
312
|
-
method,
|
|
313
|
-
requestUrl,
|
|
314
|
-
params,
|
|
315
|
-
isPublicApi,
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
if (ENABLE_HTTP_TRACE) {
|
|
319
|
-
console.log('full request: ', options);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Dispatch request
|
|
323
|
-
return axios(options)
|
|
324
|
-
.then((response) => {
|
|
325
|
-
if (response.status == 200) {
|
|
326
|
-
return response.data;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
throw response;
|
|
330
|
-
})
|
|
331
|
-
.catch((e) => this.parseException(e));
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* @private generic handler to parse request exceptions
|
|
336
|
-
*/
|
|
337
|
-
parseException(e: any): unknown {
|
|
338
|
-
if (this.options.parse_exceptions === false) {
|
|
339
|
-
throw e;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Something happened in setting up the request that triggered an Error
|
|
343
|
-
if (!e.response) {
|
|
344
|
-
if (!e.request) {
|
|
345
|
-
throw e.message;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// request made but no response received
|
|
349
|
-
throw e;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// The request was made and the server responded with a status code
|
|
353
|
-
// that falls out of the range of 2xx
|
|
354
|
-
const response: AxiosResponse = e.response;
|
|
355
|
-
throw {
|
|
356
|
-
code: response.status,
|
|
357
|
-
message: response.statusText,
|
|
358
|
-
body: response.data,
|
|
359
|
-
headers: response.headers,
|
|
360
|
-
requestOptions: this.options,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* @private sign request and set recv window
|
|
366
|
-
*/
|
|
367
|
-
private async signRequest<T extends SignedRequestContext | {} = {}>(
|
|
368
|
-
data: T,
|
|
369
|
-
method: Method,
|
|
370
|
-
signMethod: SignMethod,
|
|
371
|
-
): Promise<SignedRequest<T>> {
|
|
372
|
-
const timestamp = Date.now() + (this.timeOffset || 0);
|
|
373
|
-
|
|
374
|
-
const res: SignedRequest<T> = {
|
|
375
|
-
originalParams: {
|
|
376
|
-
...data,
|
|
377
|
-
},
|
|
378
|
-
sign: '',
|
|
379
|
-
timestamp,
|
|
380
|
-
recvWindow: 0,
|
|
381
|
-
serializedParams: '',
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
if (!this.key || !this.secret) {
|
|
385
|
-
return res;
|
|
386
|
-
}
|
|
387
|
-
const key = this.key;
|
|
388
|
-
const recvWindow =
|
|
389
|
-
res.originalParams.recv_window || this.options.recv_window || 5000;
|
|
390
|
-
const strictParamValidation = this.options.strict_param_validation;
|
|
391
|
-
const encodeSerialisedValues = this.options.encodeSerialisedValues;
|
|
392
|
-
|
|
393
|
-
// In case the parent function needs it (e.g. USDC uses a header)
|
|
394
|
-
res.recvWindow = recvWindow;
|
|
395
|
-
|
|
396
|
-
// usdc is different for some reason
|
|
397
|
-
if (signMethod === 'v5auth') {
|
|
398
|
-
const sortProperties = false;
|
|
399
|
-
const signRequestParams =
|
|
400
|
-
method === 'GET'
|
|
401
|
-
? serializeParams(
|
|
402
|
-
res.originalParams,
|
|
403
|
-
strictParamValidation,
|
|
404
|
-
sortProperties,
|
|
405
|
-
encodeSerialisedValues,
|
|
406
|
-
)
|
|
407
|
-
: JSON.stringify(res.originalParams);
|
|
408
|
-
|
|
409
|
-
const paramsStr = timestamp + key + recvWindow + signRequestParams;
|
|
410
|
-
|
|
411
|
-
res.sign = await signMessage(paramsStr, this.secret);
|
|
412
|
-
res.serializedParams = signRequestParams;
|
|
413
|
-
|
|
414
|
-
// console.log('sign req: ', {
|
|
415
|
-
// req: paramsStr,
|
|
416
|
-
// sign: res.sign,
|
|
417
|
-
// });
|
|
418
|
-
return res;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// spot/v2 derivatives
|
|
422
|
-
if (signMethod === 'v2auth') {
|
|
423
|
-
res.originalParams.api_key = key;
|
|
424
|
-
res.originalParams.timestamp = timestamp;
|
|
425
|
-
|
|
426
|
-
// Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen.
|
|
427
|
-
if (recvWindow) {
|
|
428
|
-
if (this.isSpotV1Client()) {
|
|
429
|
-
res.originalParams.recvWindow = recvWindow;
|
|
430
|
-
} else {
|
|
431
|
-
res.originalParams.recv_window = recvWindow;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const sortProperties = true;
|
|
435
|
-
const encodeValues = false;
|
|
436
|
-
|
|
437
|
-
res.serializedParams = serializeParams(
|
|
438
|
-
res.originalParams,
|
|
439
|
-
strictParamValidation,
|
|
440
|
-
sortProperties,
|
|
441
|
-
encodeValues,
|
|
442
|
-
);
|
|
443
|
-
res.sign = await signMessage(res.serializedParams, this.secret);
|
|
444
|
-
|
|
445
|
-
// @ts-ignore
|
|
446
|
-
res.paramsWithSign = {
|
|
447
|
-
...res.originalParams,
|
|
448
|
-
sign: res.sign,
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
return res;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return res;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Trigger time sync and store promise. Use force: true, if automatic time sync is disabled
|
|
459
|
-
*/
|
|
460
|
-
private syncTime(force?: boolean): Promise<any> {
|
|
461
|
-
if (!force && !this.options.enable_time_sync) {
|
|
462
|
-
this.timeOffset = 0;
|
|
463
|
-
return Promise.resolve(false);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (this.syncTimePromise !== null) {
|
|
467
|
-
return this.syncTimePromise;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
this.syncTimePromise = this.fetchTimeOffset().then((offset) => {
|
|
471
|
-
this.timeOffset = offset;
|
|
472
|
-
this.syncTimePromise = null;
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
return this.syncTimePromise;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Estimate drift based on client<->server latency
|
|
480
|
-
*/
|
|
481
|
-
async fetchTimeOffset(): Promise<number> {
|
|
482
|
-
try {
|
|
483
|
-
const start = Date.now();
|
|
484
|
-
const serverTime = await this.fetchServerTime();
|
|
485
|
-
|
|
486
|
-
if (!serverTime || isNaN(serverTime)) {
|
|
487
|
-
throw new Error(
|
|
488
|
-
`fetchServerTime() returned non-number: "${serverTime}" typeof(${typeof serverTime})`,
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const end = Date.now();
|
|
493
|
-
const severTimeMs = serverTime * 1000;
|
|
494
|
-
|
|
495
|
-
const avgDrift = (end - start) / 2;
|
|
496
|
-
return Math.ceil(severTimeMs - end + avgDrift);
|
|
497
|
-
} catch (e) {
|
|
498
|
-
console.error('Failed to fetch get time offset: ', e);
|
|
499
|
-
return 0;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
package/src/util/WsStore.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import WebSocket from 'isomorphic-ws';
|
|
2
|
-
import { WsKey } from '../types';
|
|
3
|
-
|
|
4
|
-
import { DefaultLogger } from './logger';
|
|
5
|
-
|
|
6
|
-
export enum WsConnectionStateEnum {
|
|
7
|
-
INITIAL = 0,
|
|
8
|
-
CONNECTING = 1,
|
|
9
|
-
CONNECTED = 2,
|
|
10
|
-
CLOSING = 3,
|
|
11
|
-
RECONNECTING = 4,
|
|
12
|
-
// ERROR = 5,
|
|
13
|
-
}
|
|
14
|
-
/** A "topic" is always a string */
|
|
15
|
-
type WsTopic = string;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A "Set" is used to ensure we only subscribe to a topic once (tracking a list of unique topics we're expected to be connected to)
|
|
19
|
-
* Note: Accurate duplicate tracking only works for plaintext topics.
|
|
20
|
-
* E.g. JSON objects may not be seen as duplicates if keys are in different orders. If that's needed, check the FTX implementation.
|
|
21
|
-
*/
|
|
22
|
-
type WsTopicList = Set<WsTopic>;
|
|
23
|
-
|
|
24
|
-
interface WsStoredState {
|
|
25
|
-
/** The currently active websocket connection */
|
|
26
|
-
ws?: WebSocket;
|
|
27
|
-
/** The current lifecycle state of the connection (enum) */
|
|
28
|
-
connectionState?: WsConnectionStateEnum;
|
|
29
|
-
/** A timer that will send an upstream heartbeat (ping) when it expires */
|
|
30
|
-
activePingTimer?: ReturnType<typeof setTimeout> | undefined;
|
|
31
|
-
/** A timer tracking that an upstream heartbeat was sent, expecting a reply before it expires */
|
|
32
|
-
activePongTimer?: ReturnType<typeof setTimeout> | undefined;
|
|
33
|
-
/** If a reconnection is in progress, this will have the timer for the delayed reconnect */
|
|
34
|
-
activeReconnectTimer?: ReturnType<typeof setTimeout> | undefined;
|
|
35
|
-
/**
|
|
36
|
-
* All the topics we are expected to be subscribed to (and we automatically resubscribe to if the connection drops)
|
|
37
|
-
*/
|
|
38
|
-
subscribedTopics: WsTopicList;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default class WsStore {
|
|
42
|
-
private wsState: Record<string, WsStoredState>;
|
|
43
|
-
|
|
44
|
-
private logger: typeof DefaultLogger;
|
|
45
|
-
|
|
46
|
-
constructor(logger: typeof DefaultLogger) {
|
|
47
|
-
this.logger = logger || DefaultLogger;
|
|
48
|
-
this.wsState = {};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Get WS stored state for key, optionally create if missing */
|
|
52
|
-
get(key: WsKey, createIfMissing?: true): WsStoredState;
|
|
53
|
-
|
|
54
|
-
get(key: WsKey, createIfMissing?: false): WsStoredState | undefined;
|
|
55
|
-
|
|
56
|
-
get(key: WsKey, createIfMissing?: boolean): WsStoredState | undefined {
|
|
57
|
-
if (this.wsState[key]) {
|
|
58
|
-
return this.wsState[key];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (createIfMissing) {
|
|
62
|
-
return this.create(key);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getKeys(): WsKey[] {
|
|
67
|
-
return Object.keys(this.wsState) as WsKey[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
create(key: WsKey): WsStoredState | undefined {
|
|
71
|
-
if (this.hasExistingActiveConnection(key)) {
|
|
72
|
-
this.logger.warning(
|
|
73
|
-
'WsStore setConnection() overwriting existing open connection: ',
|
|
74
|
-
this.getWs(key)
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
this.wsState[key] = {
|
|
78
|
-
subscribedTopics: new Set(),
|
|
79
|
-
connectionState: WsConnectionStateEnum.INITIAL,
|
|
80
|
-
};
|
|
81
|
-
return this.get(key);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
delete(key: WsKey) {
|
|
85
|
-
if (this.hasExistingActiveConnection(key)) {
|
|
86
|
-
const ws = this.getWs(key);
|
|
87
|
-
this.logger.warning(
|
|
88
|
-
'WsStore deleting state for connection still open: ',
|
|
89
|
-
ws
|
|
90
|
-
);
|
|
91
|
-
ws?.close();
|
|
92
|
-
}
|
|
93
|
-
delete this.wsState[key];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* connection websocket */
|
|
97
|
-
|
|
98
|
-
hasExistingActiveConnection(key: WsKey) {
|
|
99
|
-
return this.get(key) && this.isWsOpen(key);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
getWs(key: WsKey): WebSocket | undefined {
|
|
103
|
-
return this.get(key)?.ws;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
setWs(key: WsKey, wsConnection: WebSocket): WebSocket {
|
|
107
|
-
if (this.isWsOpen(key)) {
|
|
108
|
-
this.logger.warning(
|
|
109
|
-
'WsStore setConnection() overwriting existing open connection: ',
|
|
110
|
-
this.getWs(key)
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
this.get(key, true)!.ws = wsConnection;
|
|
114
|
-
return wsConnection;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* connection state */
|
|
118
|
-
|
|
119
|
-
isWsOpen(key: WsKey): boolean {
|
|
120
|
-
const existingConnection = this.getWs(key);
|
|
121
|
-
return (
|
|
122
|
-
!!existingConnection &&
|
|
123
|
-
existingConnection.readyState === existingConnection.OPEN
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
getConnectionState(key: WsKey): WsConnectionStateEnum {
|
|
128
|
-
return this.get(key, true)!.connectionState!;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
setConnectionState(key: WsKey, state: WsConnectionStateEnum) {
|
|
132
|
-
this.get(key, true)!.connectionState = state;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
isConnectionState(key: WsKey, state: WsConnectionStateEnum): boolean {
|
|
136
|
-
return this.getConnectionState(key) === state;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* subscribed topics */
|
|
140
|
-
|
|
141
|
-
getTopics(key: WsKey): WsTopicList {
|
|
142
|
-
return this.get(key, true).subscribedTopics;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
getTopicsByKey(): Record<string, WsTopicList> {
|
|
146
|
-
const result = {};
|
|
147
|
-
for (const refKey in this.wsState) {
|
|
148
|
-
result[refKey] = this.getTopics(refKey as WsKey);
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
addTopic(key: WsKey, topic: WsTopic) {
|
|
154
|
-
return this.getTopics(key).add(topic);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
deleteTopic(key: WsKey, topic: WsTopic) {
|
|
158
|
-
return this.getTopics(key).delete(topic);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export async function signMessage(
|
|
2
|
-
message: string,
|
|
3
|
-
secret: string
|
|
4
|
-
): Promise<string> {
|
|
5
|
-
const encoder = new TextEncoder();
|
|
6
|
-
// eslint-disable-next-line no-undef
|
|
7
|
-
const key = await window.crypto.subtle.importKey(
|
|
8
|
-
'raw',
|
|
9
|
-
encoder.encode(secret),
|
|
10
|
-
{ name: 'HMAC', hash: { name: 'SHA-256' } },
|
|
11
|
-
false,
|
|
12
|
-
['sign']
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
// eslint-disable-next-line no-undef
|
|
16
|
-
const signature = await window.crypto.subtle.sign(
|
|
17
|
-
'HMAC',
|
|
18
|
-
key,
|
|
19
|
-
encoder.encode(message)
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
return Array.prototype.map
|
|
23
|
-
.call(
|
|
24
|
-
new Uint8Array(signature),
|
|
25
|
-
(x: { toString: (arg0: number) => string }) =>
|
|
26
|
-
('00' + x.toString(16)).slice(-2)
|
|
27
|
-
)
|
|
28
|
-
.join('');
|
|
29
|
-
}
|