var error = require("http-error"); var _ = require("lodash"); /** * In memory rate limiter as connect middleware */ module.exports = ratelimit; function ratelimit(key, duration, max) { var requests = Object.create(null); // in case there handles like 'constructor' setInterval(function() { Object.keys(requests).forEach(expireRequests); }, duration * 0.75); function expireRequests(value) { var requestsForHandle = requests[value]; var totalToSplice = 0; var expireTime = Date.now() - duration; /* Requests are already sorted by date as they are appended, so we just loop until we find one that shouldn't have expired and splice them from the list */ for (var i = 0; i < requestsForHandle.length; i++) { if (requestsForHandle[i] >= expireTime) break; totalToSplice++; } requests[value].splice(0, totalToSplice); if (requests[value].length == 0) { delete requests[value]; } return true; } return function(req, res, next) { var value = req.params[key]; requests[value] = requests[value] || []; if (requests[value].length >= max) { if (expireRequests(value) && requests[value].length >= max) { var err = new error.TooManyRequests("Rate limit exceeded"); err.retryIn = Math.min(duration, 5000); return next(err); } } requests[value].push(Date.now()); return next(); }; }