Browse Source

Follow xo style

pull/11/head
Luke Childs 8 years ago
parent
commit
3b568f7b2e
  1. 3
      controllers/error.js
  2. 2
      controllers/error404.js
  3. 9
      controllers/listing.js
  4. 8
      controllers/node.js
  5. 11
      index.js
  6. 16
      lib/bandwidth-chart.js
  7. 1
      lib/minify.js
  8. 32
      lib/nunjucks-filters.js
  9. 2
      lib/nunjucks-middleware.js
  10. 23
      lib/tor.js
  11. 105
      public/assets/enhancements.js
  12. 39
      public/sw.js

3
controllers/error.js

@ -1,6 +1,7 @@
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "next" }] */
module.exports = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const error = err.statusMessage || 'Something went wrong';
console.error(err);
res.status(statusCode).render('error.html', { error });
res.status(statusCode).render('error.html', {error});
};

2
controllers/error404.js

@ -1,5 +1,5 @@
module.exports = (req, res) => {
const statusCode = 404;
const error = '404 Not Found';
res.status(statusCode).render('error.html', { error });
res.status(statusCode).render('error.html', {error});
};

9
controllers/listing.js

@ -1,19 +1,18 @@
const tor = require('../lib/tor');
module.exports = (req, res, next) => {
let title = 'Top nodes by consensus weight';
const query = {
limit: 10
};
if(req.query.s) {
if (req.query.s) {
title = `Search results for "${req.query.s}":`;
query.search = req.query.s;
} else {
query.order = '-consensus_weight';
query.running = true;
}
if(req.query.p) {
if (req.query.p) {
query.offset = (query.limit * req.query.p) - query.limit;
}
@ -25,9 +24,9 @@ module.exports = (req, res, next) => {
numOfNodes: query.limit
}))
.catch(err => {
if(err.statusCode == 400 && req.query.s) {
if (err.statusCode === 400 && req.query.s) {
err.statusMessage = 'Bad Search Query';
}
next(err);
});
}
};

8
controllers/node.js

@ -8,7 +8,7 @@ module.exports = (req, res, next) => {
])
.then(data => {
// Throw 404 if node doesn't exist
if(!data[0]) {
if (!data[0]) {
const err = new Error('Node doesn\'t exist');
err.statusMessage = err.message;
err.statusCode = 404;
@ -19,12 +19,12 @@ module.exports = (req, res, next) => {
pageTitle: `${data[0].type}: ${data[0].nickname}`,
node: data[0],
bandwidth: bandwidthChart(data[1])
})
});
})
.catch(err => {
if(err.statusCode == 400) {
if (err.statusCode === 400) {
err.statusMessage = 'Invalid node';
}
next(err);
});
}
};

11
index.js

