Socket
Socket
Sign inDemoInstall

rate-limiter-flexible

Package Overview
Dependencies
Maintainers
1
Versions
163
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rate-limiter-flexible - npm Package Compare versions

Comparing version 0.11.0 to 0.12.0

yarn-error.log

1

lib/component/MemoryStorage/MemoryStorage.js

@@ -39,2 +39,3 @@ const Record = require('./Record');

}, durationMs);
this._storage[key].timeoutId.unref();

@@ -41,0 +42,0 @@ return new RateLimiterRes(0, durationMs, this._storage[key].value, true);

@@ -78,2 +78,6 @@ module.exports = class RateLimiterAbstract {

parseKey(rlKey) {
return rlKey.substring(this.keyPrefix.length);
}
consume() {

@@ -80,0 +84,0 @@ throw new Error("You have to implement the method 'consume'!");

22

lib/RateLimiterCluster.js

@@ -57,3 +57,3 @@ /**

const workerSendToMaster = function (func, id, key, points) {
const workerSendToMaster = function (func, id, key, arg) {
const payload = {

@@ -66,3 +66,3 @@ channel,

key,
points,
arg,
},

@@ -88,10 +88,13 @@ };

case 'consume':
promise = this._rateLimiters[msg.keyPrefix].consume(msg.data.key, msg.data.points);
promise = this._rateLimiters[msg.keyPrefix].consume(msg.data.key, msg.data.arg);
break;
case 'penalty':
promise = this._rateLimiters[msg.keyPrefix].penalty(msg.data.key, msg.data.points);
promise = this._rateLimiters[msg.keyPrefix].penalty(msg.data.key, msg.data.arg);
break;
case 'reward':
promise = this._rateLimiters[msg.keyPrefix].reward(msg.data.key, msg.data.points);
promise = this._rateLimiters[msg.keyPrefix].reward(msg.data.key, msg.data.arg);
break;
case 'block':
promise = this._rateLimiters[msg.keyPrefix].block(msg.data.key, msg.data.arg);
break;
default:

@@ -268,2 +271,11 @@ return false;

}
block(key, secDuration) {
return new Promise((resolve, reject) => {
const id = getPromiseId.call(this);
savePromise.call(this, id, resolve, reject);
workerSendToMaster.call(this, 'block', id, key, secDuration);
});
}
}

@@ -270,0 +282,0 @@

const RateLimiterAbstract = require('./RateLimiterAbstract');
const MemoryStorage = require('./component/MemoryStorage/MemoryStorage');
const RateLimiterRes = require('./RateLimiterRes');

@@ -57,2 +58,16 @@ class RateLimiterMemory extends RateLimiterAbstract {

}
/**
* Block any key for secDuration seconds
*
* @param key
* @param secDuration
*/
block(key, secDuration) {
const msDuration = secDuration * 1000;
const initPoints = this.points + 1;
this._memoryStorage.set(this.getKey(key), initPoints, msDuration);
return Promise.resolve(new RateLimiterRes(0, msDuration, initPoints));
}
}

@@ -59,0 +74,0 @@

const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
const RateLimiterRes = require('./RateLimiterRes');
const getRateLimiterRes = function (points, result) {
const res = new RateLimiterRes();
res.isFirstInDuration = result.value === null;
res.consumedPoints = res.isFirstInDuration ? points : result.value.points;
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
res.msBeforeNext = res.isFirstInDuration
? this.duration * 1000
: Math.max(new Date(result.value.expire).getTime() - Date.now(), 0);
return res;
};
const update = function (key, points) {

@@ -51,26 +37,2 @@ return this._collection.findOneAndUpdate(

const afterConsume = function (resolve, reject, rlKey, changedPoints, result) {
const res = getRateLimiterRes.call(this, changedPoints, result);
if (res.consumedPoints > this.points) {
if (this.inmemoryBlockOnConsumed > 0 && res.consumedPoints >= this.inmemoryBlockOnConsumed) {
// Block key for this.inmemoryBlockDuration seconds
this._inmemoryBlockedKeys.add(rlKey, this.inmemoryBlockDuration);
res.msBeforeNext = this.msInmemoryBlockDuration;
// Block only first time when consumed more than points
} else if (this.blockDuration > 0 && res.consumedPoints <= (this.points + changedPoints)) {
upsertExpire.call(this, rlKey, res.consumedPoints, this.msBlockDuration);
res.msBeforeNext = this.msBlockDuration;
}
reject(res);
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
const delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
setTimeout(resolve, delay, res);
} else {
resolve(res);
}
};
class RateLimiterMongo extends RateLimiterStoreAbstract {

@@ -113,2 +75,28 @@ /**

_getRateLimiterRes(rlKey, changedPoints, result) {
const res = new RateLimiterRes();
res.isFirstInDuration = result.value === null;
res.consumedPoints = res.isFirstInDuration ? changedPoints : result.value.points;
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
res.msBeforeNext = res.isFirstInDuration
? this.duration * 1000
: Math.max(new Date(result.value.expire).getTime() - Date.now(), 0);
return res;
}
_block(rlKey, initPoints, msDuration) {
return new Promise((resolve, reject) => {
upsertExpire.call(this, rlKey, initPoints, msDuration)
.then(() => {
resolve(new RateLimiterRes(0, msDuration, initPoints));
})
.catch((err) => {
this._handleError(err, 'block', resolve, reject, this.parseKey(rlKey), initPoints);
});
});
}
/**

@@ -131,6 +119,6 @@ *

.then((res) => {
afterConsume.call(this, resolve, reject, rlKey, pointsToConsume, res);
this._afterConsume(resolve, reject, rlKey, pointsToConsume, res);
})
.catch((err) => {
this.handleError(err, 'consume', resolve, reject, key, pointsToConsume);
this._handleError(err, 'consume', resolve, reject, key, pointsToConsume);
});

@@ -145,6 +133,6 @@ });

.then((res) => {
resolve(getRateLimiterRes.call(this, points, res));
resolve(this._getRateLimiterRes(rlKey, points, res));
})
.catch((err) => {
this.handleError(err, 'penalty', resolve, reject, key, points);
this._handleError(err, 'penalty', resolve, reject, key, points);
});

@@ -159,6 +147,6 @@ });

.then((res) => {
resolve(getRateLimiterRes.call(this, points, res));
resolve(this._getRateLimiterRes(rlKey, points, res));
})
.catch((err) => {
this.handleError(err, 'reward', resolve, reject, key, points);
this._handleError(err, 'reward', resolve, reject, key, points);
});

@@ -165,0 +153,0 @@ });

const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');
const RateLimiterRes = require('./RateLimiterRes');
const afterConsume = function (resolve, reject, rlKey, changedPoints, results) {
let [resSet, consumed, resTtlMs] = results;
// Support ioredis results format
if (Array.isArray(resSet)) {
[, resSet] = resSet;
[, consumed] = consumed;
[, resTtlMs] = resTtlMs;
}
const res = new RateLimiterRes();
res.consumedPoints = consumed;
res.isFirstInDuration = resSet === 'OK';
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
if (resTtlMs === -1) { // If rlKey created by incrby() not by set()
res.isFirstInDuration = true;
res.msBeforeNext = this.duration;
this.redis.expire(rlKey, this.duration);
} else {
res.msBeforeNext = resTtlMs;
}
if (res.consumedPoints > this.points) {
if (this.inmemoryBlockOnConsumed > 0 && res.consumedPoints >= this.inmemoryBlockOnConsumed) {
// Block key in memory for this.inmemoryBlockDuration seconds
this._inmemoryBlockedKeys.add(rlKey, this.inmemoryBlockDuration);
res.msBeforeNext = this.msInmemoryBlockDuration;
} else if (this.blockDuration > 0 && res.consumedPoints <= (this.points + changedPoints)) {
// Block key
this.redis.set(rlKey, res.consumedPoints, 'EX', this.blockDuration, () => {});
res.msBeforeNext = this.msBlockDuration;
}
reject(res);
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
const delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
setTimeout(resolve, delay, res);
} else {
resolve(res);
}
};
class RateLimiterRedis extends RateLimiterStoreAbstract {

@@ -72,2 +31,37 @@ /**

_getRateLimiterRes(rlKey, changedPoints, result) {
let [resSet, consumed, resTtlMs] = result;
// Support ioredis results format
if (Array.isArray(resSet)) {
[, resSet] = resSet;
[, consumed] = consumed;
[, resTtlMs] = resTtlMs;
}
const res = new RateLimiterRes();
res.consumedPoints = consumed;
res.isFirstInDuration = resSet === 'OK';
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
if (resTtlMs === -1) { // If rlKey created by incrby() not by set()
res.isFirstInDuration = true;
res.msBeforeNext = this.duration;
this.redis.expire(rlKey, this.duration);
} else {
res.msBeforeNext = resTtlMs;
}
return res;
}
_block(rlKey, initPoints, msDuration) {
return new Promise((resolve, reject) => {
this.redis.set(rlKey, initPoints, 'EX', Math.floor(msDuration / 1000), (err) => {
if (err) {
return this._handleError(err, 'block', resolve, reject, this.parseKey(rlKey), initPoints);
}
resolve(new RateLimiterRes(0, msDuration, initPoints));
});
});
}
/**

@@ -94,5 +88,5 @@ *

if (err) {
this.handleError(err, 'consume', resolve, reject, key, pointsToConsume);
this._handleError(err, 'consume', resolve, reject, key, pointsToConsume);
} else {
afterConsume.call(this, resolve, reject, rlKey, pointsToConsume, results);
this._afterConsume(resolve, reject, rlKey, pointsToConsume, results);
}

@@ -108,3 +102,3 @@ });

if (err) {
this.handleError(err, 'penalty', resolve, reject, key, points);
this._handleError(err, 'penalty', resolve, reject, key, points);
} else {

@@ -122,3 +116,3 @@ resolve(new RateLimiterRes(this.points - consumedPoints, 0, consumedPoints));

if (err) {
this.handleError(err, 'reward', resolve, reject, key, points);
this._handleError(err, 'reward', resolve, reject, key, points);
} else {

@@ -125,0 +119,0 @@ resolve(new RateLimiterRes(this.points - consumedPoints, 0, consumedPoints));

@@ -32,3 +32,46 @@ const RateLimiterAbstract = require('./RateLimiterAbstract');

handleError(err, funcName, resolve, reject, key, points) {
/**
* Have to be launched after consume
* It blocks key and execute evenly depending on result from store
*
* @param resolve
* @param reject
* @param rlKey
* @param changedPoints
* @param storeResult
* @private
*/
_afterConsume(resolve, reject, rlKey, changedPoints, storeResult) {
const res = this._getRateLimiterRes(rlKey, changedPoints, storeResult);
if (res.consumedPoints > this.points) {
if (this.inmemoryBlockOnConsumed > 0 && res.consumedPoints >= this.inmemoryBlockOnConsumed) {
// Block key for this.inmemoryBlockDuration seconds
this._inmemoryBlockedKeys.add(rlKey, this.inmemoryBlockDuration);
res.msBeforeNext = this.msInmemoryBlockDuration;
reject(res);
// Block only first time when consumed more than points
} else if (this.blockDuration > 0 && res.consumedPoints <= (this.points + changedPoints)) {
this._block(rlKey, res.consumedPoints, this.msBlockDuration)
.then(() => {
res.msBeforeNext = this.msBlockDuration;
reject(res);
})
.catch((err) => {
this._handleError(err, 'block', resolve, reject, this.parseKey(rlKey), res.consumedPoints);
});
} else {
reject(res);
}
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
const delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
setTimeout(resolve, delay, res);
} else {
resolve(res);
}
}
_handleError(err, funcName, resolve, reject, key, points) {
if (!(this.insuranceLimiter instanceof RateLimiterAbstract)) {

@@ -83,2 +126,41 @@ reject(err);

}
/**
* Block any key for secDuration seconds
*
* @param key
* @param secDuration
*
* @return Promise<any>
*/
block(key, secDuration) {
const msDuration = secDuration * 1000;
return this._block(this.getKey(key), this.points + 1, msDuration);
}
/**
* Get RateLimiterRes object filled depending on storeResult, which specific for exact store
*
* @param rlKey
* @param changedPoints
* @param storeResult
* @private
*/
_getRateLimiterRes(rlKey, changedPoints, storeResult) { // eslint-disable-line no-unused-vars
throw new Error("You have to implement the method '_getRateLimiterRes'!");
}
/**
* Block key for this.msBlockDuration milliseconds
* Usually, it just prolongs lifetime of key
*
* @param rlKey
* @param points
* @param msDuration
*
* @return Promise<any>
*/
_block(rlKey, points, msDuration) { // eslint-disable-line no-unused-vars
return Promise.reject(new Error("You have to implement the method '_block'!"));
}
};
{
"name": "rate-limiter-flexible",
"version": "0.11.0",
"version": "0.12.0",
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -28,3 +28,3 @@ [![Build Status](https://travis-ci.org/animir/node-rate-limiter-flexible.png)](https://travis-ci.org/animir/node-rate-limiter-flexible)

* Redis and Mongo errors don't result to broken app if `insuranceLimiter` set up
* useful `penalty` and `reward` methods to change limits on some results of an action
* useful `block`, `penalty` and `reward` methods

@@ -425,2 +425,11 @@ ### Links

### rateLimiter.block(key, secDuration)
Block `key` for `secDuration` seconds
Returns Promise, which:
* **resolved** with `RateLimiterRes`
* **rejected** only for Redis and Mongo if `insuranceLimiter` isn't setup: when some error happened, where reject reason `rejRes` is Error object
* **rejected** only for RateLimiterCluster if `insuranceLimiter` isn't setup: when `timeoutMs` exceeded, where reject reason `rejRes` is Error object
## Contribution

@@ -427,0 +436,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc