UNPKG

kucoin-universal-sdk

Version:
292 lines 12.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultTransport = void 0; const common_1 = require("../../model/common"); const constant_1 = require("../../model/constant"); const transport_option_1 = require("../../model/transport_option"); const default_signer_1 = require("./default_signer"); const axios_1 = __importDefault(require("axios")); require("reflect-metadata"); const axios_retry_1 = __importDefault(require("axios-retry")); const http_1 = require("http"); const https_1 = require("https"); class DefaultTransport { constructor(option, version) { this.transportOption = {}; this.option = option; this.version = version; this.transportOption = option.transportOption || {}; this.signer = new default_signer_1.KcSigner(option.key, option.secret, option.passphrase, option.brokerName, option.brokerPartner, option.brokerKey); this.httpClient = this.createHttpClient(this.transportOption); } createHttpClient(trans_option) { var _a, _b, _c, _d, _e; const selectedProxy = (_b = (_a = trans_option.proxy) === null || _a === void 0 ? void 0 : _a.https) !== null && _b !== void 0 ? _b : (_c = trans_option.proxy) === null || _c === void 0 ? void 0 : _c.http; const instance = axios_1.default.create({ timeout: trans_option.timeout || transport_option_1.DEFAULT_TRANSPORT_OPTION.timeout, headers: { Connection: trans_option.keepAlive ? 'keep-alive' : 'close', }, httpAgent: trans_option.keepAlive ? new http_1.Agent({ maxSockets: trans_option.maxConnsPerHost || transport_option_1.DEFAULT_TRANSPORT_OPTION.maxConnsPerHost, maxFreeSockets: trans_option.maxIdleConnsPerHost || transport_option_1.DEFAULT_TRANSPORT_OPTION.maxIdleConnsPerHost, timeout: trans_option.timeout || transport_option_1.DEFAULT_TRANSPORT_OPTION.timeout, keepAlive: true, keepAliveMsecs: trans_option.idleConnTimeout || transport_option_1.DEFAULT_TRANSPORT_OPTION.idleConnTimeout, }) : undefined, httpsAgent: trans_option.keepAlive ? new https_1.Agent({ maxSockets: trans_option.maxConnsPerHost || transport_option_1.DEFAULT_TRANSPORT_OPTION.maxConnsPerHost, maxFreeSockets: trans_option.maxIdleConnsPerHost || transport_option_1.DEFAULT_TRANSPORT_OPTION.maxIdleConnsPerHost, timeout: trans_option.timeout || transport_option_1.DEFAULT_TRANSPORT_OPTION.timeout, keepAlive: true, keepAliveMsecs: trans_option.idleConnTimeout || transport_option_1.DEFAULT_TRANSPORT_OPTION.idleConnTimeout, }) : undefined, proxy: selectedProxy ? { host: selectedProxy.host, port: selectedProxy.port, auth: ((_d = trans_option.proxy) === null || _d === void 0 ? void 0 : _d.auth) ? { username: trans_option.proxy.auth.username, password: trans_option.proxy.auth.password, } : undefined, } : false, }); // Add retry logic (0, axios_retry_1.default)(instance, { retries: trans_option.maxRetries || transport_option_1.DEFAULT_TRANSPORT_OPTION.maxRetries, shouldResetTimeout: true, retryDelay: (retryCount, error) => { const delay = trans_option.retryDelay; return delay; }, retryCondition: (error) => { var _a; // acquire request config const currentRetry = ((_a = error.config) === null || _a === void 0 ? void 0 : _a._retry) || 0; const maxRetries = trans_option.maxRetries || transport_option_1.DEFAULT_TRANSPORT_OPTION.maxRetries; // change retry condition here const shouldRetry = axios_retry_1.default.isNetworkOrIdempotentRequestError(error) || error.message.includes('timeout') || (error.response && error.response.status >= 500) || error.code === 'ECONNABORTED'; return shouldRetry && currentRetry < maxRetries; }, }); (_e = this.transportOption.interceptors) === null || _e === void 0 ? void 0 : _e.forEach((interceptor) => { instance.interceptors.request.use(interceptor.before.onFulfilled, interceptor.before.onRejected, interceptor.before.options); instance.interceptors.response.use(interceptor.after.onFulfilled, interceptor.after.onRejected); }); return instance; } processHeaders(body, rawUrl, config, method, broker) { const payload = `${method}${rawUrl}${body || ''}`; const headers = broker ? this.signer.brokerHeaders(payload) : this.signer.headers(payload); config.headers = { ...config.headers, ...headers, }; } processPathVariable(path, requestObj) { if (!requestObj) { return path; } const pathVariables = {}; for (const key of Object.keys(requestObj)) { const metadata = Reflect.getMetadata('path', requestObj, key); if (metadata) { pathVariables[metadata] = requestObj[key]; } } const missingPlaceholders = Object.entries(pathVariables) .filter(([_, value]) => value == null) .map(([key]) => key); if (missingPlaceholders.length > 0) { throw new Error(`Missing path variable value(s) for: ${missingPlaceholders.join(', ')}`); } return path.replace(/{(.*?)}/g, (_, key) => { if (key in pathVariables) { return String(pathVariables[key]); } throw new Error(`Path variable {${key}} is not defined in request object.`); }); } encodeQuery(queryDict) { return Object.entries(queryDict) .map(([key, value]) => { if (Array.isArray(value)) { return value.map((val) => `${key}=${encodeURIComponent(val)}`).join('&'); } return `${key}=${encodeURIComponent(value)}`; }) .join('&'); } rawQuery(queryDict) { return Object.entries(queryDict) .map(([key, value]) => { if (Array.isArray(value)) { return value.map((val) => `${key}=${val}`).join('&'); } return `${key}=${value}`; }) .join('&'); } processRequest(requestObj, broker, path, rawpath, endpoint, method, requestAsJson, args) { const fullPath = endpoint + path; const rawUrl = path; let reqBody = null; let queryPath = path; let rawPath = path; if (requestAsJson) { if (requestObj) { reqBody = requestObj.toJson(); } } else { if (method === 'GET' || method === 'DELETE') { if (requestObj) { // create a new object for query parameters const queryObj = { ...requestObj }; // check path variables and remove from query const pathVarPattern = /{([^}]+)}/g; let match; while ((match = pathVarPattern.exec(rawpath)) !== null) { const pathVarName = match[1]; if (pathVarName in queryObj) { delete queryObj[pathVarName]; } } const queryParams = this.encodeQuery(queryObj); const rawParams = this.rawQuery(queryObj); if (queryParams) { queryPath = `${path}?${queryParams}`; } if (rawParams) { rawPath = `${path}?${rawParams}`; } } } else if (method === 'POST') { if (requestObj) { reqBody = requestObj.toJson(); } } else { throw new Error(`Invalid method: ${method}`); } } const config = { method: method.toLowerCase(), url: endpoint + queryPath, headers: { 'Content-Type': 'application/json', 'User-Agent': `Kucoin-Universal-Node-SDK/${this.version}`, }, }; if (reqBody != null) { config.data = reqBody; } // Use queryPath instead of rawUrl for signature this.processHeaders(reqBody, rawPath, config, method, broker); return config; } processLimit(headers) { const limit = parseInt(headers['gw-ratelimit-limit'] || '-1', 10); const remaining = parseInt(headers['gw-ratelimit-remaining'] || '-1', 10); const reset = parseInt(headers['gw-ratelimit-reset'] || '-1', 10); const rateLimit = { limit, remaining, reset, }; return rateLimit; } processResponse(response, responseCls) { if (response.status != 200) { throw new Error(`Invalid status code: ${response.status}, msg: ${response.data}`); } const commonResponse = common_1.RestResponse.fromJson(JSON.stringify(response.data)); commonResponse.rateLimit = this.processLimit(response.headers); commonResponse.checkRestResponseError(); if (commonResponse.data == null) { let responseObj = responseCls.fromObject({}); responseObj.setCommonResponse(commonResponse); return responseObj; } let responseObj = responseCls.fromObject(commonResponse.data); responseObj.setCommonResponse(commonResponse); return responseObj; } call(domain, isBroker, method, path, requestObj, responseCls, requestJson, args) { method = method.toUpperCase(); return Promise.resolve() .then(() => { const endpoint = this.getEndpoint(domain); const processedPath = this.processPathVariable(path, requestObj); return this.processRequest(requestObj, isBroker, processedPath, path, endpoint, method, requestJson, args); }) .then((config) => { return this.httpClient.request(config); }) .then((response) => { return this.processResponse(response, responseCls); }) .catch((err) => { if (err instanceof common_1.RestError) { throw err; } else { throw new common_1.RestError(null, err); } }); } getEndpoint(domain) { switch (domain.toLowerCase()) { case constant_1.DomainType.Spot: if (!this.option.spotEndpoint) { throw new Error('Spot endpoint is not set'); } return this.option.spotEndpoint; case constant_1.DomainType.Futures: if (!this.option.futuresEndpoint) { throw new Error('Futures endpoint is not set'); } return this.option.futuresEndpoint; case constant_1.DomainType.Broker: if (!this.option.brokerEndpoint) { throw new Error('Broker endpoint is not set'); } return this.option.brokerEndpoint; default: throw new Error(`Invalid domain: ${domain}`); } } close() { // Cancel any pending requests if (this.httpClient) { // Clear any persistent connections if (this.httpClient.defaults.httpAgent) { this.httpClient.defaults.httpAgent.destroy(); } if (this.httpClient.defaults.httpsAgent) { this.httpClient.defaults.httpsAgent.destroy(); } // Remove reference to the client this.httpClient = null; } return Promise.resolve(undefined); } } exports.DefaultTransport = DefaultTransport; //# sourceMappingURL=default_transport.js.map