rate-limiter-flexible
Advanced tools
Comparing version 1.1.6 to 1.2.0
@@ -15,4 +15,6 @@ const Record = require('./Record'); | ||
if (this._storage[key]) { | ||
const msBeforeExpires = this._storage[key].expiresAt.getTime() - new Date().getTime(); | ||
if (msBeforeExpires > 0) { | ||
const msBeforeExpires = this._storage[key].expiresAt | ||
? this._storage[key].expiresAt.getTime() - new Date().getTime() | ||
: -1; | ||
if (msBeforeExpires !== 0) { | ||
// Change value | ||
@@ -32,13 +34,18 @@ this._storage[key].value = this._storage[key].value + value; | ||
if (this._storage[key]) { | ||
if (this._storage[key] && this._storage[key].timeoutId) { | ||
clearTimeout(this._storage[key].timeoutId); | ||
} | ||
this._storage[key] = new Record(value, new Date(Date.now() + durationMs)); | ||
this._storage[key].timeoutId = setTimeout(() => { | ||
delete this._storage[key]; | ||
}, durationMs); | ||
this._storage[key].timeoutId.unref(); | ||
this._storage[key] = new Record( | ||
value, | ||
durationMs > 0 ? new Date(Date.now() + durationMs) : null | ||
); | ||
if (durationMs > 0) { | ||
this._storage[key].timeoutId = setTimeout(() => { | ||
delete this._storage[key]; | ||
}, durationMs); | ||
this._storage[key].timeoutId.unref(); | ||
} | ||
return new RateLimiterRes(0, durationMs, this._storage[key].value, true); | ||
return new RateLimiterRes(0, durationMs === 0 ? -1 : durationMs, this._storage[key].value, true); | ||
} | ||
@@ -53,3 +60,5 @@ | ||
if (this._storage[key]) { | ||
const msBeforeExpires = this._storage[key].expiresAt.getTime() - new Date().getTime(); | ||
const msBeforeExpires = this._storage[key].expiresAt | ||
? this._storage[key].expiresAt.getTime() - new Date().getTime() | ||
: -1; | ||
return new RateLimiterRes(0, msBeforeExpires, this._storage[key].value, false); | ||
@@ -56,0 +65,0 @@ } |
@@ -35,3 +35,3 @@ module.exports = class RateLimiterAbstract { | ||
set duration(value) { | ||
this._duration = value || 1; | ||
this._duration = typeof value === 'undefined' ? 1 : value; | ||
} | ||
@@ -38,0 +38,0 @@ |
@@ -38,9 +38,14 @@ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract'); | ||
if (!err) { | ||
this.client.set(`${rlKey}_expire`, nowMs + (secDuration * 1000), secDuration, () => { | ||
const res = { | ||
consumedPoints: points, | ||
msBeforeNext: secDuration * 1000, | ||
}; | ||
resolve(res); | ||
}); | ||
this.client.set( | ||
`${rlKey}_expire`, | ||
secDuration > 0 ? nowMs + (secDuration * 1000) : -1, | ||
secDuration, | ||
() => { | ||
const res = { | ||
consumedPoints: points, | ||
msBeforeNext: secDuration > 0 ? secDuration * 1000 : -1, | ||
}; | ||
resolve(res); | ||
} | ||
); | ||
} else { | ||
@@ -67,9 +72,14 @@ reject(err); | ||
} else { | ||
this.client.add(`${rlKey}_expire`, nowMs + (secDuration * 1000), secDuration, () => { | ||
const res = { | ||
consumedPoints: points, | ||
msBeforeNext: secDuration * 1000, | ||
}; | ||
resolve(res); | ||
}); | ||
this.client.add( | ||
`${rlKey}_expire`, | ||
secDuration > 0 ? nowMs + (secDuration * 1000) : -1, | ||
secDuration, | ||
() => { | ||
const res = { | ||
consumedPoints: points, | ||
msBeforeNext: secDuration > 0 ? secDuration * 1000 : -1, | ||
}; | ||
resolve(res); | ||
} | ||
); | ||
} | ||
@@ -82,6 +92,6 @@ }); | ||
} else { | ||
const expireMs = !resGetExpireMs ? 0 : resGetExpireMs; | ||
const expireMs = resGetExpireMs === false ? 0 : resGetExpireMs; | ||
const res = { | ||
consumedPoints, | ||
msBeforeNext: Math.max(expireMs - nowMs, 0), | ||
msBeforeNext: expireMs >= 0 ? Math.max(expireMs - nowMs, 0) : -1, | ||
}; | ||
@@ -109,6 +119,6 @@ resolve(res); | ||
} else { | ||
const expireMs = !resGetExpireMs ? 0 : resGetExpireMs; | ||
const expireMs = resGetExpireMs === false ? 0 : resGetExpireMs; | ||
const res = { | ||
consumedPoints, | ||
msBeforeNext: Math.max(expireMs - nowMs, 0), | ||
msBeforeNext: expireMs >= 0 ? Math.max(expireMs - nowMs, 0) : -1, | ||
}; | ||
@@ -115,0 +125,0 @@ resolve(res); |
@@ -108,3 +108,5 @@ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract'); | ||
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0); | ||
res.msBeforeNext = Math.max(new Date(doc.expire).getTime() - Date.now(), 0); | ||
res.msBeforeNext = doc.expire !== null | ||
? Math.max(new Date(doc.expire).getTime() - Date.now(), 0) | ||
: -1; | ||
@@ -130,3 +132,3 @@ return res; | ||
points, | ||
expire: new Date(Date.now() + msDuration), | ||
expire: msDuration > 0 ? new Date(Date.now() + msDuration) : null, | ||
}, | ||
@@ -137,3 +139,6 @@ }; | ||
where = { | ||
expire: { $gt: new Date() }, | ||
$or: [ | ||
{ expire: { $gt: new Date() } }, | ||
{ expire: { $eq: null } }, | ||
], | ||
key, | ||
@@ -145,3 +150,3 @@ }; | ||
key, | ||
expire: new Date(Date.now() + msDuration), | ||
expire: msDuration > 0 ? new Date(Date.now() + msDuration) : null, | ||
}, | ||
@@ -171,4 +176,7 @@ $inc: { points }, | ||
if (errUpsert && errUpsert.code === 11000) { // E11000 duplicate key error collection | ||
const replaceWhere = Object.assign({ | ||
expire: { $lte: new Date() }, // try to replace OLD limit doc | ||
const replaceWhere = Object.assign({ // try to replace OLD limit doc | ||
$or: [ | ||
{ expire: { $lte: new Date() } }, | ||
{ expire: { $eq: null } }, | ||
], | ||
key, | ||
@@ -180,3 +188,3 @@ }, docAttrs); | ||
points, | ||
expire: new Date(Date.now() + msDuration), | ||
expire: msDuration > 0 ? new Date(Date.now() + msDuration) : null, | ||
}, docAttrs); | ||
@@ -218,3 +226,6 @@ | ||
key: rlKey, | ||
expire: { $gt: new Date() }, | ||
$or: [ | ||
{ expire: { $gt: new Date() } }, | ||
{ expire: { $eq: null } }, | ||
], | ||
}, docAttrs); | ||
@@ -221,0 +232,0 @@ |
@@ -152,3 +152,3 @@ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract'); | ||
'`points` INT(9) NOT NULL default 0,' + | ||
'`expire` BIGINT UNSIGNED NOT NULL,' + | ||
'`expire` BIGINT UNSIGNED,' + | ||
'PRIMARY KEY (`key`)' + | ||
@@ -209,3 +209,5 @@ ') ENGINE = INNODB;'; | ||
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0); | ||
res.msBeforeNext = Math.max(row.expire - Date.now(), 0); | ||
res.msBeforeNext = row.expire | ||
? Math.max(row.expire - Date.now(), 0) | ||
: -1; | ||
@@ -225,3 +227,3 @@ return res; | ||
const dateNow = Date.now(); | ||
const newExpire = dateNow + msDuration; | ||
const newExpire = msDuration > 0 ? dateNow + msDuration : null; | ||
@@ -313,3 +315,3 @@ let q; | ||
conn.query( | ||
'SELECT points, expire FROM ??.?? WHERE `key` = ? AND `expire` > ?', | ||
'SELECT points, expire FROM ??.?? WHERE `key` = ? AND (`expire` > ? OR `expire` IS NULL)', | ||
[this.dbName, this.tableName, rlKey, Date.now()], | ||
@@ -316,0 +318,0 @@ (err, res) => { |
@@ -146,3 +146,3 @@ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract'); | ||
points integer NOT NULL DEFAULT 0, | ||
expire bigint NOT NULL | ||
expire bigint | ||
);`; | ||
@@ -194,3 +194,5 @@ } | ||
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0); | ||
res.msBeforeNext = Math.max(row.expire - Date.now(), 0); | ||
res.msBeforeNext = row.expire | ||
? Math.max(row.expire - Date.now(), 0) | ||
: -1; | ||
@@ -227,3 +229,3 @@ return res; | ||
const newExpire = Date.now() + msDuration; | ||
const newExpire = msDuration > 0 ? Date.now() + msDuration : null; | ||
const expireQ = forceExpire | ||
@@ -260,3 +262,3 @@ ? ' $3 ' | ||
text: ` | ||
SELECT points, expire FROM ${this.tableName} WHERE key = $1 AND expire > $2;`, | ||
SELECT points, expire FROM ${this.tableName} WHERE key = $1 AND (expire > $2 OR expire IS NULL);`, | ||
values: [rlKey, Date.now()], | ||
@@ -263,0 +265,0 @@ }) |
@@ -24,9 +24,25 @@ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract'); | ||
_getRateLimiterRes(rlKey, changedPoints, result) { | ||
let [resSet, consumed, resTtlMs] = result; | ||
// Support ioredis results format | ||
if (Array.isArray(resSet)) { | ||
[, resSet] = resSet; | ||
[, consumed] = consumed; | ||
[, resTtlMs] = resTtlMs; | ||
let resSet, consumed, resTtlMs, secDuration | ||
let isKeyNeverExpired = false | ||
if (result.length === 2) { | ||
[consumed, resTtlMs] = result | ||
// Support ioredis results format | ||
if (Array.isArray(consumed)) { | ||
[, consumed] = consumed; | ||
[, resTtlMs] = resTtlMs; | ||
} | ||
isKeyNeverExpired = true | ||
} else { | ||
[resSet, consumed, resTtlMs, secDuration] = result | ||
// Support ioredis results format | ||
if (Array.isArray(resSet)) { | ||
[, resSet] = resSet; | ||
[, consumed] = consumed; | ||
[, resTtlMs] = resTtlMs; | ||
} | ||
if (resSet === 'FORCE' && secDuration === 0) { | ||
isKeyNeverExpired = true | ||
} | ||
} | ||
const res = new RateLimiterRes(); | ||
@@ -36,4 +52,5 @@ res.consumedPoints = parseInt(consumed); | ||
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0); | ||
if (resTtlMs === -1) { | ||
// If rlKey created by incrby() not by set() | ||
// TODO: Fix Redis race conditions on transaction level with proper Lua script | ||
if (resTtlMs === -1 && !isKeyNeverExpired && resSet !== 'GET') { | ||
// If rlKey created by incrby() not by set(), this happens really rare | ||
res.isFirstInDuration = true; | ||
@@ -52,7 +69,11 @@ res.msBeforeNext = this.duration; | ||
const secDuration = Math.floor(msDuration / 1000); | ||
const multi = this.client.multi(); | ||
if (forceExpire) { | ||
this.client | ||
.multi() | ||
.set(rlKey, points, 'EX', secDuration) | ||
.pttl(rlKey) | ||
if (secDuration > 0) { | ||
multi.set(rlKey, points, 'EX', secDuration); | ||
} else { | ||
multi.set(rlKey, points); | ||
} | ||
multi.pttl(rlKey) | ||
.exec((err, res) => { | ||
@@ -63,9 +84,10 @@ if (err) { | ||
return resolve(['FORCE', points, res[1]]); | ||
return resolve(['FORCE', points, res[1], secDuration]); | ||
}); | ||
} else { | ||
this.client | ||
.multi() | ||
.set(rlKey, 0, 'EX', secDuration, 'NX') | ||
.incrby(rlKey, points) | ||
if (secDuration > 0) { | ||
multi.set(rlKey, 0, 'EX', secDuration, 'NX'); | ||
} | ||
multi.incrby(rlKey, points) | ||
.pttl(rlKey) | ||
@@ -72,0 +94,0 @@ .exec((err, res) => { |
@@ -298,3 +298,3 @@ const RateLimiterAbstract = require('./RateLimiterAbstract'); | ||
.then(() => { | ||
resolve(new RateLimiterRes(0, msDuration, initPoints)); | ||
resolve(new RateLimiterRes(0, msDuration > 0 ? msDuration : -1, initPoints)); | ||
}) | ||
@@ -301,0 +301,0 @@ .catch((err) => { |
{ | ||
"name": "rate-limiter-flexible", | ||
"version": "1.1.6", | ||
"version": "1.2.0", | ||
"description": "Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
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
103126
2808