rate-limiter-flexible 2.1.5 → 2.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/lib/BurstyRateLimiter.js +47 -3
- package/lib/RateLimiterMySQL.js +38 -21
- package/lib/RateLimiterPostgres.js +44 -24
- package/lib/component/MemoryStorage/MemoryStorage.js +3 -1
- package/lib/index.d.ts +2 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -161,6 +161,7 @@ See [releases](https://github.com/animir/node-rate-limiter-flexible/releases) fo
|
|
|
161
161
|
* [storeType](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#storetype) Have to be set to `knex`, if you use it.
|
|
162
162
|
* [dbName](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#dbname) Where to store points.
|
|
163
163
|
* [tableName](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#tablename) Table/collection.
|
|
164
|
+
* [tableCreated](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#tablecreated) Is table already created in MySQL or PostgreSQL.
|
|
164
165
|
* [clearExpiredByTimeout](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#clearexpiredbytimeout) For MySQL and PostgreSQL.
|
|
165
166
|
|
|
166
167
|
Smooth out traffic picks:
|
|
@@ -215,10 +216,11 @@ Make sure you've launched `npm run eslint` before creating PR, all errors have t
|
|
|
215
216
|
You can try to run `npm run eslint-fix` to fix some issues.
|
|
216
217
|
|
|
217
218
|
Any new limiter with storage have to be extended from `RateLimiterStoreAbstract`.
|
|
218
|
-
It has to implement
|
|
219
|
+
It has to implement 4 methods:
|
|
219
220
|
* `_getRateLimiterRes` parses raw data from store to `RateLimiterRes` object.
|
|
220
|
-
* `_upsert` inserts or updates
|
|
221
|
-
|
|
221
|
+
* `_upsert` must be atomic. it inserts or updates value by key and returns raw data. it must support `forceExpire` mode
|
|
222
|
+
to overwrite key expiration time.
|
|
223
|
+
* `_get` returns raw data by key or `null` if there is no key.
|
|
222
224
|
* `_delete` deletes all key related data and returns `true` on deleted, `false` if key is not found.
|
|
223
225
|
|
|
224
226
|
All other methods depends on store. See `RateLimiterRedis` or `RateLimiterPostgres` for example.
|
package/lib/BurstyRateLimiter.js
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
const RateLimiterRes = require("./RateLimiterRes");
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Bursty rate limiter exposes only msBeforeNext time and doesn't expose points from bursty limiter by default
|
|
5
|
+
* @type {BurstyRateLimiter}
|
|
6
|
+
*/
|
|
3
7
|
module.exports = class BurstyRateLimiter {
|
|
4
8
|
constructor(rateLimiter, burstLimiter) {
|
|
5
9
|
this._rateLimiter = rateLimiter;
|
|
6
10
|
this._burstLimiter = burstLimiter
|
|
7
11
|
}
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Merge rate limiter response objects. Responses can be null
|
|
15
|
+
*
|
|
16
|
+
* @param {RateLimiterRes} [rlRes] Rate limiter response
|
|
17
|
+
* @param {RateLimiterRes} [blRes] Bursty limiter response
|
|
18
|
+
*/
|
|
19
|
+
_combineRes(rlRes, blRes) {
|
|
20
|
+
return new RateLimiterRes(
|
|
21
|
+
rlRes.remainingPoints,
|
|
22
|
+
Math.min(rlRes.msBeforeNext, blRes.msBeforeNext),
|
|
23
|
+
rlRes.consumedPoints,
|
|
24
|
+
rlRes.isFirstInDuration
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param key
|
|
30
|
+
* @param pointsToConsume
|
|
31
|
+
* @param options
|
|
32
|
+
* @returns {Promise<any>}
|
|
33
|
+
*/
|
|
9
34
|
consume(key, pointsToConsume = 1, options = {}) {
|
|
10
35
|
return this._rateLimiter.consume(key, pointsToConsume, options)
|
|
11
36
|
.catch((rlRej) => {
|
|
12
37
|
if (rlRej instanceof RateLimiterRes) {
|
|
13
38
|
return this._burstLimiter.consume(key, pointsToConsume, options)
|
|
14
|
-
.then(() => {
|
|
15
|
-
return Promise.resolve(rlRej)
|
|
39
|
+
.then((blRes) => {
|
|
40
|
+
return Promise.resolve(this._combineRes(rlRej, blRes))
|
|
16
41
|
})
|
|
17
42
|
.catch((blRej) => {
|
|
18
43
|
if (blRej instanceof RateLimiterRes) {
|
|
19
|
-
return Promise.reject(rlRej)
|
|
44
|
+
return Promise.reject(this._combineRes(rlRej, blRej))
|
|
20
45
|
} else {
|
|
21
46
|
return Promise.reject(blRej)
|
|
22
47
|
}
|
|
@@ -27,4 +52,23 @@ module.exports = class BurstyRateLimiter {
|
|
|
27
52
|
}
|
|
28
53
|
})
|
|
29
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* It doesn't expose available points from burstLimiter
|
|
58
|
+
*
|
|
59
|
+
* @param key
|
|
60
|
+
* @returns {Promise<RateLimiterRes>}
|
|
61
|
+
*/
|
|
62
|
+
get(key) {
|
|
63
|
+
return Promise.all([
|
|
64
|
+
this._rateLimiter.get(key),
|
|
65
|
+
this._burstLimiter.get(key),
|
|
66
|
+
]).then(([rlRes, blRes]) => {
|
|
67
|
+
return this._combineRes(rlRes, blRes);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get points() {
|
|
72
|
+
return this._rateLimiter.points;
|
|
73
|
+
}
|
|
30
74
|
};
|
package/lib/RateLimiterMySQL.js
CHANGED
|
@@ -28,24 +28,33 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
|
|
|
28
28
|
|
|
29
29
|
this.clearExpiredByTimeout = opts.clearExpiredByTimeout;
|
|
30
30
|
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
cb
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
cb
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
this.tableCreated = opts.tableCreated;
|
|
32
|
+
if (!this.tableCreated) {
|
|
33
|
+
this._createDbAndTable()
|
|
34
|
+
.then(() => {
|
|
35
|
+
this.tableCreated = true;
|
|
36
|
+
if (this.clearExpiredByTimeout) {
|
|
37
|
+
this._clearExpiredHourAgo();
|
|
38
|
+
}
|
|
39
|
+
if (typeof cb === 'function') {
|
|
40
|
+
cb();
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
.catch((err) => {
|
|
44
|
+
if (typeof cb === 'function') {
|
|
45
|
+
cb(err);
|
|
46
|
+
} else {
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
if (this.clearExpiredByTimeout) {
|
|
52
|
+
this._clearExpiredHourAgo();
|
|
53
|
+
}
|
|
54
|
+
if (typeof cb === 'function') {
|
|
55
|
+
cb();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
clearExpired(expire) {
|
|
@@ -189,6 +198,14 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
|
|
|
189
198
|
this._tableName = typeof value === 'undefined' ? this.keyPrefix : value;
|
|
190
199
|
}
|
|
191
200
|
|
|
201
|
+
get tableCreated() {
|
|
202
|
+
return this._tableCreated
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
set tableCreated(value) {
|
|
206
|
+
this._tableCreated = typeof value === 'undefined' ? false : !!value;
|
|
207
|
+
}
|
|
208
|
+
|
|
192
209
|
get clearExpiredByTimeout() {
|
|
193
210
|
return this._clearExpiredByTimeout;
|
|
194
211
|
}
|
|
@@ -277,7 +294,7 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
|
|
|
277
294
|
}
|
|
278
295
|
|
|
279
296
|
_upsert(key, points, msDuration, forceExpire = false) {
|
|
280
|
-
if (!this.
|
|
297
|
+
if (!this.tableCreated) {
|
|
281
298
|
return Promise.reject(Error('Table is not created yet'));
|
|
282
299
|
}
|
|
283
300
|
|
|
@@ -301,7 +318,7 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
|
|
|
301
318
|
}
|
|
302
319
|
|
|
303
320
|
_get(rlKey) {
|
|
304
|
-
if (!this.
|
|
321
|
+
if (!this.tableCreated) {
|
|
305
322
|
return Promise.reject(Error('Table is not created yet'));
|
|
306
323
|
}
|
|
307
324
|
|
|
@@ -331,7 +348,7 @@ class RateLimiterMySQL extends RateLimiterStoreAbstract {
|
|
|
331
348
|
}
|
|
332
349
|
|
|
333
350
|
_delete(rlKey) {
|
|
334
|
-
if (!this.
|
|
351
|
+
if (!this.tableCreated) {
|
|
335
352
|
return Promise.reject(Error('Table is not created yet'));
|
|
336
353
|
}
|
|
337
354
|
|
|
@@ -26,24 +26,30 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
26
26
|
|
|
27
27
|
this.clearExpiredByTimeout = opts.clearExpiredByTimeout;
|
|
28
28
|
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
cb
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cb
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
this.tableCreated = opts.tableCreated;
|
|
30
|
+
if (!this.tableCreated) {
|
|
31
|
+
this._createTable()
|
|
32
|
+
.then(() => {
|
|
33
|
+
this.tableCreated = true;
|
|
34
|
+
if (this.clearExpiredByTimeout) {
|
|
35
|
+
this._clearExpiredHourAgo();
|
|
36
|
+
}
|
|
37
|
+
if (typeof cb === 'function') {
|
|
38
|
+
cb();
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
.catch((err) => {
|
|
42
|
+
if (typeof cb === 'function') {
|
|
43
|
+
cb(err);
|
|
44
|
+
} else {
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
if (typeof cb === 'function') {
|
|
50
|
+
cb();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
clearExpired(expire) {
|
|
@@ -152,17 +158,23 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
set clientType(value) {
|
|
161
|
+
const constructorName = this.client.constructor.name;
|
|
162
|
+
|
|
155
163
|
if (typeof value === 'undefined') {
|
|
156
|
-
if (
|
|
164
|
+
if (constructorName === 'Client') {
|
|
157
165
|
value = 'client';
|
|
158
|
-
} else if (
|
|
166
|
+
} else if (
|
|
167
|
+
constructorName === 'Pool' ||
|
|
168
|
+
constructorName === 'BoundPool'
|
|
169
|
+
) {
|
|
159
170
|
value = 'pool';
|
|
160
|
-
} else if (
|
|
171
|
+
} else if (constructorName === 'Sequelize') {
|
|
161
172
|
value = 'sequelize';
|
|
162
173
|
} else {
|
|
163
174
|
throw new Error('storeType is not defined');
|
|
164
175
|
}
|
|
165
176
|
}
|
|
177
|
+
|
|
166
178
|
this._clientType = value.toLowerCase();
|
|
167
179
|
}
|
|
168
180
|
|
|
@@ -174,6 +186,14 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
174
186
|
this._tableName = typeof value === 'undefined' ? this.keyPrefix : value;
|
|
175
187
|
}
|
|
176
188
|
|
|
189
|
+
get tableCreated() {
|
|
190
|
+
return this._tableCreated
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
set tableCreated(value) {
|
|
194
|
+
this._tableCreated = typeof value === 'undefined' ? false : !!value;
|
|
195
|
+
}
|
|
196
|
+
|
|
177
197
|
get clearExpiredByTimeout() {
|
|
178
198
|
return this._clearExpiredByTimeout;
|
|
179
199
|
}
|
|
@@ -220,7 +240,7 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
220
240
|
}
|
|
221
241
|
|
|
222
242
|
_upsert(key, points, msDuration, forceExpire = false) {
|
|
223
|
-
if (!this.
|
|
243
|
+
if (!this.tableCreated) {
|
|
224
244
|
return Promise.reject(Error('Table is not created yet'));
|
|
225
245
|
}
|
|
226
246
|
|
|
@@ -248,7 +268,7 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
248
268
|
}
|
|
249
269
|
|
|
250
270
|
_get(rlKey) {
|
|
251
|
-
if (!this.
|
|
271
|
+
if (!this.tableCreated) {
|
|
252
272
|
return Promise.reject(Error('Table is not created yet'));
|
|
253
273
|
}
|
|
254
274
|
|
|
@@ -272,7 +292,7 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
272
292
|
}
|
|
273
293
|
|
|
274
294
|
_delete(rlKey) {
|
|
275
|
-
if (!this.
|
|
295
|
+
if (!this.tableCreated) {
|
|
276
296
|
return Promise.reject(Error('Table is not created yet'));
|
|
277
297
|
}
|
|
278
298
|
|
|
@@ -42,7 +42,9 @@ module.exports = class MemoryStorage {
|
|
|
42
42
|
this._storage[key].timeoutId = setTimeout(() => {
|
|
43
43
|
delete this._storage[key];
|
|
44
44
|
}, durationMs);
|
|
45
|
-
this._storage[key].timeoutId.unref
|
|
45
|
+
if (this._storage[key].timeoutId.unref) {
|
|
46
|
+
this._storage[key].timeoutId.unref();
|
|
47
|
+
}
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
return new RateLimiterRes(0, durationMs === 0 ? -1 : durationMs, this._storage[key].value, true);
|
package/lib/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ interface IRateLimiterStoreOptions extends IRateLimiterOptions{
|
|
|
67
67
|
insuranceLimiter?: RateLimiterAbstract;
|
|
68
68
|
dbName?: string;
|
|
69
69
|
tableName?: string;
|
|
70
|
+
tableCreated?: boolean;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
interface IRateLimiterMongoOptions extends IRateLimiterStoreOptions{
|
|
@@ -157,7 +158,7 @@ interface IRateLimiterQueueOpts {
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
export class RateLimiterQueue {
|
|
160
|
-
constructor(limiterFlexible: RateLimiterAbstract, opts?: IRateLimiterQueueOpts);
|
|
161
|
+
constructor(limiterFlexible: RateLimiterAbstract | BurstyRateLimiter, opts?: IRateLimiterQueueOpts);
|
|
161
162
|
|
|
162
163
|
getTokensRemaining(key?: string | number): Promise<RateLimiterRes>;
|
|
163
164
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rate-limiter-flexible",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.10",
|
|
4
4
|
"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
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "istanbul -v cover -- _mocha --recursive",
|
|
8
8
|
"debug-test": "mocha --inspect-brk lib/**/**.test.js",
|
|
9
|
-
"coveralls": "cat ./coverage/lcov.info |
|
|
10
|
-
"eslint": "
|
|
11
|
-
"eslint-fix": "
|
|
9
|
+
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
|
10
|
+
"eslint": "eslint --quiet lib/**/**.js test/**/**.js",
|
|
11
|
+
"eslint-fix": "eslint --fix lib/**/**.js test/**/**.js"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|