UNPKG

binance

Version:

Professional Node.js & JavaScript SDK for Binance REST APIs & WebSockets, with TypeScript & end-to-end tests.

285 lines 12.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; 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 https_1 = __importDefault(require("https")); const beautifier_1 = __importDefault(require("./beautifier")); const requestUtils_1 = require("./requestUtils"); class BaseRestClient { constructor(baseUrlKey, options = {}, requestOptions = {}) { var _a, _b; this.timeOffset = 0; this.options = Object.assign(Object.assign({ recvWindow: 5000, // how often to sync time drift with binance servers syncIntervalMs: 3600000, // if true, we'll throw errors if any params are undefined strictParamValidation: false, // disable the time sync mechanism by default disableTimeSync: true }, options), { api_key: (_a = options === null || options === void 0 ? void 0 : options.api_key) === null || _a === void 0 ? void 0 : _a.replace(/\\n/g, '\n'), api_secret: (_b = options === null || options === void 0 ? void 0 : options.api_secret) === null || _b === void 0 ? void 0 : _b.replace(/\\n/g, '\n') }); this.globalRequestOptions = Object.assign({ // in ms == 5 minutes by default timeout: 1000 * 60 * 5, headers: { // 'content-type': 'application/x-www-form-urlencoded'; } }, requestOptions); // If enabled, configure a https agent with keepAlive enabled if (this.options.keepAlive) { // Extract existing https agent parameters, if provided, to prevent the keepAlive flag from overwriting an existing https agent completely const existingHttpsAgent = this.globalRequestOptions.httpsAgent; const existingAgentOptions = (existingHttpsAgent === null || existingHttpsAgent === void 0 ? void 0 : existingHttpsAgent.options) || {}; // For more advanced configuration, raise an issue on GitHub or use the "networkOptions" // parameter to define a custom httpsAgent with the desired properties this.globalRequestOptions.httpsAgent = new https_1.default.Agent(Object.assign(Object.assign({}, existingAgentOptions), { keepAlive: true, keepAliveMsecs: this.options.keepAliveMsecs })); } this.key = this.options.api_key; this.secret = this.options.api_secret; if (this.key) { if (!this.globalRequestOptions.headers) { this.globalRequestOptions.headers = {}; } this.globalRequestOptions.headers['X-MBX-APIKEY'] = this.key; } const derivedBaseUrlKey = this.options.baseUrlKey || baseUrlKey; this.baseUrlKey = options.testnet ? (0, requestUtils_1.getTestnetBaseUrlKey)(derivedBaseUrlKey) : derivedBaseUrlKey; this.baseUrl = (0, requestUtils_1.getRestBaseUrl)(this.baseUrlKey, this.options); if (this.key && !this.secret) { throw new Error('API Key & Secret are both required for private enpoints'); } // WebCryptoAPI feature /* if (this.key && this.secret) { // Provide a user friendly error message if the user is using an outdated Node.js version (where Web Crypto API is not available). // A few users have been caught out by using the end-of-life Node.js v18 release. checkWebCryptoAPISupported(); } */ if (this.options.disableTimeSync !== true) { this.syncTime(); setInterval(this.syncTime.bind(this), +this.options.syncIntervalMs); } if (this.options.beautifyResponses) { this.beautifier = new beautifier_1.default({ warnKeyMissingInMap: false }); } this.syncTimePromise = null; this.apiLimitTrackers = { 'x-mbx-used-weight': 0, 'x-mbx-used-weight-1m': 0, 'x-sapi-used-ip-weight-1m': 0, 'x-mbx-order-count-1s': 0, 'x-mbx-order-count-10s': 0, 'x-mbx-order-count-1m': 0, 'x-mbx-order-count-1h': 0, 'x-mbx-order-count-1d': 0, }; } getBaseUrlKey() { return this.baseUrlKey; } getRateLimitStates() { return Object.assign(Object.assign({}, this.apiLimitTrackers), { lastUpdated: this.apiLimitLastUpdated }); } /** * Return time sync offset, automatically set if time sync is enabled. A higher offset means system clock is behind server time. */ getTimeOffset() { return this.timeOffset; } setTimeOffset(value) { this.timeOffset = value; } get(endpoint, params) { return this._call('GET', endpoint, params); } getForBaseUrl(endpoint, baseUrlKey, params) { const baseUrl = (0, requestUtils_1.getRestBaseUrl)(baseUrlKey, {}); return this._call('GET', endpoint, params, false, baseUrl); } getPrivate(endpoint, params) { return this._call('GET', endpoint, params, true); } post(endpoint, params) { return this._call('POST', endpoint, params); } postPrivate(endpoint, params) { return this._call('POST', endpoint, params, true); } put(endpoint, params) { return this._call('PUT', endpoint, params); } putPrivate(endpoint, params) { return this._call('PUT', endpoint, params, true); } delete(endpoint, params) { return this._call('DELETE', endpoint, params); } deletePrivate(endpoint, params) { return this._call('DELETE', endpoint, params, true); } /** * @private Make a HTTP request to a specific endpoint. Private endpoints are automatically signed. */ _call(method, endpoint, params, isPrivate, baseUrlOverride) { return __awaiter(this, void 0, void 0, function* () { const timestamp = Date.now() + (this.getTimeOffset() || 0); if (isPrivate && (!this.key || !this.secret)) { throw new Error('Private endpoints require api and private keys to be set'); } // Handles serialisation of params into query string (url?key1=value1&key2=value2), handles encoding of values, adds timestamp and signature to request. const { serialisedParams, signature, requestBody } = yield (0, requestUtils_1.getRESTRequestSignature)(params, this.options, this.key, this.secret, timestamp); const baseUrl = baseUrlOverride || this.baseUrl; const options = Object.assign(Object.assign({}, this.globalRequestOptions), { url: [baseUrl, endpoint].join('/'), method: method, json: true }); if (isPrivate) { options.url += '?' + [serialisedParams, 'signature=' + signature].join('&'); } else if (method === 'GET' || method === 'DELETE') { options.params = params; } else { options.data = (0, requestUtils_1.serialiseParams)(requestBody, this.options.strictParamValidation, true); } // console.log( // 'sending request: ', // JSON.stringify( // { // serialisedParams, // requestBody, // signature, // reqOptions: options, // reqParams: params, // }, // null, // 2, // ), // ); return (0, axios_1.default)(options) .then((response) => { this.updateApiLimitState(response.headers); if (response.status == 200) { return response.data; } throw response; }) .then((response) => { if (!this.options.beautifyResponses || !this.beautifier) { return response; } // Fallback to original response if beautifier fails try { return this.beautifier.beautify(response, endpoint) || response; } catch (e) { console.error('BaseRestClient response beautify failed: ', JSON.stringify({ response: response, error: e })); } return response; }) .catch((e) => this.parseException(e, options.url)); }); } /** * @private generic handler to parse request exceptions */ parseException(e, url) { var _a, _b; const { response, request, message } = e; if (response && response.headers) { this.updateApiLimitState(response.headers); } if (this.options.parseExceptions === false) { throw e; } // Something happened in setting up the request that triggered an Error if (!response) { if (!request) { throw 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 throw { code: (_a = response.data) === null || _a === void 0 ? void 0 : _a.code, message: (_b = response.data) === null || _b === void 0 ? void 0 : _b.msg, body: response.data, headers: response.headers, requestUrl: url, requestBody: request.body, requestOptions: Object.assign(Object.assign({}, this.options), { api_key: undefined, api_secret: undefined }), }; } updateApiLimitState(responseHeaders) { const delta = {}; for (const headerKey in this.apiLimitTrackers) { const headerValue = responseHeaders[headerKey]; const value = parseInt(headerValue); if (headerValue !== undefined && !isNaN(value)) { // TODO: track last seen by key? insetad of all? some keys not returned by some endpoints more useful in estimating whether reset should've happened this.apiLimitTrackers[headerKey] = value; delta[headerKey] = { updated: true, valueParsed: value, valueRaw: headerValue, }; } else { delta[headerKey] = { updated: false, valueParsed: value, valueRaw: headerValue, }; } } // console.log('responseHeaders: ', requestedUrl); // console.table(responseHeaders); // console.table(delta); this.apiLimitLastUpdated = new Date().getTime(); } /** * Trigger time sync and store promise */ syncTime() { if (this.options.disableTimeSync === true) { return Promise.resolve(); } 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 */ fetchTimeOffset() { return __awaiter(this, void 0, void 0, function* () { try { const start = Date.now(); const serverTime = yield this.getServerTime(); const end = Date.now(); const avgDrift = (end - start) / 2; return Math.ceil(serverTime - end + avgDrift); } catch (e) { console.error('Failed to fetch get time offset: ', e); return 0; } }); } } exports.default = BaseRestClient; //# sourceMappingURL=BaseRestClient.js.map