UNPKG

bybit-api-gnome

Version:

Forked for Lick Hunter, Complete & robust node.js SDK for Bybit's REST APIs and WebSockets v5, with TypeScript & integration tests.

281 lines 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = __importDefault(require("axios")); const node_support_1 = require("./node-support"); const requestUtils_1 = require("./requestUtils"); class BaseRestClient { /** * Create an instance of the REST client. Pass API credentials in the object in the first parameter. * @param {RestClientOptions} [restClientOptions={}] options to configure REST API connectivity * @param {AxiosRequestConfig} [networkOptions={}] HTTP networking options for axios */ constructor(restOptions = {}, networkOptions = {}) { this.timeOffset = null; this.syncTimePromise = null; this.clientType = this.getClientType(); this.options = { recv_window: 5000, /** Throw errors if any request params are empty */ strict_param_validation: false, /** Disable time sync by default */ enable_time_sync: false, /** How often to sync time drift with bybit servers (if time sync is enabled) */ sync_interval_ms: 3600000, /** Request parameter values are now URI encoded by default during signing. Set to false to override this behaviour. */ encodeSerialisedValues: true, ...restOptions, }; this.globalRequestOptions = { // in ms == 5 minutes by default timeout: 1000 * 60 * 5, // custom request options based on axios specs - see: https://github.com/axios/axios#request-config ...networkOptions, headers: { 'x-referer': requestUtils_1.APIID, }, }; this.baseUrl = (0, requestUtils_1.getRestBaseUrl)(!!this.options.testnet, restOptions); this.key = this.options.key; this.secret = this.options.secret; if (this.key && !this.secret) { throw new Error('API Key & Secret are both required for private endpoints'); } if (this.options.enable_time_sync) { this.syncTime(); setInterval(this.syncTime.bind(this), +this.options.sync_interval_ms); } } isSpotV1Client() { return this.clientType === requestUtils_1.REST_CLIENT_TYPE_ENUM.spot; } get(endpoint, params) { return this._call('GET', endpoint, params, true); } getPrivate(endpoint, params) { return this._call('GET', endpoint, params, false); } post(endpoint, params) { return this._call('POST', endpoint, params, true); } postPrivate(endpoint, params) { return this._call('POST', endpoint, params, false); } deletePrivate(endpoint, params) { return this._call('DELETE', endpoint, params, false); } async prepareSignParams(method, signMethod, params, isPublicApi) { if (isPublicApi) { return { originalParams: params, paramsWithSign: params, }; } if (!this.key || !this.secret) { throw new Error('Private endpoints require api and private keys set'); } if (this.timeOffset === null) { await this.syncTime(); } return this.signRequest((params || {}), method, signMethod); } /** Returns an axios request object. Handles signing process automatically if this is a private API call */ async buildRequest(method, url, params, isPublicApi) { const options = { ...this.globalRequestOptions, url: url, method: method, }; for (const key in params) { if (typeof params[key] === 'undefined') { delete params[key]; } } if (isPublicApi) { return { ...options, params: params, }; } // USDC endpoints, unified margin and a few others use a different way of authenticating requests (headers instead of params) if (this.clientType === requestUtils_1.REST_CLIENT_TYPE_ENUM.v3) { if (!options.headers) { options.headers = {}; } const signResult = await this.prepareSignParams(method, 'usdc', params, isPublicApi); options.headers['X-BAPI-SIGN-TYPE'] = 2; options.headers['X-BAPI-API-KEY'] = this.key; options.headers['X-BAPI-TIMESTAMP'] = signResult.timestamp; options.headers['X-BAPI-SIGN'] = signResult.sign; options.headers['X-BAPI-RECV-WINDOW'] = signResult.recvWindow; if (method === 'GET') { // const serialisedParams = signResult.serializedParams; return { ...options, params: signResult.originalParams, // url: url + (serialisedParams ? '?' + serialisedParams : ''), }; } return { ...options, data: signResult.originalParams, }; } const signResult = await this.prepareSignParams(method, 'keyInBody', params, isPublicApi); if (method === 'GET' || this.isSpotV1Client()) { return { ...options, params: signResult.paramsWithSign, }; } return { ...options, data: signResult.paramsWithSign, }; } /** * @private Make a HTTP request to a specific endpoint. Private endpoints are automatically signed. */ async _call(method, endpoint, params, isPublicApi) { // Sanity check to make sure it's only ever prefixed by one forward slash const requestUrl = [this.baseUrl, endpoint].join(endpoint.startsWith('/') ? '' : '/'); // Build a request and handle signature process const options = await this.buildRequest(method, requestUrl, params, isPublicApi); // Dispatch request return (0, axios_1.default)(options) .then((response) => { if (response.status == 200) { return response.data; } throw response; }) .catch((e) => this.parseException(e)); } /** * @private generic handler to parse request exceptions */ parseException(e) { if (this.options.parse_exceptions === false) { throw e; } // Something happened in setting up the request that triggered an Error if (!e.response) { if (!e.request) { throw e.message; } // request made but no response received throw e; } // The request was made and the server responded with a status code // that falls out of the range of 2xx const response = e.response; throw { code: response.status, message: response.statusText, body: response.data, headers: response.headers, requestOptions: this.options, }; } /** * @private sign request and set recv window */ async signRequest(data, method, signMethod) { const timestamp = Date.now() + (this.timeOffset || 0); const res = { originalParams: { ...data, }, sign: '', timestamp, recvWindow: 0, serializedParams: '', }; if (!this.key || !this.secret) { return res; } const key = this.key; const recvWindow = res.originalParams.recv_window || this.options.recv_window || 5000; const strictParamValidation = this.options.strict_param_validation; const encodeSerialisedValues = this.options.encodeSerialisedValues; // In case the parent function needs it (e.g. USDC uses a header) res.recvWindow = recvWindow; // usdc is different for some reason if (signMethod === 'usdc') { const sortProperties = false; const signRequestParams = method === 'GET' ? (0, requestUtils_1.serializeParams)(res.originalParams, strictParamValidation, sortProperties, encodeSerialisedValues) : JSON.stringify(res.originalParams); const paramsStr = timestamp + key + recvWindow + signRequestParams; res.sign = await (0, node_support_1.signMessage)(paramsStr, this.secret); res.serializedParams = signRequestParams; // console.log('sign req: ', paramsStr); return res; } // spot/v2 derivatives if (signMethod === 'keyInBody') { res.originalParams.api_key = key; res.originalParams.timestamp = timestamp; // Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen. if (recvWindow) { if (this.isSpotV1Client()) { res.originalParams.recvWindow = recvWindow; } else { res.originalParams.recv_window = recvWindow; } } const sortProperties = true; const encodeValues = false; res.serializedParams = (0, requestUtils_1.serializeParams)(res.originalParams, strictParamValidation, sortProperties, encodeValues); res.sign = await (0, node_support_1.signMessage)(res.serializedParams, this.secret); res.paramsWithSign = { ...res.originalParams, sign: res.sign, }; return res; } return res; } /** * Trigger time sync and store promise. Use force: true, if automatic time sync is disabled */ syncTime(force) { if (!force && !this.options.enable_time_sync) { this.timeOffset = 0; return Promise.resolve(false); } if (this.syncTimePromise !== null) { return this.syncTimePromise; } this.syncTimePromise = this.fetchTimeOffset().then((offset) => { this.timeOffset = offset; this.syncTimePromise = null; }); return this.syncTimePromise; } /** * Estimate drift based on client<->server latency */ async fetchTimeOffset() { try { const start = Date.now(); 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 severTimeMs = serverTime * 1000; const avgDrift = (end - start) / 2; return Math.ceil(severTimeMs - end + avgDrift); } catch (e) { console.error('Failed to fetch get time offset: ', e); return 0; } } } exports.default = BaseRestClient; //# sourceMappingURL=BaseRestClient.js.map