@libp2p/utils 5.2.0-ee7ffe9b9 → 5.2.1-8bbd43628
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/dist/src/rate-limiter.d.ts +86 -0
- package/dist/src/rate-limiter.d.ts.map +1 -0
- package/dist/src/rate-limiter.js +184 -0
- package/dist/src/rate-limiter.js.map +1 -0
- package/package.json +15 -10
- package/src/rate-limiter.ts +287 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface RateLimiterInit {
|
|
2
|
+
/**
|
|
3
|
+
* Number of points
|
|
4
|
+
*
|
|
5
|
+
* @default 4
|
|
6
|
+
*/
|
|
7
|
+
points?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Per seconds
|
|
10
|
+
*
|
|
11
|
+
* @default 1
|
|
12
|
+
*/
|
|
13
|
+
duration?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Block if consumed more than points in current duration for blockDuration seconds
|
|
16
|
+
*
|
|
17
|
+
* @default 0
|
|
18
|
+
*/
|
|
19
|
+
blockDuration?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Execute allowed actions evenly over duration
|
|
22
|
+
*
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
execEvenly?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* ms, works with execEvenly=true option
|
|
28
|
+
*
|
|
29
|
+
* @default duration * 1000 / points
|
|
30
|
+
*/
|
|
31
|
+
execEvenlyMinDelayMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* @default rlflx
|
|
34
|
+
*/
|
|
35
|
+
keyPrefix?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface GetKeySecDurationOptions {
|
|
38
|
+
customDuration?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface RateLimiterResult {
|
|
41
|
+
remainingPoints: number;
|
|
42
|
+
msBeforeNext: number;
|
|
43
|
+
consumedPoints: number;
|
|
44
|
+
isFirstInDuration: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface RateRecord {
|
|
47
|
+
value: number;
|
|
48
|
+
expiresAt?: Date;
|
|
49
|
+
timeoutId?: ReturnType<typeof setTimeout>;
|
|
50
|
+
}
|
|
51
|
+
export declare class RateLimiter {
|
|
52
|
+
readonly memoryStorage: MemoryStorage;
|
|
53
|
+
protected points: number;
|
|
54
|
+
protected duration: number;
|
|
55
|
+
protected blockDuration: number;
|
|
56
|
+
protected execEvenly: boolean;
|
|
57
|
+
protected execEvenlyMinDelayMs: number;
|
|
58
|
+
protected keyPrefix: string;
|
|
59
|
+
constructor(opts?: RateLimiterInit);
|
|
60
|
+
consume(key: string, pointsToConsume?: number, options?: GetKeySecDurationOptions): Promise<RateLimiterResult>;
|
|
61
|
+
penalty(key: string, points?: number, options?: GetKeySecDurationOptions): RateLimiterResult;
|
|
62
|
+
reward(key: string, points?: number, options?: GetKeySecDurationOptions): RateLimiterResult;
|
|
63
|
+
/**
|
|
64
|
+
* Block any key for secDuration seconds
|
|
65
|
+
*
|
|
66
|
+
* @param key
|
|
67
|
+
* @param secDuration
|
|
68
|
+
*/
|
|
69
|
+
block(key: string, secDuration: number): RateLimiterResult;
|
|
70
|
+
set(key: string, points: number, secDuration?: number): RateLimiterResult;
|
|
71
|
+
get(key: string): RateLimiterResult | undefined;
|
|
72
|
+
delete(key: string): void;
|
|
73
|
+
private _getKeySecDuration;
|
|
74
|
+
getKey(key: string): string;
|
|
75
|
+
parseKey(rlKey: string): string;
|
|
76
|
+
}
|
|
77
|
+
declare class MemoryStorage {
|
|
78
|
+
readonly storage: Map<string, RateRecord>;
|
|
79
|
+
constructor();
|
|
80
|
+
incrby(key: string, value: number, durationSec: number): RateLimiterResult;
|
|
81
|
+
set(key: string, value: number, durationSec: number): RateLimiterResult;
|
|
82
|
+
get(key: string): RateLimiterResult | undefined;
|
|
83
|
+
delete(key: string): boolean;
|
|
84
|
+
}
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limiter.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAE7B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;CAC1C;AAED,qBAAa,WAAW;IACtB,SAAgB,aAAa,EAAE,aAAa,CAAA;IAC5C,SAAS,CAAC,MAAM,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,aAAa,EAAE,MAAM,CAAA;IAC/B,SAAS,CAAC,UAAU,EAAE,OAAO,CAAA;IAC7B,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACtC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAA;gBAEd,IAAI,GAAE,eAAoB;IAUjC,OAAO,CAAE,GAAG,EAAE,MAAM,EAAE,eAAe,GAAE,MAAU,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA2B5H,OAAO,CAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,EAAE,OAAO,GAAE,wBAA6B,GAAG,iBAAiB;IASpG,MAAM,CAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,EAAE,OAAO,GAAE,wBAA6B,GAAG,iBAAiB;IASnG;;;;;OAKG;IACH,KAAK,CAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB;IAc3D,GAAG,CAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAE,MAAU,GAAG,iBAAiB;IAa7E,GAAG,CAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAUhD,MAAM,CAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAI1B,OAAO,CAAC,kBAAkB;IAQ1B,MAAM,CAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAI5B,QAAQ,CAAE,KAAK,EAAE,MAAM,GAAG,MAAM;CAGjC;AAED,cAAM,aAAa;IACjB,SAAgB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;;IAMhD,MAAM,CAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB;IA0B3E,GAAG,CAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,iBAAiB;IAiCxE,GAAG,CAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAgBhD,MAAM,CAAE,GAAG,EAAE,MAAM,GAAG,OAAO;CAc9B"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { CodeError } from '@libp2p/interface';
|
|
2
|
+
import delay from 'delay';
|
|
3
|
+
export class RateLimiter {
|
|
4
|
+
memoryStorage;
|
|
5
|
+
points;
|
|
6
|
+
duration;
|
|
7
|
+
blockDuration;
|
|
8
|
+
execEvenly;
|
|
9
|
+
execEvenlyMinDelayMs;
|
|
10
|
+
keyPrefix;
|
|
11
|
+
constructor(opts = {}) {
|
|
12
|
+
this.points = opts.points ?? 4;
|
|
13
|
+
this.duration = opts.duration ?? 1;
|
|
14
|
+
this.blockDuration = opts.blockDuration ?? 0;
|
|
15
|
+
this.execEvenly = opts.execEvenly ?? false;
|
|
16
|
+
this.execEvenlyMinDelayMs = opts.execEvenlyMinDelayMs ?? (this.duration * 1000 / this.points);
|
|
17
|
+
this.keyPrefix = opts.keyPrefix ?? 'rlflx';
|
|
18
|
+
this.memoryStorage = new MemoryStorage();
|
|
19
|
+
}
|
|
20
|
+
async consume(key, pointsToConsume = 1, options = {}) {
|
|
21
|
+
const rlKey = this.getKey(key);
|
|
22
|
+
const secDuration = this._getKeySecDuration(options);
|
|
23
|
+
let res = this.memoryStorage.incrby(rlKey, pointsToConsume, secDuration);
|
|
24
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
|
|
25
|
+
if (res.consumedPoints > this.points) {
|
|
26
|
+
// Block only first time when consumed more than points
|
|
27
|
+
if (this.blockDuration > 0 && res.consumedPoints <= (this.points + pointsToConsume)) {
|
|
28
|
+
// Block key
|
|
29
|
+
res = this.memoryStorage.set(rlKey, res.consumedPoints, this.blockDuration);
|
|
30
|
+
}
|
|
31
|
+
throw new CodeError('Rate limit exceeded', 'ERR_RATE_LIMIT_EXCEEDED', res);
|
|
32
|
+
}
|
|
33
|
+
else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
|
|
34
|
+
// Execute evenly
|
|
35
|
+
let delayMs = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
|
|
36
|
+
if (delayMs < this.execEvenlyMinDelayMs) {
|
|
37
|
+
delayMs = res.consumedPoints * this.execEvenlyMinDelayMs;
|
|
38
|
+
}
|
|
39
|
+
await delay(delayMs);
|
|
40
|
+
}
|
|
41
|
+
return res;
|
|
42
|
+
}
|
|
43
|
+
penalty(key, points = 1, options = {}) {
|
|
44
|
+
const rlKey = this.getKey(key);
|
|
45
|
+
const secDuration = this._getKeySecDuration(options);
|
|
46
|
+
const res = this.memoryStorage.incrby(rlKey, points, secDuration);
|
|
47
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
|
|
48
|
+
return res;
|
|
49
|
+
}
|
|
50
|
+
reward(key, points = 1, options = {}) {
|
|
51
|
+
const rlKey = this.getKey(key);
|
|
52
|
+
const secDuration = this._getKeySecDuration(options);
|
|
53
|
+
const res = this.memoryStorage.incrby(rlKey, -points, secDuration);
|
|
54
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Block any key for secDuration seconds
|
|
59
|
+
*
|
|
60
|
+
* @param key
|
|
61
|
+
* @param secDuration
|
|
62
|
+
*/
|
|
63
|
+
block(key, secDuration) {
|
|
64
|
+
const msDuration = secDuration * 1000;
|
|
65
|
+
const initPoints = this.points + 1;
|
|
66
|
+
this.memoryStorage.set(this.getKey(key), initPoints, secDuration);
|
|
67
|
+
return {
|
|
68
|
+
remainingPoints: 0,
|
|
69
|
+
msBeforeNext: msDuration === 0 ? -1 : msDuration,
|
|
70
|
+
consumedPoints: initPoints,
|
|
71
|
+
isFirstInDuration: false
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
set(key, points, secDuration = 0) {
|
|
75
|
+
const msDuration = (secDuration >= 0 ? secDuration : this.duration) * 1000;
|
|
76
|
+
this.memoryStorage.set(this.getKey(key), points, secDuration);
|
|
77
|
+
return {
|
|
78
|
+
remainingPoints: 0,
|
|
79
|
+
msBeforeNext: msDuration === 0 ? -1 : msDuration,
|
|
80
|
+
consumedPoints: points,
|
|
81
|
+
isFirstInDuration: false
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
get(key) {
|
|
85
|
+
const res = this.memoryStorage.get(this.getKey(key));
|
|
86
|
+
if (res != null) {
|
|
87
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
|
|
88
|
+
}
|
|
89
|
+
return res;
|
|
90
|
+
}
|
|
91
|
+
delete(key) {
|
|
92
|
+
this.memoryStorage.delete(this.getKey(key));
|
|
93
|
+
}
|
|
94
|
+
_getKeySecDuration(options) {
|
|
95
|
+
if (options?.customDuration != null && options.customDuration >= 0) {
|
|
96
|
+
return options.customDuration;
|
|
97
|
+
}
|
|
98
|
+
return this.duration;
|
|
99
|
+
}
|
|
100
|
+
getKey(key) {
|
|
101
|
+
return this.keyPrefix.length > 0 ? `${this.keyPrefix}:${key}` : key;
|
|
102
|
+
}
|
|
103
|
+
parseKey(rlKey) {
|
|
104
|
+
return rlKey.substring(this.keyPrefix.length);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class MemoryStorage {
|
|
108
|
+
storage;
|
|
109
|
+
constructor() {
|
|
110
|
+
this.storage = new Map();
|
|
111
|
+
}
|
|
112
|
+
incrby(key, value, durationSec) {
|
|
113
|
+
const existing = this.storage.get(key);
|
|
114
|
+
if (existing != null) {
|
|
115
|
+
const msBeforeExpires = existing.expiresAt != null
|
|
116
|
+
? existing.expiresAt.getTime() - new Date().getTime()
|
|
117
|
+
: -1;
|
|
118
|
+
if (existing.expiresAt == null || msBeforeExpires > 0) {
|
|
119
|
+
// Change value
|
|
120
|
+
existing.value += value;
|
|
121
|
+
return {
|
|
122
|
+
remainingPoints: 0,
|
|
123
|
+
msBeforeNext: msBeforeExpires,
|
|
124
|
+
consumedPoints: existing.value,
|
|
125
|
+
isFirstInDuration: false
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return this.set(key, value, durationSec);
|
|
129
|
+
}
|
|
130
|
+
return this.set(key, value, durationSec);
|
|
131
|
+
}
|
|
132
|
+
set(key, value, durationSec) {
|
|
133
|
+
const durationMs = durationSec * 1000;
|
|
134
|
+
const existing = this.storage.get(key);
|
|
135
|
+
if (existing != null) {
|
|
136
|
+
clearTimeout(existing.timeoutId);
|
|
137
|
+
}
|
|
138
|
+
const record = {
|
|
139
|
+
value,
|
|
140
|
+
expiresAt: durationMs > 0 ? new Date(Date.now() + durationMs) : undefined
|
|
141
|
+
};
|
|
142
|
+
this.storage.set(key, record);
|
|
143
|
+
if (durationMs > 0) {
|
|
144
|
+
record.timeoutId = setTimeout(() => {
|
|
145
|
+
this.storage.delete(key);
|
|
146
|
+
}, durationMs);
|
|
147
|
+
if (record.timeoutId.unref != null) {
|
|
148
|
+
record.timeoutId.unref();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
remainingPoints: 0,
|
|
153
|
+
msBeforeNext: durationMs === 0 ? -1 : durationMs,
|
|
154
|
+
consumedPoints: record.value,
|
|
155
|
+
isFirstInDuration: true
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
get(key) {
|
|
159
|
+
const existing = this.storage.get(key);
|
|
160
|
+
if (existing != null) {
|
|
161
|
+
const msBeforeExpires = existing.expiresAt != null
|
|
162
|
+
? existing.expiresAt.getTime() - new Date().getTime()
|
|
163
|
+
: -1;
|
|
164
|
+
return {
|
|
165
|
+
remainingPoints: 0,
|
|
166
|
+
msBeforeNext: msBeforeExpires,
|
|
167
|
+
consumedPoints: existing.value,
|
|
168
|
+
isFirstInDuration: false
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
delete(key) {
|
|
173
|
+
const record = this.storage.get(key);
|
|
174
|
+
if (record != null) {
|
|
175
|
+
if (record.timeoutId != null) {
|
|
176
|
+
clearTimeout(record.timeoutId);
|
|
177
|
+
}
|
|
178
|
+
this.storage.delete(key);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libp2p/utils",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.1-8bbd43628",
|
|
4
4
|
"description": "Package to aggregate shared logic and dependencies for the libp2p ecosystem",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/utils#readme",
|
|
@@ -76,13 +76,17 @@
|
|
|
76
76
|
"types": "./dist/src/multiaddr/is-private.d.ts",
|
|
77
77
|
"import": "./dist/src/multiaddr/is-private.js"
|
|
78
78
|
},
|
|
79
|
+
"./peer-queue": {
|
|
80
|
+
"types": "./dist/src/peer-queue.d.ts",
|
|
81
|
+
"import": "./dist/src/peer-queue.js"
|
|
82
|
+
},
|
|
79
83
|
"./queue": {
|
|
80
84
|
"types": "./dist/src/queue/index.d.ts",
|
|
81
85
|
"import": "./dist/src/queue/index.js"
|
|
82
86
|
},
|
|
83
|
-
"./
|
|
84
|
-
"types": "./dist/src/
|
|
85
|
-
"import": "./dist/src/
|
|
87
|
+
"./rate-limiter": {
|
|
88
|
+
"types": "./dist/src/rate-limiter.d.ts",
|
|
89
|
+
"import": "./dist/src/rate-limiter.js"
|
|
86
90
|
},
|
|
87
91
|
"./stream-to-ma-conn": {
|
|
88
92
|
"types": "./dist/src/stream-to-ma-conn.d.ts",
|
|
@@ -119,22 +123,23 @@
|
|
|
119
123
|
},
|
|
120
124
|
"dependencies": {
|
|
121
125
|
"@chainsafe/is-ip": "^2.0.2",
|
|
122
|
-
"@libp2p/interface": "1.1.1-
|
|
123
|
-
"@libp2p/logger": "4.0.4-
|
|
126
|
+
"@libp2p/interface": "1.1.1-8bbd43628",
|
|
127
|
+
"@libp2p/logger": "4.0.4-8bbd43628",
|
|
124
128
|
"@multiformats/multiaddr": "^12.1.10",
|
|
125
129
|
"@multiformats/multiaddr-matcher": "^1.1.0",
|
|
130
|
+
"delay": "^6.0.0",
|
|
126
131
|
"get-iterator": "^2.0.1",
|
|
127
132
|
"is-loopback-addr": "^2.0.1",
|
|
128
|
-
"it-pushable": "^3.2.
|
|
133
|
+
"it-pushable": "^3.2.3",
|
|
129
134
|
"it-stream-types": "^2.0.1",
|
|
130
135
|
"p-defer": "^4.0.0",
|
|
131
136
|
"private-ip": "^3.0.1",
|
|
132
137
|
"race-event": "^1.1.0",
|
|
133
|
-
"race-signal": "^1.0.
|
|
134
|
-
"uint8arraylist": "^2.4.
|
|
138
|
+
"race-signal": "^1.0.2",
|
|
139
|
+
"uint8arraylist": "^2.4.7"
|
|
135
140
|
},
|
|
136
141
|
"devDependencies": {
|
|
137
|
-
"@libp2p/peer-id-factory": "4.0.
|
|
142
|
+
"@libp2p/peer-id-factory": "4.0.4-8bbd43628",
|
|
138
143
|
"aegir": "^42.0.0",
|
|
139
144
|
"delay": "^6.0.0",
|
|
140
145
|
"it-all": "^3.0.3",
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { CodeError } from '@libp2p/interface'
|
|
2
|
+
import delay from 'delay'
|
|
3
|
+
|
|
4
|
+
export interface RateLimiterInit {
|
|
5
|
+
/**
|
|
6
|
+
* Number of points
|
|
7
|
+
*
|
|
8
|
+
* @default 4
|
|
9
|
+
*/
|
|
10
|
+
points?: number
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Per seconds
|
|
14
|
+
*
|
|
15
|
+
* @default 1
|
|
16
|
+
*/
|
|
17
|
+
duration?: number
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Block if consumed more than points in current duration for blockDuration seconds
|
|
21
|
+
*
|
|
22
|
+
* @default 0
|
|
23
|
+
*/
|
|
24
|
+
blockDuration?: number
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute allowed actions evenly over duration
|
|
28
|
+
*
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
execEvenly?: boolean
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* ms, works with execEvenly=true option
|
|
35
|
+
*
|
|
36
|
+
* @default duration * 1000 / points
|
|
37
|
+
*/
|
|
38
|
+
execEvenlyMinDelayMs?: number
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @default rlflx
|
|
42
|
+
*/
|
|
43
|
+
keyPrefix?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GetKeySecDurationOptions {
|
|
47
|
+
customDuration?: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RateLimiterResult {
|
|
51
|
+
remainingPoints: number
|
|
52
|
+
msBeforeNext: number
|
|
53
|
+
consumedPoints: number
|
|
54
|
+
isFirstInDuration: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RateRecord {
|
|
58
|
+
value: number
|
|
59
|
+
expiresAt?: Date
|
|
60
|
+
timeoutId?: ReturnType<typeof setTimeout>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class RateLimiter {
|
|
64
|
+
public readonly memoryStorage: MemoryStorage
|
|
65
|
+
protected points: number
|
|
66
|
+
protected duration: number
|
|
67
|
+
protected blockDuration: number
|
|
68
|
+
protected execEvenly: boolean
|
|
69
|
+
protected execEvenlyMinDelayMs: number
|
|
70
|
+
protected keyPrefix: string
|
|
71
|
+
|
|
72
|
+
constructor (opts: RateLimiterInit = {}) {
|
|
73
|
+
this.points = opts.points ?? 4
|
|
74
|
+
this.duration = opts.duration ?? 1
|
|
75
|
+
this.blockDuration = opts.blockDuration ?? 0
|
|
76
|
+
this.execEvenly = opts.execEvenly ?? false
|
|
77
|
+
this.execEvenlyMinDelayMs = opts.execEvenlyMinDelayMs ?? (this.duration * 1000 / this.points)
|
|
78
|
+
this.keyPrefix = opts.keyPrefix ?? 'rlflx'
|
|
79
|
+
this.memoryStorage = new MemoryStorage()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async consume (key: string, pointsToConsume: number = 1, options: GetKeySecDurationOptions = {}): Promise<RateLimiterResult> {
|
|
83
|
+
const rlKey = this.getKey(key)
|
|
84
|
+
const secDuration = this._getKeySecDuration(options)
|
|
85
|
+
let res = this.memoryStorage.incrby(rlKey, pointsToConsume, secDuration)
|
|
86
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0)
|
|
87
|
+
|
|
88
|
+
if (res.consumedPoints > this.points) {
|
|
89
|
+
// Block only first time when consumed more than points
|
|
90
|
+
if (this.blockDuration > 0 && res.consumedPoints <= (this.points + pointsToConsume)) {
|
|
91
|
+
// Block key
|
|
92
|
+
res = this.memoryStorage.set(rlKey, res.consumedPoints, this.blockDuration)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new CodeError('Rate limit exceeded', 'ERR_RATE_LIMIT_EXCEEDED', res)
|
|
96
|
+
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
|
|
97
|
+
// Execute evenly
|
|
98
|
+
let delayMs = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2))
|
|
99
|
+
if (delayMs < this.execEvenlyMinDelayMs) {
|
|
100
|
+
delayMs = res.consumedPoints * this.execEvenlyMinDelayMs
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await delay(delayMs)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return res
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
penalty (key: string, points: number = 1, options: GetKeySecDurationOptions = {}): RateLimiterResult {
|
|
110
|
+
const rlKey = this.getKey(key)
|
|
111
|
+
const secDuration = this._getKeySecDuration(options)
|
|
112
|
+
const res = this.memoryStorage.incrby(rlKey, points, secDuration)
|
|
113
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0)
|
|
114
|
+
|
|
115
|
+
return res
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
reward (key: string, points: number = 1, options: GetKeySecDurationOptions = {}): RateLimiterResult {
|
|
119
|
+
const rlKey = this.getKey(key)
|
|
120
|
+
const secDuration = this._getKeySecDuration(options)
|
|
121
|
+
const res = this.memoryStorage.incrby(rlKey, -points, secDuration)
|
|
122
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0)
|
|
123
|
+
|
|
124
|
+
return res
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Block any key for secDuration seconds
|
|
129
|
+
*
|
|
130
|
+
* @param key
|
|
131
|
+
* @param secDuration
|
|
132
|
+
*/
|
|
133
|
+
block (key: string, secDuration: number): RateLimiterResult {
|
|
134
|
+
const msDuration = secDuration * 1000
|
|
135
|
+
const initPoints = this.points + 1
|
|
136
|
+
|
|
137
|
+
this.memoryStorage.set(this.getKey(key), initPoints, secDuration)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
remainingPoints: 0,
|
|
141
|
+
msBeforeNext: msDuration === 0 ? -1 : msDuration,
|
|
142
|
+
consumedPoints: initPoints,
|
|
143
|
+
isFirstInDuration: false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
set (key: string, points: number, secDuration: number = 0): RateLimiterResult {
|
|
148
|
+
const msDuration = (secDuration >= 0 ? secDuration : this.duration) * 1000
|
|
149
|
+
|
|
150
|
+
this.memoryStorage.set(this.getKey(key), points, secDuration)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
remainingPoints: 0,
|
|
154
|
+
msBeforeNext: msDuration === 0 ? -1 : msDuration,
|
|
155
|
+
consumedPoints: points,
|
|
156
|
+
isFirstInDuration: false
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get (key: string): RateLimiterResult | undefined {
|
|
161
|
+
const res = this.memoryStorage.get(this.getKey(key))
|
|
162
|
+
|
|
163
|
+
if (res != null) {
|
|
164
|
+
res.remainingPoints = Math.max(this.points - res.consumedPoints, 0)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return res
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
delete (key: string): void {
|
|
171
|
+
this.memoryStorage.delete(this.getKey(key))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private _getKeySecDuration (options?: GetKeySecDurationOptions): number {
|
|
175
|
+
if (options?.customDuration != null && options.customDuration >= 0) {
|
|
176
|
+
return options.customDuration
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return this.duration
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
getKey (key: string): string {
|
|
183
|
+
return this.keyPrefix.length > 0 ? `${this.keyPrefix}:${key}` : key
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
parseKey (rlKey: string): string {
|
|
187
|
+
return rlKey.substring(this.keyPrefix.length)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
class MemoryStorage {
|
|
192
|
+
public readonly storage: Map<string, RateRecord>
|
|
193
|
+
|
|
194
|
+
constructor () {
|
|
195
|
+
this.storage = new Map()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
incrby (key: string, value: number, durationSec: number): RateLimiterResult {
|
|
199
|
+
const existing = this.storage.get(key)
|
|
200
|
+
|
|
201
|
+
if (existing != null) {
|
|
202
|
+
const msBeforeExpires = existing.expiresAt != null
|
|
203
|
+
? existing.expiresAt.getTime() - new Date().getTime()
|
|
204
|
+
: -1
|
|
205
|
+
|
|
206
|
+
if (existing.expiresAt == null || msBeforeExpires > 0) {
|
|
207
|
+
// Change value
|
|
208
|
+
existing.value += value
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
remainingPoints: 0,
|
|
212
|
+
msBeforeNext: msBeforeExpires,
|
|
213
|
+
consumedPoints: existing.value,
|
|
214
|
+
isFirstInDuration: false
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return this.set(key, value, durationSec)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return this.set(key, value, durationSec)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
set (key: string, value: number, durationSec: number): RateLimiterResult {
|
|
225
|
+
const durationMs = durationSec * 1000
|
|
226
|
+
const existing = this.storage.get(key)
|
|
227
|
+
|
|
228
|
+
if (existing != null) {
|
|
229
|
+
clearTimeout(existing.timeoutId)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const record: RateRecord = {
|
|
233
|
+
value,
|
|
234
|
+
expiresAt: durationMs > 0 ? new Date(Date.now() + durationMs) : undefined
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.storage.set(key, record)
|
|
238
|
+
|
|
239
|
+
if (durationMs > 0) {
|
|
240
|
+
record.timeoutId = setTimeout(() => {
|
|
241
|
+
this.storage.delete(key)
|
|
242
|
+
}, durationMs)
|
|
243
|
+
|
|
244
|
+
if (record.timeoutId.unref != null) {
|
|
245
|
+
record.timeoutId.unref()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
remainingPoints: 0,
|
|
251
|
+
msBeforeNext: durationMs === 0 ? -1 : durationMs,
|
|
252
|
+
consumedPoints: record.value,
|
|
253
|
+
isFirstInDuration: true
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
get (key: string): RateLimiterResult | undefined {
|
|
258
|
+
const existing = this.storage.get(key)
|
|
259
|
+
|
|
260
|
+
if (existing != null) {
|
|
261
|
+
const msBeforeExpires = existing.expiresAt != null
|
|
262
|
+
? existing.expiresAt.getTime() - new Date().getTime()
|
|
263
|
+
: -1
|
|
264
|
+
return {
|
|
265
|
+
remainingPoints: 0,
|
|
266
|
+
msBeforeNext: msBeforeExpires,
|
|
267
|
+
consumedPoints: existing.value,
|
|
268
|
+
isFirstInDuration: false
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
delete (key: string): boolean {
|
|
274
|
+
const record = this.storage.get(key)
|
|
275
|
+
|
|
276
|
+
if (record != null) {
|
|
277
|
+
if (record.timeoutId != null) {
|
|
278
|
+
clearTimeout(record.timeoutId)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.storage.delete(key)
|
|
282
|
+
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
}
|