kucoin-universal-sdk
Version:
Official KuCoin Universal SDK.
292 lines • 12.8 kB
JavaScript
;
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