- Returned type improvements.
- Start migrating to base rest client. - Remove deprecated methods. - Run linter. - Deprecate shared endpoints for readibility. All methods are replicated within each client (there's not much duplication). - Expand test coverage for public inverse endpoints.
This commit is contained in:
@@ -1,7 +1,18 @@
|
||||
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
|
||||
import axios, {
|
||||
AxiosError,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
Method,
|
||||
} from 'axios';
|
||||
|
||||
import { signMessage } from './node-support';
|
||||
import { RestClientOptions, GenericAPIResponse, getRestBaseUrl, serializeParams, isPublicEndpoint } from './requestUtils';
|
||||
import {
|
||||
RestClientOptions,
|
||||
GenericAPIResponse,
|
||||
getRestBaseUrl,
|
||||
serializeParams,
|
||||
isPublicEndpoint,
|
||||
} from './requestUtils';
|
||||
|
||||
export default abstract class BaseRestClient {
|
||||
private timeOffset: number | null;
|
||||
@@ -12,6 +23,9 @@ export default abstract class BaseRestClient {
|
||||
private key: string | undefined;
|
||||
private secret: string | undefined;
|
||||
|
||||
/** Function that calls exchange API to query & resolve server time, used by time sync */
|
||||
abstract fetchServerTime(): Promise<number>;
|
||||
|
||||
constructor(
|
||||
key: string | undefined,
|
||||
secret: string | undefined,
|
||||
@@ -28,7 +42,7 @@ export default abstract class BaseRestClient {
|
||||
sync_interval_ms: 3600000,
|
||||
// if true, we'll throw errors if any params are undefined
|
||||
strict_param_validation: false,
|
||||
...options
|
||||
...options,
|
||||
};
|
||||
|
||||
this.globalRequestOptions = {
|
||||
@@ -37,14 +51,16 @@ export default abstract class BaseRestClient {
|
||||
// custom request options based on axios specs - see: https://github.com/axios/axios#request-config
|
||||
...requestOptions,
|
||||
headers: {
|
||||
'x-referer': 'bybitapinode'
|
||||
'x-referer': 'bybitapinode',
|
||||
},
|
||||
};
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
|
||||
if (key && !secret) {
|
||||
throw new Error('API Key & Secret are both required for private enpoints')
|
||||
throw new Error(
|
||||
'API Key & Secret are both required for private enpoints'
|
||||
);
|
||||
}
|
||||
|
||||
if (this.options.disable_time_sync !== true) {
|
||||
@@ -79,7 +95,12 @@ export default abstract class BaseRestClient {
|
||||
/**
|
||||
* @private Make a HTTP request to a specific endpoint. Private endpoints are automatically signed.
|
||||
*/
|
||||
private async _call(method: Method, endpoint: string, params?: any, isPublicApi?: boolean): GenericAPIResponse {
|
||||
private async _call(
|
||||
method: Method,
|
||||
endpoint: string,
|
||||
params?: any,
|
||||
isPublicApi?: boolean
|
||||
): GenericAPIResponse {
|
||||
if (!isPublicApi) {
|
||||
if (!this.key || !this.secret) {
|
||||
throw new Error('Private endpoints require api and private keys set');
|
||||
@@ -96,7 +117,7 @@ export default abstract class BaseRestClient {
|
||||
...this.globalRequestOptions,
|
||||
url: [this.baseUrl, endpoint].join(endpoint.startsWith('/') ? '' : '/'),
|
||||
method: method,
|
||||
json: true
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (method === 'GET') {
|
||||
@@ -105,13 +126,15 @@ export default abstract class BaseRestClient {
|
||||
options.data = params;
|
||||
}
|
||||
|
||||
return axios(options).then(response => {
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
return axios(options)
|
||||
.then((response) => {
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw response;
|
||||
}).catch(e => this.parseException(e));
|
||||
throw response;
|
||||
})
|
||||
.catch((e) => this.parseException(e));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +163,7 @@ export default abstract class BaseRestClient {
|
||||
message: response.statusText,
|
||||
body: response.data,
|
||||
headers: response.headers,
|
||||
requestOptions: this.options
|
||||
requestOptions: this.options,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,7 +174,7 @@ export default abstract class BaseRestClient {
|
||||
const params = {
|
||||
...data,
|
||||
api_key: this.key,
|
||||
timestamp: Date.now() + (this.timeOffset || 0)
|
||||
timestamp: Date.now() + (this.timeOffset || 0),
|
||||
};
|
||||
|
||||
// Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen.
|
||||
@@ -160,7 +183,10 @@ export default abstract class BaseRestClient {
|
||||
}
|
||||
|
||||
if (this.key && this.secret) {
|
||||
const serializedParams = serializeParams(params, this.options.strict_param_validation);
|
||||
const serializedParams = serializeParams(
|
||||
params,
|
||||
this.options.strict_param_validation
|
||||
);
|
||||
params.sign = await signMessage(serializedParams, this.secret);
|
||||
}
|
||||
|
||||
@@ -179,7 +205,7 @@ export default abstract class BaseRestClient {
|
||||
return this.syncTimePromise;
|
||||
}
|
||||
|
||||
this.syncTimePromise = this.fetchTimeOffset().then(offset => {
|
||||
this.syncTimePromise = this.fetchTimeOffset().then((offset) => {
|
||||
this.timeOffset = offset;
|
||||
this.syncTimePromise = null;
|
||||
});
|
||||
@@ -187,22 +213,27 @@ export default abstract class BaseRestClient {
|
||||
return this.syncTimePromise;
|
||||
}
|
||||
|
||||
abstract getServerTime(baseUrlKeyOverride?: string): Promise<number>;
|
||||
|
||||
/**
|
||||
* Estimate drift based on client<->server latency
|
||||
*/
|
||||
async fetchTimeOffset(): Promise<number> {
|
||||
try {
|
||||
const start = Date.now();
|
||||
const serverTime = await this.getServerTime();
|
||||
const serverTime = await this.fetchServerTime();
|
||||
|
||||
if (!serverTime || isNaN(serverTime)) {
|
||||
throw new Error(
|
||||
`fetchServerTime() returned non-number: "${serverTime}" typeof(${typeof serverTime})`
|
||||
);
|
||||
}
|
||||
|
||||
const end = Date.now();
|
||||
|
||||
const avgDrift = ((end - start) / 2);
|
||||
const avgDrift = (end - start) / 2;
|
||||
return Math.ceil(serverTime - end + avgDrift);
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch get time offset: ', e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,25 +19,33 @@ export interface RestClientOptions {
|
||||
parse_exceptions?: boolean;
|
||||
}
|
||||
|
||||
export type GenericAPIResponse = Promise<any>;
|
||||
export type GenericAPIResponse<T = any> = Promise<T>;
|
||||
|
||||
export function serializeParams(params: object = {}, strict_validation = false): string {
|
||||
export function serializeParams(
|
||||
params: object = {},
|
||||
strict_validation = false
|
||||
): string {
|
||||
return Object.keys(params)
|
||||
.sort()
|
||||
.map(key => {
|
||||
.map((key) => {
|
||||
const value = params[key];
|
||||
if (strict_validation === true && typeof value === 'undefined') {
|
||||
throw new Error('Failed to sign API request due to undefined parameter');
|
||||
throw new Error(
|
||||
'Failed to sign API request due to undefined parameter'
|
||||
);
|
||||
}
|
||||
return `${key}=${value}`;
|
||||
})
|
||||
.join('&');
|
||||
};
|
||||
}
|
||||
|
||||
export function getRestBaseUrl(useLivenet: boolean, restInverseOptions: RestClientOptions) {
|
||||
export function getRestBaseUrl(
|
||||
useLivenet: boolean,
|
||||
restInverseOptions: RestClientOptions
|
||||
) {
|
||||
const baseUrlsInverse = {
|
||||
livenet: 'https://api.bybit.com',
|
||||
testnet: 'https://api-testnet.bybit.com'
|
||||
testnet: 'https://api-testnet.bybit.com',
|
||||
};
|
||||
|
||||
if (restInverseOptions.baseUrl) {
|
||||
@@ -50,7 +58,7 @@ export function getRestBaseUrl(useLivenet: boolean, restInverseOptions: RestClie
|
||||
return baseUrlsInverse.testnet;
|
||||
}
|
||||
|
||||
export function isPublicEndpoint (endpoint: string): boolean {
|
||||
export function isPublicEndpoint(endpoint: string): boolean {
|
||||
if (endpoint.startsWith('v2/public')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user