You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

136 lines
3.8 KiB

const got = require('got')
const cacheManager = require('cache-manager')
const querystring = require('querystring')
const pkg = require('../package.json')
const addSeconds = require('date-fns/add_seconds')
const isFuture = require('date-fns/is_future')
const deepAssign = require('deep-assign')
class Onionoo {
// Constructor returns a new object so instance properties are private
constructor (options = {}) {
// Set default options
this.options = Object.assign({}, {
baseUrl: 'https://onionoo.torproject.org',
endpoints: [
'summary',
'details',
'bandwidth',
'weights',
'clients',
'uptime'
]
}, options)
if (options.cache !== false) {
this.options.cache = cacheManager.caching(Object.assign({}, {
store: 'memory',
max: 500
}, options.cache))
}
// Return object containing endpoint methods
return this.options.endpoints.reduce((onionoo, endpoint) => {
onionoo[endpoint] = this.createEndpointMethod(endpoint)
return onionoo
}, {})
}
// Returns expirey date from response headers
expires (headers) {
const originDate = new Date(headers.date)
// Get max age ms
let maxAge = headers['cache-control'] && headers['cache-control'].match(/max-age=(\d+)/)
maxAge = parseInt(maxAge ? maxAge[1] : 0)
// Calculate expirey date
return addSeconds(new Date(originDate), maxAge)
}
// Returns a function to make requests to a given endpoint
createEndpointMethod (endpoint) {
return options => {
// Build query string (don't encode ':' for search filters)
const qs = querystring.encode(options).replace(/%3A/g, ':')
// Build url
const url = `${this.options.baseUrl}/${endpoint}?${qs}`
// If caching is enabled, check for url in cache
if (this.options.cache) {
return this.options.cache.get(url)
.then(cachedResult => {
let options = {}
// If we have it cached
if (cachedResult) {
// Return the cached entry if it's still valid
if (isFuture(this.expires(cachedResult.headers))) {
return cachedResult
// If it's stale, add last-modified date to headers
} else {
options.headers = {
'if-modified-since': cachedResult.headers['last-modified']
}
}
}
// Make a request
return this.makeRequest(url, options)
.then(response => {
// If we get a 304, fill in the body
if (response.statusCode === 304) {
response.body = cachedResult.body
}
// If we get a 200 or 304, cache it
if ([200, 304].indexOf(response.statusCode) > -1) {
this.options.cache.set(url, response)
}
return response
})
})
// If caching is disabled, just make the request
} else {
return this.makeRequest(url)
}
}
}
// Returns a promise for a request
makeRequest (url, options = {}) {
options = deepAssign({
headers: {
'user-agent': `onionoo-node-client v${pkg.version} (${pkg.homepage})`
}
}, options)
return got(url, options)
.catch(error => {
// Don't throw 304 responses
if (error.statusCode === 304) {
return error.response
} else {
throw error
}
})
.then(response => {
// Format response
response = {
statusCode: response.statusCode,
statusMessage: response.statusMessage,
headers: response.headers,
body: response.body && JSON.parse(response.body)
}
return response
})
}
}
module.exports = Onionoo