-
Notifications
You must be signed in to change notification settings - Fork 62
/
index.js
117 lines (103 loc) · 2.48 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
* Module dependencies.
*/
var assert = require('assert');
var microtime = require('./microtime');
/**
* Expose `Limiter`.
*/
module.exports = Limiter;
/**
* Initialize a new limiter with `opts`:
*
* - `id` identifier being limited
* - `db` redis connection instance
*
* @param {Object} opts
* @api public
*/
function Limiter(opts) {
this.id = opts.id;
this.db = opts.db;
this.tidy = opts.tidy || false;
assert(this.id, '.id required');
assert(this.db, '.db required');
this.max = opts.max || 2500;
this.duration = opts.duration || 3600000;
this.key = 'limit:' + this.id;
}
/**
* Inspect implementation.
*
* @api public
*/
Limiter.prototype.inspect = function() {
return '<Limiter id=' +
this.id + ', duration=' +
this.duration + ', max=' +
this.max + '>';
};
/**
* Get values and header / status code and invoke `fn(err, info)`.
*
* redis is populated with the following keys
* that expire after N milliseconds:
*
* - limit:<id>
*
* @param {Function} fn
* @api public
*/
Limiter.prototype.get = function (fn) {
var db = this.db;
var tidy = this.key;
var duration = this.duration;
var key = this.key;
var max = this.max;
var now = microtime.now();
var start = now - duration * 1000;
var operations = [
['zremrangebyscore', key, 0, start],
['zcard', key],
['zadd', key, now, now],
['zrange', key, 0, 0],
['zrange', key, -max, -max],
['pexpire', key, duration],
]
if (tidy) {
operations.splice(5, 0, ['zremrangebyrank', key, 0, -(max + 1)])
}
db.multi(operations)
.exec(function (err, res) {
if (err) return fn(err);
var isIoRedis = Array.isArray(res[0]);
var count = parseInt(isIoRedis ? res[1][1] : res[1]);
var oldest = parseInt(isIoRedis ? res[3][1] : res[3]);
var oldestInRange = parseInt(isIoRedis ? res[4][1] : res[4]);
var resetMicro = (Number.isNaN(oldestInRange) ? oldest : oldestInRange) + duration * 1000;
fn(null, {
remaining: count < max ? max - count : 0,
reset: Math.floor(resetMicro / 1000000),
resetMs: Math.floor(resetMicro / 1000),
total: max
});
});
};
/**
* Check whether the first item of multi replies is null,
* works with ioredis and node_redis
*
* @param {Array} replies
* @return {Boolean}
* @api private
*/
function isFirstReplyNull(replies) {
if (!replies) {
return true;
}
return Array.isArray(replies[0]) ?
// ioredis
!replies[0][1] :
// node_redis
!replies[0];
}