UNPKG

@tradle/cb-proxy

Version:

proxy for common-blockchain API, that temporarily caches calls. Currently supports Blockr

130 lines (105 loc) 3.13 kB
var debug = require('debug')('cb-proxy') var jsend = require('jsend') var superagent = require('superagent') var typeforce = require('typeforce') var Cache = require('lru-cache') var Queue = require('./queue') var THROTTLE = 100 var SAVE_INTERVAL = 60000 var MAX_AGE = 300000 module.exports = function (opts) { typeforce({ blockchain: 'Object', path: 'String', throttle: '?Number', maxAge: '?Number' }, opts) var blockchain = opts.blockchain var cachePath = opts.path var maxAgeMillis = isNaN(opts.maxAge) ? MAX_AGE : opts.maxAge var cache = new Cache({ maxAge: maxAgeMillis / 1000 | 0 }) var savedCached = fs.readFileSync(cachePath) if (savedCached) { cache.load(JSON.parse(savedCached)) } setInterval(function () { fs.writeFile(cachePath, JSON.stringify(cache.dump())) }, SAVE_INTERVAL) var throttle = isNaN(opts.throttle) ? THROTTLE : opts.throttle var router = opts.router var queue = new Queue(throttle) var Transactions = blockchain.transactions var Addresses router.get('/', function (req, res) { var url = req.query.url.replace('http:', 'https:') if (!isCacheable(url)) { debug('forwarding', url) return queue.push(function (cb) { fetch(url, function (err, _res) { cb(err) if (err) return fail(res, err.message) res.send(_res.body) }) }) } var split = toUrls(url) var results = split.map(cache.get, cache) var missing = split .filter(function (r, i) { return !results[i] }) if (!missing.length) return success() debug('fetching', missing) queue.push(function (cb) { fetch(toUrl(missing), function (err, _res) { cb(err) if (err) return fail(res, err.message) var status = _res.body.status var data = _res.body.data if (status !== 'success') return fail(res, data) if (!Array.isArray(data)) data = [data] for (var i = 0, j = 0; i < results.length; i++) { if (!results[i]) { results[i] = data[j++] } } success() debug('saving', missing) data.forEach(function (item, i) { cache.set(missing[i], item) }) }) }) function success () { res.send(jsend.success(results.length === 1 ? results[0] : results)) } }) } function isCacheable (url) { var match = url.match(/([a-zA-Z]+)\.blockr.io\/api\/v\d+\/(block|tx)\/(info|raw)\/([^\?]+)$/) return match && match[4] && match[4].split(/\s+/).indexOf('last') === -1 } function fail (res, msg) { return res.send(jsend.fail({ error: msg })) } function getBase (url) { return url.slice(0, url.lastIndexOf('/')) } function toUrls (url) { var base = getBase(url) var ids = url.slice(base.length + 1).split(',') return ids.map(function (id) { return base + '/' + id }) } function toUrl (urls) { var base = getBase(urls[0]) return base + '/' + urls.map(function (url) { return url.slice(base.length + 1) }).join(',') } function fetch (url, cb) { return superagent.get(url).end(cb) }