meddelare-server
Advanced tools
Comparing version 1.3.1 to 1.4.0
@@ -1,26 +0,67 @@ | ||
var express = require("express"); | ||
var Promise = require("bluebird"); | ||
var request = require("request"); | ||
var cache = require("memory-cache"); | ||
"use strict"; | ||
// Cache results in memory -- but keep good and bad (error thrown) results for different periods of time. | ||
var LOCAL_CACHE_TIME_GOOD_RESULT = 4 * 60 * 1000, | ||
LOCAL_CACHE_TIME_BAD_RESULT = 1 * 60 * 1000, | ||
LOCAL_CACHE_TIME_TIMEOUT_RESULT = 10 * 1000; | ||
var express = require("express"), | ||
Promise = require("bluebird"), | ||
request = require("request"), | ||
// Return this count if none was found or an error was thrown. | ||
var DEFAULT_UNKNOWN_COUNT = -1; | ||
// The memory-cache is per-process and returns pointers, not copies. | ||
// Using process-scoped keys with a SocialButtonsServerMiddleware "namespace", so multiple SocialButtonsServerMiddleware servers share cache. | ||
// Using cache.clear() will clear all caches, even in other parts of the application. Be careful! | ||
cache = require("memory-cache"), | ||
extend = require("extend"), | ||
// How many minutes should we cache the results for a given request | ||
var CACHE_TIME = process.env.CACHE_TIME || 4 * 60; | ||
// TODO: use a package/library instead? | ||
copyDeep = function() { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
function cacheControl(req, res, next) { | ||
return extend.apply(null, [true, {}].concat(args)); | ||
}, | ||
// TODO: use a package/library instead? | ||
sortObjectByKeys = function(unsorted) { | ||
var sorted = {}, | ||
keys = Object.keys(unsorted); | ||
keys.sort(); | ||
keys.forEach(function(key) { | ||
sorted[key] = unsorted[key]; | ||
}); | ||
return sorted; | ||
}; | ||
function SocialButtonsServerMiddleware(options) { | ||
// TODO: better configuration. | ||
this._options = copyDeep(SocialButtonsServerMiddleware._defaultOptions, options); | ||
// Perhaps it's futile to try and clean up before/exit, but one can try. | ||
process | ||
.once("beforeExit", function() { | ||
cache.clear(); | ||
}) | ||
.once("exit", function() { | ||
cache.clear(); | ||
}); | ||
this._router = express.Router(this._options.routerOptions); | ||
this._router.get("/", this._cacheControl.bind(this)); | ||
this._router.get("/", this._getCount.bind(this)); | ||
this._router.use(SocialButtonsServerMiddleware._forbidden); | ||
return this; | ||
} | ||
SocialButtonsServerMiddleware.prototype._cacheControl = function(req, res, next) { | ||
// Setup caching headers (works well with cloudfront) | ||
res.set("Cache-Control", "max-age=" + CACHE_TIME); | ||
res.set("Cache-Control", "max-age=" + this._options.CACHE_TIME); | ||
next(); | ||
} | ||
}; | ||
var networkCallbacks = { | ||
SocialButtonsServerMiddleware.prototype._networkCallbacks = { | ||
twitter: function(url, callback) { | ||
@@ -103,39 +144,35 @@ // Twitter is nice and easy | ||
function sortObjectByKeys(unsorted) { | ||
var sorted = {}, | ||
keys = Object.keys(unsorted); | ||
SocialButtonsServerMiddleware.prototype._isValidNetwork = function(network) { | ||
return Object.prototype.hasOwnProperty.call(this._networkCallbacks, network); | ||
}; | ||
keys.sort(); | ||
SocialButtonsServerMiddleware.prototype._getInvalidNetworks = function(networks) { | ||
return networks.filter(function(network) { | ||
return !this._isValidNetwork(network); | ||
}, this); | ||
}; | ||
keys.forEach(function(key) { | ||
sorted[key] = unsorted[key]; | ||
}); | ||
SocialButtonsServerMiddleware.prototype._retrieveCount = function(url, network) { | ||
if (!this._isValidNetwork(network)) { | ||
throw new Error("Unknown network: " + network); | ||
} | ||
return sorted; | ||
} | ||
var self = this; | ||
function retrieveCount(url, network) { | ||
if (typeof networkCallbacks[network] === "undefined") { | ||
throw new Error("Unknown network"); | ||
} | ||
return Promise.promisify(networkCallbacks[network])(url) | ||
return Promise.promisify(self._networkCallbacks[network])(url) | ||
.catch(function(err) { | ||
console.error("Could not fetch count", network, url, err); | ||
self._options.logger.error("Could not fetch count", network, url, err); | ||
throw err; | ||
}); | ||
} | ||
}; | ||
function createCacheKey(url, network) { | ||
return network + " '" + url + "'"; | ||
} | ||
SocialButtonsServerMiddleware.prototype._getCachedOrRetrieveCount = function(url, network) { | ||
var self = this, | ||
cacheKey = SocialButtonsServerMiddleware._createCacheKey(url, network); | ||
function getCachedOrRetrieveCount(url, network) { | ||
var cacheKey = createCacheKey(url, network); | ||
return Promise.resolve(cache.get(cacheKey)) | ||
.then(function(cachedResult) { | ||
if (typeof cachedResult !== "undefined" && cachedResult !== null) { | ||
console.log(cacheKey, "from cache", cachedResult); | ||
self._options.logger.info(cacheKey, "from cache", cachedResult); | ||
@@ -148,24 +185,24 @@ return cachedResult; | ||
// for the same network/url cannot be sent at the same time. | ||
var retrieveCountPromise = retrieveCount(url, network) | ||
var retrieveCountPromise = self._retrieveCount(url, network) | ||
.tap(function(uncachedResult) { | ||
console.log(cacheKey, "fetched good result", uncachedResult); | ||
self._options.logger.info(cacheKey, "fetched good result", uncachedResult); | ||
cache.put(cacheKey, uncachedResult, LOCAL_CACHE_TIME_GOOD_RESULT); | ||
cache.put(cacheKey, uncachedResult, self._options.LOCAL_CACHE_TIME_GOOD_RESULT); | ||
}) | ||
.catch(function(err) { | ||
console.error(cacheKey, "fetched bad result", err); | ||
self._options.logger.error(cacheKey, "fetched bad result", err); | ||
cache.put(cacheKey, DEFAULT_UNKNOWN_COUNT, LOCAL_CACHE_TIME_BAD_RESULT); | ||
cache.put(cacheKey, self._options.DEFAULT_UNKNOWN_COUNT, self._options.LOCAL_CACHE_TIME_BAD_RESULT); | ||
return DEFAULT_UNKNOWN_COUNT; | ||
return self._options.DEFAULT_UNKNOWN_COUNT; | ||
}); | ||
// Setting a cache timeout just in case, even though it can lead to parallel requests. | ||
cache.put(cacheKey, retrieveCountPromise, LOCAL_CACHE_TIME_TIMEOUT_RESULT); | ||
cache.put(cacheKey, retrieveCountPromise, self._options.LOCAL_CACHE_TIME_TIMEOUT_RESULT); | ||
return retrieveCountPromise; | ||
}); | ||
} | ||
}; | ||
function retrieveCounts(url, networks) { | ||
SocialButtonsServerMiddleware.prototype._retrieveCounts = function(url, networks) { | ||
// Create an object of callbacks for each of the requested networks It is | ||
@@ -177,33 +214,12 @@ // then passed to the Promise library to executed in parallel All results will | ||
networks.forEach(function(network) { | ||
networksToRequest[network] = getCachedOrRetrieveCount(url, network); | ||
}); | ||
networksToRequest[network] = this._getCachedOrRetrieveCount(url, network); | ||
}, this); | ||
return Promise.props(networksToRequest) | ||
.then(sortObjectByKeys); | ||
} | ||
}; | ||
function errorAndDie(req, res, next, httpStatus, msg) { | ||
res.status(httpStatus); | ||
res.jsonp({ | ||
error: msg, | ||
}); | ||
res.end(); | ||
} | ||
function inputErrorAndDie(req, res, next, msg) { | ||
errorAndDie(req, res, next, 422, msg); | ||
} | ||
function serverErrorAndDie(req, res, next, msg) { | ||
errorAndDie(req, res, next, 500, msg); | ||
} | ||
function forbiddenErrorAndDie(req, res, next, msg) { | ||
errorAndDie(req, res, next, 403, msg); | ||
} | ||
function getCount(req, res, next) { | ||
var url, | ||
SocialButtonsServerMiddleware.prototype._getCount = function(req, res, next) { | ||
var self = this, | ||
url, | ||
networks, | ||
@@ -214,3 +230,3 @@ nonExistantNetworks; | ||
if (!req.query.networks) { | ||
inputErrorAndDie(req, res, next, "You have to specify which networks you want stats for (networks=facebook,twitter,googleplus)"); | ||
SocialButtonsServerMiddleware._inputErrorAndDie(req, res, next, "You have to specify which networks you want stats for (networks=facebook,twitter,googleplus)"); | ||
return; | ||
@@ -220,8 +236,6 @@ } else { | ||
nonExistantNetworks = networks.filter(function(network) { | ||
return (typeof networkCallbacks[network] === "undefined"); | ||
}); | ||
nonExistantNetworks = self._getInvalidNetworks(networks); | ||
if (nonExistantNetworks.length > 0) { | ||
inputErrorAndDie(req, res, next, "Unknown network(s) specified: " + nonExistantNetworks.join()); | ||
SocialButtonsServerMiddleware._inputErrorAndDie(req, res, next, "Unknown network(s) specified: '" + nonExistantNetworks.join("', '") + "'"); | ||
} | ||
@@ -238,3 +252,3 @@ } | ||
if (!url) { | ||
inputErrorAndDie(req, res, next, "You asked for the referring urls stats but there is no referring url, specify one manually (&url=https://example.com/)"); | ||
SocialButtonsServerMiddleware._inputErrorAndDie(req, res, next, "You asked for the referring urls stats but there is no referring url, specify one manually (&url=https://example.com/)"); | ||
return; | ||
@@ -244,3 +258,3 @@ } | ||
retrieveCounts(url, networks) | ||
self._retrieveCounts(url, networks) | ||
.then(function(results) { | ||
@@ -251,35 +265,66 @@ res.jsonp(results); | ||
.catch(function(err) { | ||
console.error("getCount", "catch", "retrieveCounts", err); | ||
self._options.logger.error("self._getCount", "catch", "self._retrieveCounts", err); | ||
serverErrorAndDie(req, res, next, "There was an unknown error."); | ||
SocialButtonsServerMiddleware._serverErrorAndDie(req, res, next, "There was an unknown error."); | ||
}); | ||
} | ||
}; | ||
function forbidden(req, res, next) { | ||
forbiddenErrorAndDie(req, res, next, "Forbidden"); | ||
} | ||
SocialButtonsServerMiddleware.prototype.getRouter = function() { | ||
return this._router; | ||
}; | ||
function router() { | ||
var routerOptions = { | ||
caseSensitive: true, | ||
strict: true, | ||
}, | ||
socialButtonsServerRouter = express.Router(routerOptions); | ||
SocialButtonsServerMiddleware._createCacheKey = function(url, network) { | ||
return "SocialButtonsServerMiddleware " + network + " '" + url + "'"; | ||
}; | ||
socialButtonsServerRouter.get("/", cacheControl); | ||
socialButtonsServerRouter.get("/", getCount); | ||
socialButtonsServerRouter.use(forbidden); | ||
return socialButtonsServerRouter; | ||
} | ||
// Perhaps it's futile to try and clean up before/exit, but one can try. | ||
process | ||
.once("beforeExit", function() { | ||
cache.clear(); | ||
}) | ||
.once("exit", function() { | ||
cache.clear(); | ||
SocialButtonsServerMiddleware._defaultOptions = { | ||
logger: console, | ||
// Cache results in memory -- but keep good and bad (error thrown) results for different periods of time. | ||
LOCAL_CACHE_TIME_GOOD_RESULT: 4 * 60 * 1000, | ||
LOCAL_CACHE_TIME_BAD_RESULT: 1 * 60 * 1000, | ||
LOCAL_CACHE_TIME_TIMEOUT_RESULT: 10 * 1000, | ||
// How many minutes should HTTP requests' results be cached. | ||
CACHE_TIME: process.env.CACHE_TIME || 4 * 60, | ||
// Return this count if none was found or an error was thrown. | ||
DEFAULT_UNKNOWN_COUNT: -1, | ||
routerOptions: { | ||
caseSensitive: true, | ||
strict: true, | ||
}, | ||
}; | ||
SocialButtonsServerMiddleware._errorAndDie = function(req, res, next, httpStatus, msg) { | ||
res.status(httpStatus); | ||
res.jsonp({ | ||
error: msg, | ||
}); | ||
module.exports = router; | ||
res.end(); | ||
}; | ||
SocialButtonsServerMiddleware._inputErrorAndDie = function(req, res, next, msg) { | ||
SocialButtonsServerMiddleware._errorAndDie(req, res, next, 422, msg); | ||
}; | ||
SocialButtonsServerMiddleware._serverErrorAndDie = function(req, res, next, msg) { | ||
SocialButtonsServerMiddleware._errorAndDie(req, res, next, 500, msg); | ||
}; | ||
SocialButtonsServerMiddleware._forbiddenErrorAndDie = function(req, res, next, msg) { | ||
SocialButtonsServerMiddleware._errorAndDie(req, res, next, 403, msg); | ||
}; | ||
SocialButtonsServerMiddleware._forbidden = function(req, res, next) { | ||
SocialButtonsServerMiddleware._forbiddenErrorAndDie(req, res, next, "Forbidden"); | ||
}; | ||
module.exports = SocialButtonsServerMiddleware; |
{ | ||
"name": "meddelare-server", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "Install custom social share counters on your website with your own hosted solution, which only makes a single API request and loads minimal or zero assets to display the counters.", | ||
@@ -18,2 +18,3 @@ "homepage": "http://meddelare.com/", | ||
"express": "^4.12.4", | ||
"extend": "^3.0.0", | ||
"memory-cache": "^0.1.4", | ||
@@ -20,0 +21,0 @@ "morgan": "^1.5.3", |
@@ -7,3 +7,4 @@ var express = require("express"), | ||
socialButtonsServer = require("./lib/social-buttons-server-middleware.js"), | ||
SocialButtonsServerMiddleware = require("./lib/social-buttons-server-middleware.js"), | ||
socialButtonsServerMiddleware = new SocialButtonsServerMiddleware(), | ||
@@ -50,3 +51,3 @@ morgan = require("morgan"), | ||
app.use("/", socialButtonsServer()); | ||
app.use("/", socialButtonsServerMiddleware.getRouter()); | ||
@@ -53,0 +54,0 @@ app.listen(PORT, function() { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21807
278
7
+ Addedextend@^3.0.0
+ Addedextend@3.0.2(transitive)
+ Addedtldts@6.1.64(transitive)
+ Addedtldts-core@6.1.64(transitive)
- Removedtldts@6.1.63(transitive)
- Removedtldts-core@6.1.63(transitive)