From 7412212c71a7a32ca8022a338c88912b9ef3d534 Mon Sep 17 00:00:00 2001 From: MT00x Date: Mon, 4 Dec 2017 09:53:19 +0000 Subject: [PATCH 1/4] Fix automatic products generation. Fix commi 757 that overwrote minimun increment for kraken products --- extensions/exchanges/kraken/products.json | 94 +++++++++---------- .../exchanges/kraken/update-products.sh | 2 +- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/extensions/exchanges/kraken/products.json b/extensions/exchanges/kraken/products.json index 079959a4ad..3477647823 100644 --- a/extensions/exchanges/kraken/products.json +++ b/extensions/exchanges/kraken/products.json @@ -3,329 +3,329 @@ "asset": "BCH", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "BCH/EUR" }, { "asset": "BCH", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "BCH/USD" }, { "asset": "BCH", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "BCH/XBT" }, { "asset": "DASH", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "DASH/EUR" }, { "asset": "DASH", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "DASH/USD" }, { "asset": "DASH", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "DASH/XBT" }, { "asset": "EOS", "currency": "XETH", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "EOS/ETH" }, { "asset": "EOS", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "EOS/XBT" }, { "asset": "GNO", "currency": "XETH", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "GNO/ETH" }, { "asset": "GNO", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "GNO/XBT" }, { "asset": "USDT", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "USDT/USD" }, { "asset": "XETC", "currency": "XETH", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETC/ETH" }, { "asset": "XETC", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETC/XBT" }, { "asset": "XETC", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETC/EUR" }, { "asset": "XETC", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETC/USD" }, { "asset": "XETH", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETH/XBT" }, { "asset": "XETH", "currency": "ZCAD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETH/CAD" }, { "asset": "XETH", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETH/EUR" }, { "asset": "XETH", "currency": "ZGBP", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETH/GBP" }, { "asset": "XETH", "currency": "ZJPY", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETH/JPY" }, { "asset": "XETH", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ETH/USD" }, { "asset": "XICN", "currency": "XETH", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ICN/ETH" }, { "asset": "XICN", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ICN/XBT" }, { "asset": "XLTC", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "LTC/XBT" }, { "asset": "XLTC", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "LTC/EUR" }, { "asset": "XLTC", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "LTC/USD" }, { "asset": "XMLN", "currency": "XETH", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "MLN/ETH" }, { "asset": "XMLN", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "MLN/XBT" }, { "asset": "XREP", "currency": "XETH", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "REP/ETH" }, { "asset": "XREP", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "REP/XBT" }, { "asset": "XREP", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "REP/EUR" }, { "asset": "XXBT", "currency": "ZCAD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XBT/CAD" }, { "asset": "XXBT", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XBT/EUR" }, { "asset": "XXBT", "currency": "ZGBP", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XBT/GBP" }, { "asset": "XXBT", "currency": "ZJPY", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XBT/JPY" }, { "asset": "XXBT", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XBT/USD" }, { "asset": "XXDG", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XDG/XBT" }, { "asset": "XXLM", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XLM/XBT" }, { "asset": "XXMR", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XMR/XBT" }, { "asset": "XXMR", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XMR/EUR" }, { "asset": "XXMR", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XMR/USD" }, { "asset": "XXRP", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XRP/XBT" }, { "asset": "XXRP", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XRP/EUR" }, { "asset": "XXRP", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "XRP/USD" }, { "asset": "XZEC", "currency": "XXBT", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ZEC/XBT" }, { "asset": "XZEC", "currency": "ZEUR", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ZEC/EUR" }, { "asset": "XZEC", "currency": "ZUSD", "min_size": "0.01", - "increment": "0.00000001", + "increment": "0.01", "label": "ZEC/USD" } ] \ No newline at end of file diff --git a/extensions/exchanges/kraken/update-products.sh b/extensions/exchanges/kraken/update-products.sh index 91adab8624..1c2598a96c 100755 --- a/extensions/exchanges/kraken/update-products.sh +++ b/extensions/exchanges/kraken/update-products.sh @@ -11,7 +11,7 @@ function addProduct(base, quote, altname) { asset: base, currency: quote, min_size: '0.01', - increment: '0.00000001', + increment: '0.01', label: getPair(base) + '/' + getPair(quote) }) } From f4a2ed2bf3a65e5ac7bd30c6434f6eee6a2d7d6d Mon Sep 17 00:00:00 2001 From: MT00x Date: Tue, 5 Dec 2017 09:12:08 +0000 Subject: [PATCH 2/4] Catching more recoverable connection errors in Kraken --- extensions/exchanges/kraken/exchange.js | 612 ++++++++++++------------ 1 file changed, 311 insertions(+), 301 deletions(-) diff --git a/extensions/exchanges/kraken/exchange.js b/extensions/exchanges/kraken/exchange.js index 64d82dcde1..04a5ab8b54 100644 --- a/extensions/exchanges/kraken/exchange.js +++ b/extensions/exchanges/kraken/exchange.js @@ -1,352 +1,362 @@ var KrakenClient = require('kraken-api'), - path = require('path'), - minimist = require('minimist'), - moment = require('moment'), - n = require('numbro'), - colors = require('colors') +path = require('path'), +minimist = require('minimist'), +moment = require('moment'), +n = require('numbro'), +colors = require('colors') module.exports = function container(get, set, clear) { - var c = get('conf') - var s = { - options: minimist(process.argv) - } - var so = s.options +var c = get('conf') +var s = { + options: minimist(process.argv) +} +var so = s.options - var public_client, authed_client - // var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|API:Rate limit exceeded|between Cloudflare and the origin web server)/) - var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|between Cloudflare and the origin web server)/) - var silencedRecoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT)/) +var public_client, authed_client +// var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|API:Rate limit exceeded|between Cloudflare and the origin web server)/) +var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|between Cloudflare and the origin web server|The web server reported a gateway time\-out|The web server reported a bad gateway|525\: SSL handshake failed)/) +var silencedRecoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT)/) - function publicClient() { - if (!public_client) { - public_client = new KrakenClient() - } - return public_client +function publicClient() { + if (!public_client) { + public_client = new KrakenClient() } + return public_client +} - function authedClient() { - if (!authed_client) { - if (!c.kraken || !c.kraken.key || c.kraken.key === 'YOUR-API-KEY') { - throw new Error('please configure your Kraken credentials in conf.js') - } - authed_client = new KrakenClient(c.kraken.key, c.kraken.secret) +function authedClient() { + if (!authed_client) { + if (!c.kraken || !c.kraken.key || c.kraken.key === 'YOUR-API-KEY') { + throw new Error('please configure your Kraken credentials in conf.js') } - return authed_client + authed_client = new KrakenClient(c.kraken.key, c.kraken.secret) } + return authed_client +} - function joinProduct(product_id) { - return product_id.split('-')[0] + product_id.split('-')[1] +function joinProduct(product_id) { + return product_id.split('-')[0] + product_id.split('-')[1] +} + +// This is to deal with a silly bug where kraken doesn't use a consistent definition for currency +// with certain assets they will mix the use of 'Z' and 'X' prefixes +function joinProductFormatted(product_id) { + var asset = product_id.split('-')[0] + var currency = product_id.split('-')[1] + + var assetsToFix = ['BCH', 'DASH', 'EOS', 'GNO'] + if (assetsToFix.indexOf(asset) >= 0 && currency.length > 3) { + currency = currency.substring(1) } + return asset + currency; +} - // This is to deal with a silly bug where kraken doesn't use a consistent definition for currency - // with certain assets they will mix the use of 'Z' and 'X' prefixes - function joinProductFormatted(product_id) { - var asset = product_id.split('-')[0] - var currency = product_id.split('-')[1] +function retry(method, args, error) { + if (error.message.match(/API:Rate limit exceeded/)) { + var timeout = 10000 + } else { + var timeout = 150 + } - var assetsToFix = ['BCH', 'DASH', 'EOS', 'GNO'] - if (assetsToFix.indexOf(asset) >= 0 && currency.length > 3) { - currency = currency.substring(1) + // silence common timeout errors + if (so.debug || !error.message.match(silencedRecoverableErrors)) { + if (error.message.match(/between Cloudflare and the origin web server/)) { + errorMsg = 'Connection between Cloudflare CDN and api.kraken.com failed' } - return asset + currency; + else if (error.message.match(/The web server reported a gateway time\-out/)) { + errorMsg = 'Web server Gateway time-out' + } + else if (error.message.match(/The web server reported a bad gateway/)) { + errorMsg = 'Web server bad Gateway' + } + else if (error.message.match(/525\: SSL handshake failed/)) { + errorMsg = 'SSL handshake failed' + } + else { + errorMsg = error + } + console.warn(('\nKraken API warning - unable to call ' + method + ' (' + errorMsg + '), retrying in ' + timeout / 1000 + 's').yellow) } + setTimeout(function () { + exchange[method].apply(exchange, args) + }, timeout) +} - function retry(method, args, error) { - if (error.message.match(/API:Rate limit exceeded/)) { - var timeout = 10000 - } else { - var timeout = 150 +var orders = {} + +var exchange = { + name: 'kraken', + historyScan: 'forward', + makerFee: 0.16, + takerFee: 0.26, + // The limit for the public API is not documented, 1750 ms between getTrades in backfilling seems to do the trick to omit warning messages. + backfillRateLimit: 3500, + + getProducts: function () { + return require('./products.json') + }, + + getTrades: function (opts, cb) { + var func_args = [].slice.call(arguments) + var client = publicClient() + var args = { + pair: joinProductFormatted(opts.product_id) } - - // silence common timeout errors - if (so.debug || !error.message.match(silencedRecoverableErrors)) { - if (error.message.match(/between Cloudflare and the origin web server/)) { - errorMsg = 'Connection between Cloudflare CDN and api.kraken.com failed' - } else { - errorMsg = error - } - console.warn(('\nKraken API warning - unable to call ' + method + ' (' + errorMsg + '), retrying in ' + timeout / 1000 + 's').yellow) + if (opts.from) { + args.since = Number(opts.from) * 1000000 } - setTimeout(function() { - exchange[method].apply(exchange, args) - }, timeout) - } - var orders = {} - - var exchange = { - name: 'kraken', - historyScan: 'forward', - makerFee: 0.16, - takerFee: 0.26, - // The limit for the public API is not documented, 1750 ms between getTrades in backfilling seems to do the trick to omit warning messages. - backfillRateLimit: 3500, - - getProducts: function() { - return require('./products.json') - }, - - getTrades: function(opts, cb) { - var func_args = [].slice.call(arguments) - var client = publicClient() - var args = { - pair: joinProductFormatted(opts.product_id) + client.api('Trades', args, function (error, data) { + if (error && error.message.match(recoverableErrors)) { + return retry('getTrades', func_args, error) + } + if (error) { + console.error(('\nTrades error:').red) + console.error(error) + return cb(null, []) } - if (opts.from) { - args.since = Number(opts.from) * 1000000 + if (data.error.length) { + return cb(data.error.join(',')) } - client.api('Trades', args, function(error, data) { - if (error && error.message.match(recoverableErrors)) { - return retry('getTrades', func_args, error) - } - if (error) { - console.error(('\nTrades error:').red) - console.error(error) - return cb(null, []) + var trades = [] + Object.keys(data.result[args.pair]).forEach(function (i) { + var trade = data.result[args.pair][i] + if (!opts.from || (Number(opts.from) < moment.unix((trade[2]).valueOf()))) { + trades.push({ + trade_id: trade[2] + trade[1] + trade[0], + time: moment.unix(trade[2]).valueOf(), + size: parseFloat(trade[1]), + price: parseFloat(trade[0]), + side: trade[3] == 'b' ? 'buy' : 'sell' + }) } - if (data.error.length) { - return cb(data.error.join(',')) - } - - var trades = [] - Object.keys(data.result[args.pair]).forEach(function(i) { - var trade = data.result[args.pair][i] - if (!opts.from || (Number(opts.from) < moment.unix((trade[2]).valueOf()))) { - trades.push({ - trade_id: trade[2] + trade[1] + trade[0], - time: moment.unix(trade[2]).valueOf(), - size: parseFloat(trade[1]), - price: parseFloat(trade[0]), - side: trade[3] == 'b' ? 'buy' : 'sell' - }) - } - }) - - cb(null, trades) }) - }, - - getBalance: function(opts, cb) { - var args = [].slice.call(arguments) - var client = authedClient() - client.api('Balance', null, function(error, data) { - var balance = { - asset: '0', - asset_hold: '0', - currency: '0', - currency_hold: '0' - } - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('getBalance', args, error) - } - console.error(('\ngetBalance error:').red) - console.error(error) - return cb(error) - } + cb(null, trades) + }) + }, + + getBalance: function (opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + client.api('Balance', null, function (error, data) { + var balance = { + asset: '0', + asset_hold: '0', + currency: '0', + currency_hold: '0' + } - if (data.error.length) { - return cb(data.error.join(',')) + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('getBalance', args, error) } + console.error(('\ngetBalance error:').red) + console.error(error) + return cb(error) + } - if (data.result[opts.currency]) { - balance.currency = n(data.result[opts.currency]).format('0.00000000') - balance.currency_hold = '0' - } + if (data.error.length) { + return cb(data.error.join(',')) + } - if (data.result[opts.asset]) { - balance.asset = n(data.result[opts.asset]).format('0.00000000') - balance.asset_hold = '0' - } + if (data.result[opts.currency]) { + balance.currency = n(data.result[opts.currency]).format('0.00000000') + balance.currency_hold = '0' + } - cb(null, balance) - }) - }, - - getQuote: function(opts, cb) { - var args = [].slice.call(arguments) - var client = publicClient() - var pair = joinProductFormatted(opts.product_id) - client.api('Ticker', { - pair: pair - }, function(error, data) { - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('getQuote', args, error) - } - console.error(('\ngetQuote error:').red) - console.error(error) - return cb(error) - } - if (data.error.length) { - return cb(data.error.join(',')) + if (data.result[opts.asset]) { + balance.asset = n(data.result[opts.asset]).format('0.00000000') + balance.asset_hold = '0' + } + + cb(null, balance) + }) + }, + + getQuote: function (opts, cb) { + var args = [].slice.call(arguments) + var client = publicClient() + var pair = joinProductFormatted(opts.product_id) + client.api('Ticker', { + pair: pair + }, function (error, data) { + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('getQuote', args, error) } - cb(null, { - bid: data.result[pair].b[0], - ask: data.result[pair].a[0], - }) + console.error(('\ngetQuote error:').red) + console.error(error) + return cb(error) + } + if (data.error.length) { + return cb(data.error.join(',')) + } + cb(null, { + bid: data.result[pair].b[0], + ask: data.result[pair].a[0], }) - }, - - cancelOrder: function(opts, cb) { - var args = [].slice.call(arguments) - var client = authedClient() - client.api('CancelOrder', { - txid: opts.order_id - }, function(error, data) { - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('cancelOrder', args, error) - } - console.error(('\ncancelOrder error:').red) - console.error(error) - return cb(error) - } - if (data.error.length) { - return cb(data.error.join(',')) + }) + }, + + cancelOrder: function (opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + client.api('CancelOrder', { + txid: opts.order_id + }, function (error, data) { + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('cancelOrder', args, error) } - if (so.debug) { - console.log("cancelOrder") - console.log(data) - } - cb(error) - }) - }, - - trade: function(type, opts, cb) { - var args = [].slice.call(arguments) - var client = authedClient() - var params = { - pair: joinProductFormatted(opts.product_id), - type: type, - ordertype: (opts.order_type === 'taker' ? 'market' : 'limit'), - volume: opts.size, - trading_agreement: c.kraken.tosagree + console.error(('\ncancelOrder error:').red) + console.error(error) + return cb(error) } - if (opts.post_only === true && params.ordertype === 'limit') { - params.oflags = 'post' - } - if ('price' in opts) { - params.price = opts.price + if (data.error.length) { + return cb(data.error.join(',')) } if (so.debug) { - console.log("trade") - console.log(params) + console.log("cancelOrder") + console.log(data) + } + cb(error) + }) + }, + + trade: function (type, opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + var params = { + pair: joinProductFormatted(opts.product_id), + type: type, + ordertype: (opts.order_type === 'taker' ? 'market' : 'limit'), + volume: opts.size, + trading_agreement: c.kraken.tosagree + } + if (opts.post_only === true && params.ordertype === 'limit') { + params.oflags = 'post' + } + if ('price' in opts) { + params.price = opts.price + } + if (so.debug) { + console.log("trade") + console.log(params) + } + client.api('AddOrder', params, function (error, data) { + if (error && error.message.match(recoverableErrors)) { + return retry('trade', args, error) } - client.api('AddOrder', params, function(error, data) { - if (error && error.message.match(recoverableErrors)) { - return retry('trade', args, error) - } - - var order = { - id: data && data.result ? data.result.txid[0] : null, - status: 'open', - price: opts.price, - size: opts.size, - created_at: new Date().getTime(), - filled_size: '0' - } - - if (opts.order_type === 'maker') { - order.post_only = !!opts.post_only - } - - if (so.debug) { - console.log("Data") - console.log(data) - console.log("Order") - console.log(order) - console.log("Error") - console.log(error) - } - if (error) { - if (error.message.match(/Order:Insufficient funds$/)) { - order.status = 'rejected' - order.reject_reason = 'balance' - return cb(null, order) - } else if (error.message.length) { - console.error(('\nUnhandeld AddOrder error:').red) - console.error(error) - order.status = 'rejected' - order.reject_reason = error.message - return cb(null, order) - } else if (data.error.length) { - console.error(('\nUnhandeld AddOrder error:').red) - console.error(data.error) - order.status = 'rejected' - order.reject_reason = data.error.join(',') - } - } + var order = { + id: data && data.result ? data.result.txid[0] : null, + status: 'open', + price: opts.price, + size: opts.size, + created_at: new Date().getTime(), + filled_size: '0' + } - orders['~' + data.result.txid[0]] = order - cb(null, order) - }) - }, - - buy: function(opts, cb) { - exchange.trade('buy', opts, cb) - }, - - sell: function(opts, cb) { - exchange.trade('sell', opts, cb) - }, - - getOrder: function(opts, cb) { - var args = [].slice.call(arguments) - var order = orders['~' + opts.order_id] - if (!order) return cb(new Error('order not found in cache')) - var client = authedClient() - var params = { - txid: opts.order_id + if (opts.order_type === 'maker') { + order.post_only = !!opts.post_only } - client.api('QueryOrders', params, function(error, data) { - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('getOrder', args, error) - } - console.error(('\ngetOrder error:').red) - console.error(error) - return cb(error) - } - if (data.error.length) { - return cb(data.error.join(',')) - } - var orderData = data.result[params.txid] - if (so.debug) { - console.log("QueryOrders") - console.log(orderData) - } - if (!orderData) { - return cb('Order not found') - } + if (so.debug) { + console.log("Data") + console.log(data) + console.log("Order") + console.log(order) + console.log("Error") + console.log(error) + } - if (orderData.status === 'canceled' && orderData.reason === 'Post only order') { + if (error) { + if (error.message.match(/Order:Insufficient funds$/)) { + order.status = 'rejected' + order.reject_reason = 'balance' + return cb(null, order) + } else if (error.message.length) { + console.error(('\nUnhandeld AddOrder error:').red) + console.error(error) order.status = 'rejected' - order.reject_reason = 'post only' - order.done_at = new Date().getTime() - order.filled_size = '0.00000000' + order.reject_reason = error.message return cb(null, order) + } else if (data.error.length) { + console.error(('\nUnhandeld AddOrder error:').red) + console.error(data.error) + order.status = 'rejected' + order.reject_reason = data.error.join(',') } + } - if (orderData.status === 'closed' || (orderData.status === 'canceled' && orderData.reason === 'User canceled')) { - order.status = 'done' - order.done_at = new Date().getTime() - order.filled_size = n(orderData.vol_exec).format('0.00000000') - return cb(null, order) + orders['~' + data.result.txid[0]] = order + cb(null, order) + }) + }, + + buy: function (opts, cb) { + exchange.trade('buy', opts, cb) + }, + + sell: function (opts, cb) { + exchange.trade('sell', opts, cb) + }, + + getOrder: function (opts, cb) { + var args = [].slice.call(arguments) + var order = orders['~' + opts.order_id] + if (!order) return cb(new Error('order not found in cache')) + var client = authedClient() + var params = { + txid: opts.order_id + } + client.api('QueryOrders', params, function (error, data) { + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('getOrder', args, error) } + console.error(('\ngetOrder error:').red) + console.error(error) + return cb(error) + } + if (data.error.length) { + return cb(data.error.join(',')) + } + var orderData = data.result[params.txid] + if (so.debug) { + console.log("QueryOrders") + console.log(orderData) + } - cb(null, order) - }) - }, + if (!orderData) { + return cb('Order not found') + } - // return the property used for range querying. - getCursor: function(trade) { - return (trade.time || trade) - } + if (orderData.status === 'canceled' && orderData.reason === 'Post only order') { + order.status = 'rejected' + order.reject_reason = 'post only' + order.done_at = new Date().getTime() + order.filled_size = '0.00000000' + return cb(null, order) + } + + if (orderData.status === 'closed' || (orderData.status === 'canceled' && orderData.reason === 'User canceled')) { + order.status = 'done' + order.done_at = new Date().getTime() + order.filled_size = n(orderData.vol_exec).format('0.00000000') + return cb(null, order) + } + + cb(null, order) + }) + }, + + // return the property used for range querying. + getCursor: function (trade) { + return (trade.time || trade) } - return exchange +} +return exchange } From e09d5b3d8ea5cd978b9b0fcb56e89ddc77d3050d Mon Sep 17 00:00:00 2001 From: MT00x Date: Tue, 5 Dec 2017 16:20:55 +0000 Subject: [PATCH 3/4] Fix indenting -Issue #800 : Fix QueryOrders price --- extensions/exchanges/kraken/exchange.js | 725 ++++++++++++------------ 1 file changed, 363 insertions(+), 362 deletions(-) diff --git a/extensions/exchanges/kraken/exchange.js b/extensions/exchanges/kraken/exchange.js index 04a5ab8b54..1500de08e5 100644 --- a/extensions/exchanges/kraken/exchange.js +++ b/extensions/exchanges/kraken/exchange.js @@ -1,362 +1,363 @@ -var KrakenClient = require('kraken-api'), -path = require('path'), -minimist = require('minimist'), -moment = require('moment'), -n = require('numbro'), -colors = require('colors') - -module.exports = function container(get, set, clear) { -var c = get('conf') -var s = { - options: minimist(process.argv) -} -var so = s.options - -var public_client, authed_client -// var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|API:Rate limit exceeded|between Cloudflare and the origin web server)/) -var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|between Cloudflare and the origin web server|The web server reported a gateway time\-out|The web server reported a bad gateway|525\: SSL handshake failed)/) -var silencedRecoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT)/) - -function publicClient() { - if (!public_client) { - public_client = new KrakenClient() - } - return public_client -} - -function authedClient() { - if (!authed_client) { - if (!c.kraken || !c.kraken.key || c.kraken.key === 'YOUR-API-KEY') { - throw new Error('please configure your Kraken credentials in conf.js') - } - authed_client = new KrakenClient(c.kraken.key, c.kraken.secret) - } - return authed_client -} - -function joinProduct(product_id) { - return product_id.split('-')[0] + product_id.split('-')[1] -} - -// This is to deal with a silly bug where kraken doesn't use a consistent definition for currency -// with certain assets they will mix the use of 'Z' and 'X' prefixes -function joinProductFormatted(product_id) { - var asset = product_id.split('-')[0] - var currency = product_id.split('-')[1] - - var assetsToFix = ['BCH', 'DASH', 'EOS', 'GNO'] - if (assetsToFix.indexOf(asset) >= 0 && currency.length > 3) { - currency = currency.substring(1) - } - return asset + currency; -} - -function retry(method, args, error) { - if (error.message.match(/API:Rate limit exceeded/)) { - var timeout = 10000 - } else { - var timeout = 150 - } - - // silence common timeout errors - if (so.debug || !error.message.match(silencedRecoverableErrors)) { - if (error.message.match(/between Cloudflare and the origin web server/)) { - errorMsg = 'Connection between Cloudflare CDN and api.kraken.com failed' - } - else if (error.message.match(/The web server reported a gateway time\-out/)) { - errorMsg = 'Web server Gateway time-out' - } - else if (error.message.match(/The web server reported a bad gateway/)) { - errorMsg = 'Web server bad Gateway' - } - else if (error.message.match(/525\: SSL handshake failed/)) { - errorMsg = 'SSL handshake failed' - } - else { - errorMsg = error - } - console.warn(('\nKraken API warning - unable to call ' + method + ' (' + errorMsg + '), retrying in ' + timeout / 1000 + 's').yellow) - } - setTimeout(function () { - exchange[method].apply(exchange, args) - }, timeout) -} - -var orders = {} - -var exchange = { - name: 'kraken', - historyScan: 'forward', - makerFee: 0.16, - takerFee: 0.26, - // The limit for the public API is not documented, 1750 ms between getTrades in backfilling seems to do the trick to omit warning messages. - backfillRateLimit: 3500, - - getProducts: function () { - return require('./products.json') - }, - - getTrades: function (opts, cb) { - var func_args = [].slice.call(arguments) - var client = publicClient() - var args = { - pair: joinProductFormatted(opts.product_id) - } - if (opts.from) { - args.since = Number(opts.from) * 1000000 - } - - client.api('Trades', args, function (error, data) { - if (error && error.message.match(recoverableErrors)) { - return retry('getTrades', func_args, error) - } - if (error) { - console.error(('\nTrades error:').red) - console.error(error) - return cb(null, []) - } - if (data.error.length) { - return cb(data.error.join(',')) - } - - var trades = [] - Object.keys(data.result[args.pair]).forEach(function (i) { - var trade = data.result[args.pair][i] - if (!opts.from || (Number(opts.from) < moment.unix((trade[2]).valueOf()))) { - trades.push({ - trade_id: trade[2] + trade[1] + trade[0], - time: moment.unix(trade[2]).valueOf(), - size: parseFloat(trade[1]), - price: parseFloat(trade[0]), - side: trade[3] == 'b' ? 'buy' : 'sell' - }) - } - }) - - cb(null, trades) - }) - }, - - getBalance: function (opts, cb) { - var args = [].slice.call(arguments) - var client = authedClient() - client.api('Balance', null, function (error, data) { - var balance = { - asset: '0', - asset_hold: '0', - currency: '0', - currency_hold: '0' - } - - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('getBalance', args, error) - } - console.error(('\ngetBalance error:').red) - console.error(error) - return cb(error) - } - - if (data.error.length) { - return cb(data.error.join(',')) - } - - if (data.result[opts.currency]) { - balance.currency = n(data.result[opts.currency]).format('0.00000000') - balance.currency_hold = '0' - } - - if (data.result[opts.asset]) { - balance.asset = n(data.result[opts.asset]).format('0.00000000') - balance.asset_hold = '0' - } - - cb(null, balance) - }) - }, - - getQuote: function (opts, cb) { - var args = [].slice.call(arguments) - var client = publicClient() - var pair = joinProductFormatted(opts.product_id) - client.api('Ticker', { - pair: pair - }, function (error, data) { - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('getQuote', args, error) - } - console.error(('\ngetQuote error:').red) - console.error(error) - return cb(error) - } - if (data.error.length) { - return cb(data.error.join(',')) - } - cb(null, { - bid: data.result[pair].b[0], - ask: data.result[pair].a[0], - }) - }) - }, - - cancelOrder: function (opts, cb) { - var args = [].slice.call(arguments) - var client = authedClient() - client.api('CancelOrder', { - txid: opts.order_id - }, function (error, data) { - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('cancelOrder', args, error) - } - console.error(('\ncancelOrder error:').red) - console.error(error) - return cb(error) - } - if (data.error.length) { - return cb(data.error.join(',')) - } - if (so.debug) { - console.log("cancelOrder") - console.log(data) - } - cb(error) - }) - }, - - trade: function (type, opts, cb) { - var args = [].slice.call(arguments) - var client = authedClient() - var params = { - pair: joinProductFormatted(opts.product_id), - type: type, - ordertype: (opts.order_type === 'taker' ? 'market' : 'limit'), - volume: opts.size, - trading_agreement: c.kraken.tosagree - } - if (opts.post_only === true && params.ordertype === 'limit') { - params.oflags = 'post' - } - if ('price' in opts) { - params.price = opts.price - } - if (so.debug) { - console.log("trade") - console.log(params) - } - client.api('AddOrder', params, function (error, data) { - if (error && error.message.match(recoverableErrors)) { - return retry('trade', args, error) - } - - var order = { - id: data && data.result ? data.result.txid[0] : null, - status: 'open', - price: opts.price, - size: opts.size, - created_at: new Date().getTime(), - filled_size: '0' - } - - if (opts.order_type === 'maker') { - order.post_only = !!opts.post_only - } - - if (so.debug) { - console.log("Data") - console.log(data) - console.log("Order") - console.log(order) - console.log("Error") - console.log(error) - } - - if (error) { - if (error.message.match(/Order:Insufficient funds$/)) { - order.status = 'rejected' - order.reject_reason = 'balance' - return cb(null, order) - } else if (error.message.length) { - console.error(('\nUnhandeld AddOrder error:').red) - console.error(error) - order.status = 'rejected' - order.reject_reason = error.message - return cb(null, order) - } else if (data.error.length) { - console.error(('\nUnhandeld AddOrder error:').red) - console.error(data.error) - order.status = 'rejected' - order.reject_reason = data.error.join(',') - } - } - - orders['~' + data.result.txid[0]] = order - cb(null, order) - }) - }, - - buy: function (opts, cb) { - exchange.trade('buy', opts, cb) - }, - - sell: function (opts, cb) { - exchange.trade('sell', opts, cb) - }, - - getOrder: function (opts, cb) { - var args = [].slice.call(arguments) - var order = orders['~' + opts.order_id] - if (!order) return cb(new Error('order not found in cache')) - var client = authedClient() - var params = { - txid: opts.order_id - } - client.api('QueryOrders', params, function (error, data) { - if (error) { - if (error.message.match(recoverableErrors)) { - return retry('getOrder', args, error) - } - console.error(('\ngetOrder error:').red) - console.error(error) - return cb(error) - } - if (data.error.length) { - return cb(data.error.join(',')) - } - var orderData = data.result[params.txid] - if (so.debug) { - console.log("QueryOrders") - console.log(orderData) - } - - if (!orderData) { - return cb('Order not found') - } - - if (orderData.status === 'canceled' && orderData.reason === 'Post only order') { - order.status = 'rejected' - order.reject_reason = 'post only' - order.done_at = new Date().getTime() - order.filled_size = '0.00000000' - return cb(null, order) - } - - if (orderData.status === 'closed' || (orderData.status === 'canceled' && orderData.reason === 'User canceled')) { - order.status = 'done' - order.done_at = new Date().getTime() - order.filled_size = n(orderData.vol_exec).format('0.00000000') - return cb(null, order) - } - - cb(null, order) - }) - }, - - // return the property used for range querying. - getCursor: function (trade) { - return (trade.time || trade) - } -} -return exchange -} +var KrakenClient = require('kraken-api'), + path = require('path'), + minimist = require('minimist'), + moment = require('moment'), + n = require('numbro'), + colors = require('colors') + +module.exports = function container(get, set, clear) { + var c = get('conf') + var s = { + options: minimist(process.argv) + } + var so = s.options + + var public_client, authed_client + // var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|API:Rate limit exceeded|between Cloudflare and the origin web server)/) + var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|between Cloudflare and the origin web server|The web server reported a gateway time\-out|The web server reported a bad gateway|525\: SSL handshake failed)/) + var silencedRecoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT)/) + + function publicClient() { + if (!public_client) { + public_client = new KrakenClient() + } + return public_client + } + + function authedClient() { + if (!authed_client) { + if (!c.kraken || !c.kraken.key || c.kraken.key === 'YOUR-API-KEY') { + throw new Error('please configure your Kraken credentials in conf.js') + } + authed_client = new KrakenClient(c.kraken.key, c.kraken.secret) + } + return authed_client + } + + function joinProduct(product_id) { + return product_id.split('-')[0] + product_id.split('-')[1] + } + + // This is to deal with a silly bug where kraken doesn't use a consistent definition for currency + // with certain assets they will mix the use of 'Z' and 'X' prefixes + function joinProductFormatted(product_id) { + var asset = product_id.split('-')[0] + var currency = product_id.split('-')[1] + + var assetsToFix = ['BCH', 'DASH', 'EOS', 'GNO'] + if (assetsToFix.indexOf(asset) >= 0 && currency.length > 3) { + currency = currency.substring(1) + } + return asset + currency; + } + + function retry(method, args, error) { + if (error.message.match(/API:Rate limit exceeded/)) { + var timeout = 10000 + } else { + var timeout = 150 + } + + // silence common timeout errors + if (so.debug || !error.message.match(silencedRecoverableErrors)) { + if (error.message.match(/between Cloudflare and the origin web server/)) { + errorMsg = 'Connection between Cloudflare CDN and api.kraken.com failed' + } + else if (error.message.match(/The web server reported a gateway time\-out/)) { + errorMsg = 'Web server Gateway time-out' + } + else if (error.message.match(/The web server reported a bad gateway/)) { + errorMsg = 'Web server bad Gateway' + } + else if (error.message.match(/525\: SSL handshake failed/)) { + errorMsg = 'SSL handshake failed' + } + else { + errorMsg = error + } + console.warn(('\nKraken API warning - unable to call ' + method + ' (' + errorMsg + '), retrying in ' + timeout / 1000 + 's').yellow) + } + setTimeout(function() { + exchange[method].apply(exchange, args) + }, timeout) + } + + var orders = {} + + var exchange = { + name: 'kraken', + historyScan: 'forward', + makerFee: 0.16, + takerFee: 0.26, + // The limit for the public API is not documented, 1750 ms between getTrades in backfilling seems to do the trick to omit warning messages. + backfillRateLimit: 3500, + + getProducts: function() { + return require('./products.json') + }, + + getTrades: function(opts, cb) { + var func_args = [].slice.call(arguments) + var client = publicClient() + var args = { + pair: joinProductFormatted(opts.product_id) + } + if (opts.from) { + args.since = Number(opts.from) * 1000000 + } + + client.api('Trades', args, function(error, data) { + if (error && error.message.match(recoverableErrors)) { + return retry('getTrades', func_args, error) + } + if (error) { + console.error(('\nTrades error:').red) + console.error(error) + return cb(null, []) + } + if (data.error.length) { + return cb(data.error.join(',')) + } + + var trades = [] + Object.keys(data.result[args.pair]).forEach(function(i) { + var trade = data.result[args.pair][i] + if (!opts.from || (Number(opts.from) < moment.unix((trade[2]).valueOf()))) { + trades.push({ + trade_id: trade[2] + trade[1] + trade[0], + time: moment.unix(trade[2]).valueOf(), + size: parseFloat(trade[1]), + price: parseFloat(trade[0]), + side: trade[3] == 'b' ? 'buy' : 'sell' + }) + } + }) + + cb(null, trades) + }) + }, + + getBalance: function(opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + client.api('Balance', null, function(error, data) { + var balance = { + asset: '0', + asset_hold: '0', + currency: '0', + currency_hold: '0' + } + + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('getBalance', args, error) + } + console.error(('\ngetBalance error:').red) + console.error(error) + return cb(error) + } + + if (data.error.length) { + return cb(data.error.join(',')) + } + + if (data.result[opts.currency]) { + balance.currency = n(data.result[opts.currency]).format('0.00000000') + balance.currency_hold = '0' + } + + if (data.result[opts.asset]) { + balance.asset = n(data.result[opts.asset]).format('0.00000000') + balance.asset_hold = '0' + } + + cb(null, balance) + }) + }, + + getQuote: function(opts, cb) { + var args = [].slice.call(arguments) + var client = publicClient() + var pair = joinProductFormatted(opts.product_id) + client.api('Ticker', { + pair: pair + }, function(error, data) { + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('getQuote', args, error) + } + console.error(('\ngetQuote error:').red) + console.error(error) + return cb(error) + } + if (data.error.length) { + return cb(data.error.join(',')) + } + cb(null, { + bid: data.result[pair].b[0], + ask: data.result[pair].a[0], + }) + }) + }, + + cancelOrder: function(opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + client.api('CancelOrder', { + txid: opts.order_id + }, function(error, data) { + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('cancelOrder', args, error) + } + console.error(('\ncancelOrder error:').red) + console.error(error) + return cb(error) + } + if (data.error.length) { + return cb(data.error.join(',')) + } + if (so.debug) { + console.log("cancelOrder") + console.log(data) + } + cb(error) + }) + }, + + trade: function(type, opts, cb) { + var args = [].slice.call(arguments) + var client = authedClient() + var params = { + pair: joinProductFormatted(opts.product_id), + type: type, + ordertype: (opts.order_type === 'taker' ? 'market' : 'limit'), + volume: opts.size, + trading_agreement: c.kraken.tosagree + } + if (opts.post_only === true && params.ordertype === 'limit') { + params.oflags = 'post' + } + if ('price' in opts) { + params.price = opts.price + } + if (so.debug) { + console.log("trade") + console.log(params) + } + client.api('AddOrder', params, function(error, data) { + if (error && error.message.match(recoverableErrors)) { + return retry('trade', args, error) + } + + var order = { + id: data && data.result ? data.result.txid[0] : null, + status: 'open', + price: opts.price, + size: opts.size, + created_at: new Date().getTime(), + filled_size: '0' + } + + if (opts.order_type === 'maker') { + order.post_only = !!opts.post_only + } + + if (so.debug) { + console.log("Data") + console.log(data) + console.log("Order") + console.log(order) + console.log("Error") + console.log(error) + } + + if (error) { + if (error.message.match(/Order:Insufficient funds$/)) { + order.status = 'rejected' + order.reject_reason = 'balance' + return cb(null, order) + } else if (error.message.length) { + console.error(('\nUnhandeld AddOrder error:').red) + console.error(error) + order.status = 'rejected' + order.reject_reason = error.message + return cb(null, order) + } else if (data.error.length) { + console.error(('\nUnhandeld AddOrder error:').red) + console.error(data.error) + order.status = 'rejected' + order.reject_reason = data.error.join(',') + } + } + + orders['~' + data.result.txid[0]] = order + cb(null, order) + }) + }, + + buy: function(opts, cb) { + exchange.trade('buy', opts, cb) + }, + + sell: function(opts, cb) { + exchange.trade('sell', opts, cb) + }, + + getOrder: function(opts, cb) { + var args = [].slice.call(arguments) + var order = orders['~' + opts.order_id] + if (!order) return cb(new Error('order not found in cache')) + var client = authedClient() + var params = { + txid: opts.order_id + } + client.api('QueryOrders', params, function(error, data) { + if (error) { + if (error.message.match(recoverableErrors)) { + return retry('getOrder', args, error) + } + console.error(('\ngetOrder error:').red) + console.error(error) + return cb(error) + } + if (data.error.length) { + return cb(data.error.join(',')) + } + var orderData = data.result[params.txid] + if (so.debug) { + console.log("QueryOrders") + console.log(orderData) + } + + if (!orderData) { + return cb('Order not found') + } + + if (orderData.status === 'canceled' && orderData.reason === 'Post only order') { + order.status = 'rejected' + order.reject_reason = 'post only' + order.done_at = new Date().getTime() + order.filled_size = '0.00000000' + return cb(null, order) + } + + if (orderData.status === 'closed' || (orderData.status === 'canceled' && orderData.reason === 'User canceled')) { + order.status = 'done' + order.done_at = new Date().getTime() + order.filled_size = n(orderData.vol_exec).format('0.00000000') + order.price = n(orderData.price).format('0.00000000') + return cb(null, order) + } + + cb(null, order) + }) + }, + + // return the property used for range querying. + getCursor: function(trade) { + return (trade.time || trade) + } + } + return exchange +} \ No newline at end of file From 9d1364698632737b0e9c0580f608826691f2af1e Mon Sep 17 00:00:00 2001 From: MT00x Date: Tue, 5 Dec 2017 17:01:58 +0000 Subject: [PATCH 4/4] Fix indenting --- extensions/exchanges/kraken/exchange.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/exchanges/kraken/exchange.js b/extensions/exchanges/kraken/exchange.js index 1500de08e5..3e4f8988a0 100644 --- a/extensions/exchanges/kraken/exchange.js +++ b/extensions/exchanges/kraken/exchange.js @@ -12,10 +12,10 @@ module.exports = function container(get, set, clear) { } var so = s.options - var public_client, authed_client - // var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|API:Rate limit exceeded|between Cloudflare and the origin web server)/) - var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|between Cloudflare and the origin web server|The web server reported a gateway time\-out|The web server reported a bad gateway|525\: SSL handshake failed)/) - var silencedRecoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT)/) + var public_client, authed_client + // var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|API:Rate limit exceeded|between Cloudflare and the origin web server)/) + var recoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|API:Invalid nonce|between Cloudflare and the origin web server|The web server reported a gateway time\-out|The web server reported a bad gateway|525\: SSL handshake failed)/) + var silencedRecoverableErrors = new RegExp(/(ESOCKETTIMEDOUT|ETIMEDOUT)/) function publicClient() { if (!public_client) {