@ -1,16 +1,17 @@
const express = require('express');
const anonlytics = require('anonlytics-express');
const nunjucks = require('nunjucks');
const compression = require('compression');
const nunjucksFilters = require('./lib/nunjucks-filters');
const nunjucksMiddleware = require('./lib/nunjucks-middleware');
const compression = require('compression');
const minify = require('./lib/minify');
const controllers = require('./controllers');
const app = express();
const port = process.env.port || 3000;
// Trust proxy headers if we're deployed on now
if(process.env.NOW) {
if (process.env.NOW) {
app.enable('trust proxy');
}
@ -18,7 +19,7 @@ if(process.env.NOW) {
app.use(anonlytics());
// Setup nunjucks
nunjucks.configure('views', { express: app });
nunjucks.configure('views', {express: app});
nunjucksFilters(app);
app.use(nunjucksMiddleware);
@ -35,8 +36,8 @@ app.get('/about', controllers.about);
app.get('/no-connection', controllers.noConnection);
// Serve assets with cache headers
app.use('/sw.js', express.static(`${__dirname}/public/sw.js`, { maxAge: '1 hour' }));
app.use(express.static(`${__dirname}/public`, { maxAge: '1 year' }));
app.use('/sw.js', express.static(`${__dirname}/public/sw.js`, {maxAge: '1 hour'}));
app.use(express.static(`${__dirname}/public`, {maxAge: '1 year'}));
// Errors
app.use(controllers.error404);

16
lib/bandwidth-chart.js

@ -1,7 +1,6 @@
const chart = require('ascii-chart');
function pointsFromBandwidthData(values, numPoints) {
// Define vars
const len = values.length;
const points = [];
@ -9,10 +8,10 @@ function pointsFromBandwidthData(values, numPoints) {
let size;
// Split values into n points
if(numPoints < 2) {
if (numPoints < 2) {
points.push(values);
} else {
if(len % numPoints === 0) {
if (len % numPoints === 0) {
size = Math.floor(len / numPoints);
while (i < len) {
points.push(values.slice(i, i += size));
@ -28,14 +27,16 @@ function pointsFromBandwidthData(values, numPoints) {
return points
// Calculate average value of each point
.map(point => Math.round(point.reduce((a,b) => a + b) / point.length))
.map(point => Math.round(point.reduce((a, b) => a + b) / point.length))
// Convert bytes to megabytes
.map(bytes => Number((bytes / 1000000).toPrecision(3)));
}
module.exports = values => {
if(values && values.length) {
if (!values || values.length < 1) {
return '';
}
const points = pointsFromBandwidthData(values, 57);
return chart(points, {
width: 120,
@ -45,7 +46,4 @@ module.exports = values => {
negativePointChar: '.',
axisChar: '.'
});
} else {
return '';
}
}
};

1
lib/minify.js

@ -1,6 +1,7 @@
const minify = require('express-minify');
const minifyHTML = require('express-minify-html');
const CleanCSS = require('clean-css');
const cleanCSS = new CleanCSS();
module.exports = [

32
lib/nunjucks-filters.js

@ -1,6 +1,6 @@
const querystring = require('querystring');
const prettyBytes = require('pretty-bytes');
const moment = require('moment');
const querystring = require('querystring');
function humanTimeAgo(utcDate) {
const diff = moment.utc().diff(moment.utc(utcDate));
@ -18,7 +18,9 @@ function humanTimeAgo(utcDate) {
let readableUptime = '';
readableUptime += uptime.d ? ` ${uptime.d}d` : '';
readableUptime += uptime.h ? ` ${uptime.h}h` : '';
readableUptime += !uptime.d || !uptime.h && uptime.m ? ` ${uptime.m}m` : '';
if ((!uptime.d || !uptime.h) && uptime.m) {
readableUptime += ` ${uptime.m}m`;
}
return readableUptime.trim();
}
@ -26,9 +28,8 @@ function humanTimeAgo(utcDate) {
const filters = {
bandwidth: node => `${prettyBytes(node.advertised_bandwidth)}/s`,
uptime: node => {
// Check node is up
if(!node.running) {
if (!node.running) {
return 'Down';
}
@ -36,7 +37,6 @@ const filters = {
return humanTimeAgo(node.last_restarted);
},
pagination: (req, direction) => {
// Clone query string
const query = Object.assign({}, req.query);
@ -44,27 +44,35 @@ const filters = {
query.p = query.p ? query.p : 1;
// Update page
if(direction == 'next') {
if (direction === 'next') {
query.p++;
} else if(direction == 'prev') {
} else if (direction === 'prev') {
query.p--;
}
// Don't add p var if it's page 1
query.p == 1 && delete query.p
if (query.p === 1) {
delete query.p;
}
// Encode query string
const queryString = querystring.encode(query);
return queryString ? `/?${queryString}` : '/';
},
name: node => node.nickname
|| node.fingerprint && node.fingerprint.slice(0, 8)
|| node.hashed_fingerprint && node.hashed_fingerprint.slice(0, 8),
name: node => {
let name = '';
if (node.nickname) {
name = node.nickname;
} else if (node.fingerprint || node.hashed_fingerprint) {
name = (node.fingerprint || node.hashed_fingerprint).slice(0, 8);
}
return name;
},
status: node => node.running ?
`Up for ${humanTimeAgo(node.last_restarted)}` :
`Down for ${humanTimeAgo(node.last_seen)}`
};
module.exports = app => Object.keys(filters).forEach(filter => {
app.settings.nunjucksEnv.addFilter(filter, filters[filter])
app.settings.nunjucksEnv.addFilter(filter, filters[filter]);
});

2
lib/nunjucks-middleware.js

@ -2,4 +2,4 @@ module.exports = (req, res, next) => {
res.locals.req = req;
res.locals.res = res;
next();
}
};

23
lib/tor.js

@ -1,26 +1,32 @@
const Onionoo = require('onionoo');
const onionoo = new Onionoo();
const setNodeType = type => node => {
node.type = type;
return node;
};
module.exports = {
listNodes: query => {
return onionoo
.details(query)
.then(response => {
const details = response.body;
details.relays.forEach(node => node.type = 'relay');
details.bridges.forEach(node => node.type = 'bridge');
details.relays.map(setNodeType('relay'));
details.bridges.map(setNodeType('bridge'));
return details.relays.concat(details.bridges);
});
},
node: id => {
return onionoo
.details({ lookup: id })
.details({lookup: id})
.then(response => {
const details = response.body;
if(details.relays[0]) {
if (details.relays[0]) {
details.relays[0].type = 'relay';
return details.relays[0];
} else if(details.bridges[0]) {
} else if (details.bridges[0]) {
details.bridges[0].type = 'bridge';
return details.bridges[0];
}
@ -28,14 +34,13 @@ module.exports = {
},
bandwidth: id => {
return onionoo
.bandwidth({ lookup: id })
.bandwidth({lookup: id})
.then(response => {
const bandwidth = response.body;
try {
const lastMonth = bandwidth.relays[0].write_history['1_month'];
return lastMonth.values.map(value => value * lastMonth.factor)
}
catch(e) {
return lastMonth.values.map(value => value * lastMonth.factor);
} catch (err) {
return [];
}
});

105
public/assets/enhancements.js

@ -1,15 +1,14 @@
(function() {
/* eslint-env browser */
(function () {
// Space optimisations
var doc = document;
var find = doc.querySelector.bind(doc);
var create = doc.createElement.bind(doc);
// Run callback when DOM is ready
function DOMReady(cb) {
function domReady(cb) {
// Run now if DOM has already loaded as we're loading async
if(['interactive', 'complete'].indexOf(doc.readyState) >= 0) {
if (['interactive', 'complete'].indexOf(doc.readyState) >= 0) {
cb();
// Otherwise wait for DOM
@ -20,36 +19,36 @@
// Feature detection
var supports = {
test: function(features) {
test: function (features) {
var self = this;
if(!features || !features.length) {
if (!features || features.length < 1) {
return false;
}
return features.every(function(feature) {
return features.every(function (feature) {
return self.tests[feature];
});
},
tests: {
localStorage: (function() {
localStorage: (function () {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');
return true;
} catch (e) {
} catch (err) {
return false;
}
})(),
inlineSVG: (function() {
inlineSVG: (function () {
var div = create('div');
div.innerHTML = '<svg/>';
return (
typeof SVGRect != 'undefined'
&& div.firstChild
&& div.firstChild.namespaceURI
) == 'http://www.w3.org/2000/svg';
typeof SVGRect !== 'undefined' &&
div.firstChild &&
div.firstChild.namespaceURI
) === 'http://www.w3.org/2000/svg';
})(),
querySelector: typeof doc.querySelector === 'function',
classList: (function() {
classList: (function () {
var div = create('div');
div.innerHTML = '<svg/>';
return 'classList' in div.firstChild;
@ -71,33 +70,33 @@
activeClass: 'hearted',
// Gets current node hash
getCurrentNode: function() {
getCurrentNode: function () {
var node = /^\/node\/([a-zA-Z0-9]+)/.exec(window.location.pathname);
return node ? node[1] : node;
},
// Gets current node title
getCurrentNodeTitle: function() {
getCurrentNodeTitle: function () {
return find('h2.node-title .name').innerText;
},
// Gets hearted nodes
getHeartedNodes: function() {
getHeartedNodes: function () {
return JSON.parse(localStorage.getItem(favouriteNodes.storageKey)) || {};
},
// Saves hearted nodes
saveHeartedNodes: function(heartedNodes) {
saveHeartedNodes: function (heartedNodes) {
return localStorage.setItem(favouriteNodes.storageKey, JSON.stringify(heartedNodes));
},
// Checks if node is hearted
isHearted: function(node) {
isHearted: function (node) {
return typeof favouriteNodes.getHeartedNodes()[node] !== 'undefined';
},
// Heart node
heart: function(node) {
heart: function (node) {
var heartedNodes = favouriteNodes.getHeartedNodes();
heartedNodes[node] = favouriteNodes.getCurrentNodeTitle();
favouriteNodes.saveHeartedNodes(heartedNodes);
@ -106,7 +105,7 @@
},
// Unheart node
unHeart: function(node) {
unHeart: function (node) {
var heartedNodes = favouriteNodes.getHeartedNodes();
delete heartedNodes[node];
favouriteNodes.saveHeartedNodes(heartedNodes);
@ -115,44 +114,42 @@
},
// Get list of hearted nodes
updateHeartedNodesList: function() {
updateHeartedNodesList: function () {
var menu = find('.menu');
if(!menu) {
if (!menu) {
return false;
}
var menuHTML = '';
var heartedNodes = favouriteNodes.getHeartedNodes();
var nodeHashes = Object.keys(heartedNodes);
if(nodeHashes.length) {
if (nodeHashes.length > 0) {
menuHTML += '<ul>';
nodeHashes.forEach(function(node) {
nodeHashes.forEach(function (node) {
menuHTML += '<li><a href="/node/' + node + '">' + heartedNodes[node] + '</a></li>';
});
menuHTML += '</ul>';
} else {
menuHTML += '<div class="empty">Click the heart next to a node\'s title on it\'s own page to save it here for easy access :)</div>';
}
return menu.innerHTML = menuHTML;
menu.innerHTML = menuHTML;
return menu.innerHTML;
},
// Load SVG, run callback when loaded
loadSVG: function(cb) {
loadSVG: function (cb) {
// Get heart SVG
var xhr = new XMLHttpRequest();
xhr.open('GET', favouriteNodes.heartPath);
xhr.addEventListener('load', function() {
xhr.addEventListener('load', function () {
cb(xhr.responseText);
});
xhr.send();
},
// Initiate node favouriting
init: function() {
init: function () {
// Start loading heart SVG before DOM
favouriteNodes.loadSVG(function(svg) {
favouriteNodes.loadSVG(function (svg) {
// Create heart SVG elem
var div = create('div');
div.innerHTML = svg;
@ -160,16 +157,15 @@
// Show heart as active if we've already hearted this node
var node = favouriteNodes.getCurrentNode();
if(favouriteNodes.isHearted(node)) {
if (favouriteNodes.isHearted(node)) {
heartEl.classList.add(favouriteNodes.activeClass);
}
// Add click handler
heartEl.addEventListener('click', function() {
heartEl.addEventListener('click', function () {
// Heart/unheart node
var node = favouriteNodes.getCurrentNode();
if(favouriteNodes.isHearted(node)) {
if (favouriteNodes.isHearted(node)) {
heartEl.classList.remove(favouriteNodes.activeClass);
favouriteNodes.unHeart(node);
} else {
@ -179,13 +175,13 @@
});
// Then inject into DOM when it's ready
DOMReady(function() {
domReady(function () {
var headerHeight = find('.title').offsetHeight;
var headerBoxShadow = 3;
// Heart
var titleEl = find('h2.node-title');
if(titleEl) {
if (titleEl) {
titleEl.insertBefore(heartEl, titleEl.firstChild);
}
@ -194,7 +190,7 @@
menuButton.classList.add('menu-button');
menuButton.style.height = headerHeight + 'px';
menuButton.innerHTML = svg;
menuButton.addEventListener('click', function() {
menuButton.addEventListener('click', function () {
favouriteNodes.updateHeartedNodesList();
find('.menu').classList.toggle('active');
});
@ -212,8 +208,7 @@
// If current node is hearted
var node = favouriteNodes.getCurrentNode();
if(favouriteNodes.isHearted(node)) {
if (favouriteNodes.isHearted(node)) {
// Heart it again so we get the new name if it's updated
favouriteNodes.heart(node);
}
@ -221,19 +216,18 @@
};
// Service worker
if(supports.test(['serviceWorker', 'querySelector', 'classList'])) {
if (supports.test(['serviceWorker', 'querySelector', 'classList'])) {
// Register service worker
navigator.serviceWorker.register('/sw.js');
// Show cache message on stale pages
DOMReady(function() {
if(window.cacheDate) {
domReady(function () {
if (window.cacheDate) {
var offlineMessage = create('div');
offlineMessage.classList.add('cache-message');
offlineMessage.innerText = '*There seems to be an issue connecting to the server. This data is cached from ' + window.cacheDate;
var main = find('main');
if(main) {
if (main) {
doc.body.classList.add('no-connection');
main.insertBefore(offlineMessage, main.firstChild);
}
@ -242,20 +236,19 @@
}
// Init favourite nodes
if(supports.test(['localStorage', 'inlineSVG', 'querySelector', 'classList'])) {
if (supports.test(['localStorage', 'inlineSVG', 'querySelector', 'classList'])) {
favouriteNodes.init();
}
// Add ios class to body on iOS devices
if(supports.test(['classList'])) {
DOMReady(function() {
if(
/iPad|iPhone|iPod/.test(navigator.userAgent)
&& !window.MSStream
if (supports.test(['classList'])) {
domReady(function () {
if (
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
!window.MSStream
) {
doc.body.classList.add('ios');
}
});
}
})();

39
public/sw.js

@ -1,13 +1,13 @@
/* eslint-env browser */
var cacheName = 'onionite-cache-v1';
var offlineUrl = '/no-connection';
// Install
self.addEventListener('install', function(event) {
self.addEventListener('install', function (event) {
// Cache assets
event.waitUntil(
caches.open(cacheName)
.then(function(cache) {
.then(function (cache) {
return cache.addAll([
offlineUrl,
'/',
@ -22,24 +22,22 @@ self.addEventListener('install', function(event) {
});
// Fetch
self.addEventListener('fetch', function(event) {
self.addEventListener('fetch', function (event) {
var requestUrl = new URL(event.request.url);
// Only do stuff with our own urls
if(requestUrl.origin !== location.origin) {
if (requestUrl.origin !== location.origin) {
return;
}
// Try cache first for assets
if(requestUrl.pathname.startsWith('/assets/')) {
if (requestUrl.pathname.startsWith('/assets/')) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
.then(function (response) {
// If we don't have it make the request
return response || fetch(event.request);
}
)
})
);
return;
}
@ -53,20 +51,17 @@ self.addEventListener('fetch', function(event) {
// Make the request
fetch(event.request)
.then(function(response) {
.then(function (response) {
// If it's the homepage or a node page
if(requestUrl.pathname === '/' || requestUrl.pathname.startsWith('/node/')) {
if (requestUrl.pathname === '/' || requestUrl.pathname.startsWith('/node/')) {
// Clone the response and read the html
response.clone().text().then(function(html) {
response.clone().text().then(function (html) {
// Modify the html so we know when it was cached
html = html.replace('window.cacheDate=false;', 'window.cacheDate="'+Date()+'";');
var modifiedResponse = new Response(new Blob([html]), { headers: response.headers });
html = html.replace('window.cacheDate=false;', 'window.cacheDate="' + Date() + '";');
var modifiedResponse = new Response(new Blob([html]), {headers: response.headers});
// Cache the modified response
caches.open(cacheName).then(function(cache) {
caches.open(cacheName).then(function (cache) {
cache.put(event.request, modifiedResponse);
});
});
@ -77,12 +72,10 @@ self.addEventListener('fetch', function(event) {
})
// If it fails
.catch(function() {
.catch(function () {
// Try and return a previously cached version
return caches.match(event.request)
.then(function(response) {
.then(function (response) {
// If we don't have a cached version show pretty offline page
return response || caches.match(offlineUrl);
});

Loading…
Cancel
Save