@tradle/cb-proxy
Version:
proxy for common-blockchain API, that temporarily caches calls. Currently supports Blockr
130 lines (105 loc) • 3.13 kB
JavaScript
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)
}