express-rate-limit 6.5.2 → 6.7.0

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/changelog.md CHANGED
@@ -6,21 +6,50 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
- ## [6.5.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.0)
9
+ ## [6.7.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.0)
10
10
 
11
- ## Changed
11
+ ### Changed
12
+
13
+ - Updated links to point to new express-rate-limit organization on GitHub.
14
+ - Added advertisement to Readme for project sponsor
15
+ [Zuplo](https://zuplo.link/express-rate-limit).
16
+ - Updated TypeScript version and other dev dependencies
17
+ - Changed CI test suite: dropped node.js 12, added node.js 19
18
+
19
+ No functional changes.
20
+
21
+ ## [6.6.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.6.0)
22
+
23
+ ### Added
24
+
25
+ - Added `shutdown` method to the Store interface and the MemoryStore.
26
+
27
+ ## [6.5.2](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.2)
28
+
29
+ ### Fixed
30
+
31
+ - Fixed an issue with missing types in ESM monorepos.
32
+
33
+ ## [6.5.1](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.1)
34
+
35
+ ### Added
12
36
 
13
37
  - The message option can now be a (sync/asynx) function that returns a value
14
38
  (#311)
39
+
40
+ ### Changed
41
+
15
42
  - Updated all dependencies
16
43
 
44
+ Note: 6.5.0 was not released due to CI automation issues.
45
+
17
46
  ## [6.4.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.3.0)
18
47
 
19
48
  ### Added
20
49
 
21
50
  - Adds Express 5 (`5.0.0-beta.1`) as a supported peer dependency (#304)
22
51
 
23
- ## Changed
52
+ ### Changed
24
53
 
25
54
  - Tests are now run on Node 12, 14, 16 and 18 on CI (#305)
26
55
  - Updated all development dependencies (#306)
package/dist/index.cjs CHANGED
@@ -37,12 +37,11 @@ var MemoryStore = class {
37
37
  this.windowMs = options.windowMs;
38
38
  this.resetTime = calculateNextResetTime(this.windowMs);
39
39
  this.hits = {};
40
- const interval = setInterval(async () => {
40
+ this.interval = setInterval(async () => {
41
41
  await this.resetAll();
42
42
  }, this.windowMs);
43
- if (interval.unref) {
44
- interval.unref();
45
- }
43
+ if (this.interval.unref)
44
+ this.interval.unref();
46
45
  }
47
46
  async increment(key) {
48
47
  var _a;
@@ -55,9 +54,8 @@ var MemoryStore = class {
55
54
  }
56
55
  async decrement(key) {
57
56
  const current = this.hits[key];
58
- if (current) {
57
+ if (current)
59
58
  this.hits[key] = current - 1;
60
- }
61
59
  }
62
60
  async resetKey(key) {
63
61
  delete this.hits[key];
@@ -66,6 +64,9 @@ var MemoryStore = class {
66
64
  this.hits = {};
67
65
  this.resetTime = calculateNextResetTime(this.windowMs);
68
66
  }
67
+ shutdown() {
68
+ clearInterval(this.interval);
69
+ }
69
70
  };
70
71
 
71
72
  // source/lib.ts
@@ -78,11 +79,14 @@ var promisifyStore = (passedStore) => {
78
79
  class PromisifiedStore {
79
80
  async increment(key) {
80
81
  return new Promise((resolve, reject) => {
81
- legacyStore.incr(key, (error, totalHits, resetTime) => {
82
- if (error)
83
- reject(error);
84
- resolve({ totalHits, resetTime });
85
- });
82
+ legacyStore.incr(
83
+ key,
84
+ (error, totalHits, resetTime) => {
85
+ if (error)
86
+ reject(error);
87
+ resolve({ totalHits, resetTime });
88
+ }
89
+ );
86
90
  });
87
91
  }
88
92
  async decrement(key) {
@@ -115,13 +119,18 @@ var parseOptions = (passedOptions) => {
115
119
  skip: (_request, _response) => false,
116
120
  keyGenerator(request, _response) {
117
121
  if (!request.ip) {
118
- console.error("WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue.");
122
+ console.error(
123
+ "WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue."
124
+ );
119
125
  }
120
126
  return request.ip;
121
127
  },
122
128
  async handler(request, response, _next, _optionsUsed) {
123
129
  response.status(config.statusCode);
124
- const message = typeof config.message === "function" ? await config.message(request, response) : config.message;
130
+ const message = typeof config.message === "function" ? await config.message(
131
+ request,
132
+ response
133
+ ) : config.message;
125
134
  if (!response.writableEnded) {
126
135
  response.send(message != null ? message : "Too many requests, please try again later.");
127
136
  }
@@ -132,7 +141,9 @@ var parseOptions = (passedOptions) => {
132
141
  store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
133
142
  };
134
143
  if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
135
- throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
144
+ throw new TypeError(
145
+ "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
146
+ );
136
147
  }
137
148
  return config;
138
149
  };
@@ -147,79 +158,92 @@ var rateLimit = (passedOptions) => {
147
158
  const options = parseOptions(passedOptions != null ? passedOptions : {});
148
159
  if (typeof options.store.init === "function")
149
160
  options.store.init(options);
150
- const middleware = handleAsyncErrors(async (request, response, next) => {
151
- const skip = await options.skip(request, response);
152
- if (skip) {
153
- next();
154
- return;
155
- }
156
- const augmentedRequest = request;
157
- const key = await options.keyGenerator(request, response);
158
- const { totalHits, resetTime } = await options.store.increment(key);
159
- const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
160
- const maxHits = await retrieveQuota;
161
- augmentedRequest[options.requestPropertyName] = {
162
- limit: maxHits,
163
- current: totalHits,
164
- remaining: Math.max(maxHits - totalHits, 0),
165
- resetTime
166
- };
167
- if (options.legacyHeaders && !response.headersSent) {
168
- response.setHeader("X-RateLimit-Limit", maxHits);
169
- response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
170
- if (resetTime instanceof Date) {
171
- response.setHeader("Date", new Date().toUTCString());
172
- response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
161
+ const middleware = handleAsyncErrors(
162
+ async (request, response, next) => {
163
+ const skip = await options.skip(request, response);
164
+ if (skip) {
165
+ next();
166
+ return;
173
167
  }
174
- }
175
- if (options.standardHeaders && !response.headersSent) {
176
- response.setHeader("RateLimit-Limit", maxHits);
177
- response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
178
- if (resetTime) {
179
- const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
180
- response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
168
+ const augmentedRequest = request;
169
+ const key = await options.keyGenerator(request, response);
170
+ const { totalHits, resetTime } = await options.store.increment(key);
171
+ const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
172
+ const maxHits = await retrieveQuota;
173
+ augmentedRequest[options.requestPropertyName] = {
174
+ limit: maxHits,
175
+ current: totalHits,
176
+ remaining: Math.max(maxHits - totalHits, 0),
177
+ resetTime
178
+ };
179
+ if (options.legacyHeaders && !response.headersSent) {
180
+ response.setHeader("X-RateLimit-Limit", maxHits);
181
+ response.setHeader(
182
+ "X-RateLimit-Remaining",
183
+ augmentedRequest[options.requestPropertyName].remaining
184
+ );
185
+ if (resetTime instanceof Date) {
186
+ response.setHeader("Date", new Date().toUTCString());
187
+ response.setHeader(
188
+ "X-RateLimit-Reset",
189
+ Math.ceil(resetTime.getTime() / 1e3)
190
+ );
191
+ }
181
192
  }
182
- }
183
- if (options.skipFailedRequests || options.skipSuccessfulRequests) {
184
- let decremented = false;
185
- const decrementKey = async () => {
186
- if (!decremented) {
187
- await options.store.decrement(key);
188
- decremented = true;
193
+ if (options.standardHeaders && !response.headersSent) {
194
+ response.setHeader("RateLimit-Limit", maxHits);
195
+ response.setHeader(
196
+ "RateLimit-Remaining",
197
+ augmentedRequest[options.requestPropertyName].remaining
198
+ );
199
+ if (resetTime) {
200
+ const deltaSeconds = Math.ceil(
201
+ (resetTime.getTime() - Date.now()) / 1e3
202
+ );
203
+ response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
189
204
  }
190
- };
191
- if (options.skipFailedRequests) {
192
- response.on("finish", async () => {
193
- if (!options.requestWasSuccessful(request, response))
194
- await decrementKey();
195
- });
196
- response.on("close", async () => {
197
- if (!response.writableEnded)
198
- await decrementKey();
199
- });
200
- response.on("error", async () => {
201
- await decrementKey();
202
- });
203
205
  }
204
- if (options.skipSuccessfulRequests) {
205
- response.on("finish", async () => {
206
- if (options.requestWasSuccessful(request, response))
206
+ if (options.skipFailedRequests || options.skipSuccessfulRequests) {
207
+ let decremented = false;
208
+ const decrementKey = async () => {
209
+ if (!decremented) {
210
+ await options.store.decrement(key);
211
+ decremented = true;
212
+ }
213
+ };
214
+ if (options.skipFailedRequests) {
215
+ response.on("finish", async () => {
216
+ if (!options.requestWasSuccessful(request, response))
217
+ await decrementKey();
218
+ });
219
+ response.on("close", async () => {
220
+ if (!response.writableEnded)
221
+ await decrementKey();
222
+ });
223
+ response.on("error", async () => {
207
224
  await decrementKey();
208
- });
225
+ });
226
+ }
227
+ if (options.skipSuccessfulRequests) {
228
+ response.on("finish", async () => {
229
+ if (options.requestWasSuccessful(request, response))
230
+ await decrementKey();
231
+ });
232
+ }
209
233
  }
210
- }
211
- if (maxHits && totalHits === maxHits + 1) {
212
- options.onLimitReached(request, response, options);
213
- }
214
- if (maxHits && totalHits > maxHits) {
215
- if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
216
- response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
234
+ if (maxHits && totalHits === maxHits + 1) {
235
+ options.onLimitReached(request, response, options);
217
236
  }
218
- options.handler(request, response, next, options);
219
- return;
237
+ if (maxHits && totalHits > maxHits) {
238
+ if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
239
+ response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
240
+ }
241
+ options.handler(request, response, next, options);
242
+ return;
243
+ }
244
+ next();
220
245
  }
221
- next();
222
- });
246
+ );
223
247
  middleware.resetKey = options.store.resetKey.bind(options.store);
224
248
  return middleware;
225
249
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated by dts-bundle-generator v6.12.0
1
+ // Generated by dts-bundle-generator v7.0.0
2
2
 
3
3
  import { NextFunction, Request, RequestHandler, Response } from 'express';
4
4
 
@@ -66,7 +66,7 @@ export declare type RateLimitRequestHandler = RequestHandler & {
66
66
  *
67
67
  * @deprecated 6.x - Implement the `Store` interface instead.
68
68
  */
69
- export interface LegacyStore {
69
+ export declare type LegacyStore = {
70
70
  /**
71
71
  * Method to increment a client's hit counter.
72
72
  *
@@ -90,11 +90,11 @@ export interface LegacyStore {
90
90
  * Method to reset everyone's hit counter.
91
91
  */
92
92
  resetAll?: () => void;
93
- }
93
+ };
94
94
  /**
95
95
  * An interface that all hit counter stores must implement.
96
96
  */
97
- export interface Store {
97
+ export declare type Store = {
98
98
  /**
99
99
  * Method that initializes the store, and has access to the options passed to
100
100
  * the middleware too.
@@ -126,11 +126,15 @@ export interface Store {
126
126
  * Method to reset everyone's hit counter.
127
127
  */
128
128
  resetAll?: () => Promise<void> | void;
129
- }
129
+ /**
130
+ * Method to shutdown the store, stop timers, and release all resources.
131
+ */
132
+ shutdown?: () => Promise<void> | void;
133
+ };
130
134
  /**
131
135
  * The configuration options for the rate limiter.
132
136
  */
133
- export interface Options {
137
+ export declare type Options = {
134
138
  /**
135
139
  * How long we should remember the requests.
136
140
  *
@@ -248,7 +252,7 @@ export interface Options {
248
252
  * @deprecated 6.x - This option was renamed to `standardHeaders`.
249
253
  */
250
254
  draft_polli_ratelimit_headers?: boolean;
251
- }
255
+ };
252
256
  /**
253
257
  * The extended request object that includes information about the client's
254
258
  * rate limit.
@@ -260,12 +264,12 @@ export declare type AugmentedRequest = Request & {
260
264
  * The rate limit related information for each client included in the
261
265
  * Express request object.
262
266
  */
263
- export interface RateLimitInfo {
267
+ export declare type RateLimitInfo = {
264
268
  readonly limit: number;
265
269
  readonly current: number;
266
270
  readonly remaining: number;
267
271
  readonly resetTime: Date | undefined;
268
- }
272
+ };
269
273
  /**
270
274
  *
271
275
  * Create an instance of IP rate-limiting middleware for Express.
@@ -297,6 +301,10 @@ export declare class MemoryStore implements Store {
297
301
  * The time at which all hit counts will be reset.
298
302
  */
299
303
  resetTime: Date;
304
+ /**
305
+ * Reference to the active timer.
306
+ */
307
+ interval?: NodeJS.Timer;
300
308
  /**
301
309
  * Method that initializes the store.
302
310
  *
@@ -335,6 +343,13 @@ export declare class MemoryStore implements Store {
335
343
  * @public
336
344
  */
337
345
  resetAll(): Promise<void>;
346
+ /**
347
+ * Method to stop the timer (if currently running) and prevent any memory
348
+ * leaks.
349
+ *
350
+ * @public
351
+ */
352
+ shutdown(): void;
338
353
  }
339
354
 
340
355
  export {
package/dist/index.mjs CHANGED
@@ -9,12 +9,11 @@ var MemoryStore = class {
9
9
  this.windowMs = options.windowMs;
10
10
  this.resetTime = calculateNextResetTime(this.windowMs);
11
11
  this.hits = {};
12
- const interval = setInterval(async () => {
12
+ this.interval = setInterval(async () => {
13
13
  await this.resetAll();
14
14
  }, this.windowMs);
15
- if (interval.unref) {
16
- interval.unref();
17
- }
15
+ if (this.interval.unref)
16
+ this.interval.unref();
18
17
  }
19
18
  async increment(key) {
20
19
  var _a;
@@ -27,9 +26,8 @@ var MemoryStore = class {
27
26
  }
28
27
  async decrement(key) {
29
28
  const current = this.hits[key];
30
- if (current) {
29
+ if (current)
31
30
  this.hits[key] = current - 1;
32
- }
33
31
  }
34
32
  async resetKey(key) {
35
33
  delete this.hits[key];
@@ -38,6 +36,9 @@ var MemoryStore = class {
38
36
  this.hits = {};
39
37
  this.resetTime = calculateNextResetTime(this.windowMs);
40
38
  }
39
+ shutdown() {
40
+ clearInterval(this.interval);
41
+ }
41
42
  };
42
43
 
43
44
  // source/lib.ts
@@ -50,11 +51,14 @@ var promisifyStore = (passedStore) => {
50
51
  class PromisifiedStore {
51
52
  async increment(key) {
52
53
  return new Promise((resolve, reject) => {
53
- legacyStore.incr(key, (error, totalHits, resetTime) => {
54
- if (error)
55
- reject(error);
56
- resolve({ totalHits, resetTime });
57
- });
54
+ legacyStore.incr(
55
+ key,
56
+ (error, totalHits, resetTime) => {
57
+ if (error)
58
+ reject(error);
59
+ resolve({ totalHits, resetTime });
60
+ }
61
+ );
58
62
  });
59
63
  }
60
64
  async decrement(key) {
@@ -87,13 +91,18 @@ var parseOptions = (passedOptions) => {
87
91
  skip: (_request, _response) => false,
88
92
  keyGenerator(request, _response) {
89
93
  if (!request.ip) {
90
- console.error("WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue.");
94
+ console.error(
95
+ "WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue."
96
+ );
91
97
  }
92
98
  return request.ip;
93
99
  },
94
100
  async handler(request, response, _next, _optionsUsed) {
95
101
  response.status(config.statusCode);
96
- const message = typeof config.message === "function" ? await config.message(request, response) : config.message;
102
+ const message = typeof config.message === "function" ? await config.message(
103
+ request,
104
+ response
105
+ ) : config.message;
97
106
  if (!response.writableEnded) {
98
107
  response.send(message != null ? message : "Too many requests, please try again later.");
99
108
  }
@@ -104,7 +113,9 @@ var parseOptions = (passedOptions) => {
104
113
  store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
105
114
  };
106
115
  if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
107
- throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
116
+ throw new TypeError(
117
+ "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
118
+ );
108
119
  }
109
120
  return config;
110
121
  };
@@ -119,79 +130,92 @@ var rateLimit = (passedOptions) => {
119
130
  const options = parseOptions(passedOptions != null ? passedOptions : {});
120
131
  if (typeof options.store.init === "function")
121
132
  options.store.init(options);
122
- const middleware = handleAsyncErrors(async (request, response, next) => {
123
- const skip = await options.skip(request, response);
124
- if (skip) {
125
- next();
126
- return;
127
- }
128
- const augmentedRequest = request;
129
- const key = await options.keyGenerator(request, response);
130
- const { totalHits, resetTime } = await options.store.increment(key);
131
- const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
132
- const maxHits = await retrieveQuota;
133
- augmentedRequest[options.requestPropertyName] = {
134
- limit: maxHits,
135
- current: totalHits,
136
- remaining: Math.max(maxHits - totalHits, 0),
137
- resetTime
138
- };
139
- if (options.legacyHeaders && !response.headersSent) {
140
- response.setHeader("X-RateLimit-Limit", maxHits);
141
- response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
142
- if (resetTime instanceof Date) {
143
- response.setHeader("Date", new Date().toUTCString());
144
- response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
133
+ const middleware = handleAsyncErrors(
134
+ async (request, response, next) => {
135
+ const skip = await options.skip(request, response);
136
+ if (skip) {
137
+ next();
138
+ return;
145
139
  }
146
- }
147
- if (options.standardHeaders && !response.headersSent) {
148
- response.setHeader("RateLimit-Limit", maxHits);
149
- response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
150
- if (resetTime) {
151
- const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
152
- response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
140
+ const augmentedRequest = request;
141
+ const key = await options.keyGenerator(request, response);
142
+ const { totalHits, resetTime } = await options.store.increment(key);
143
+ const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
144
+ const maxHits = await retrieveQuota;
145
+ augmentedRequest[options.requestPropertyName] = {
146
+ limit: maxHits,
147
+ current: totalHits,
148
+ remaining: Math.max(maxHits - totalHits, 0),
149
+ resetTime
150
+ };
151
+ if (options.legacyHeaders && !response.headersSent) {
152
+ response.setHeader("X-RateLimit-Limit", maxHits);
153
+ response.setHeader(
154
+ "X-RateLimit-Remaining",
155
+ augmentedRequest[options.requestPropertyName].remaining
156
+ );
157
+ if (resetTime instanceof Date) {
158
+ response.setHeader("Date", new Date().toUTCString());
159
+ response.setHeader(
160
+ "X-RateLimit-Reset",
161
+ Math.ceil(resetTime.getTime() / 1e3)
162
+ );
163
+ }
153
164
  }
154
- }
155
- if (options.skipFailedRequests || options.skipSuccessfulRequests) {
156
- let decremented = false;
157
- const decrementKey = async () => {
158
- if (!decremented) {
159
- await options.store.decrement(key);
160
- decremented = true;
165
+ if (options.standardHeaders && !response.headersSent) {
166
+ response.setHeader("RateLimit-Limit", maxHits);
167
+ response.setHeader(
168
+ "RateLimit-Remaining",
169
+ augmentedRequest[options.requestPropertyName].remaining
170
+ );
171
+ if (resetTime) {
172
+ const deltaSeconds = Math.ceil(
173
+ (resetTime.getTime() - Date.now()) / 1e3
174
+ );
175
+ response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
161
176
  }
162
- };
163
- if (options.skipFailedRequests) {
164
- response.on("finish", async () => {
165
- if (!options.requestWasSuccessful(request, response))
166
- await decrementKey();
167
- });
168
- response.on("close", async () => {
169
- if (!response.writableEnded)
170
- await decrementKey();
171
- });
172
- response.on("error", async () => {
173
- await decrementKey();
174
- });
175
177
  }
176
- if (options.skipSuccessfulRequests) {
177
- response.on("finish", async () => {
178
- if (options.requestWasSuccessful(request, response))
178
+ if (options.skipFailedRequests || options.skipSuccessfulRequests) {
179
+ let decremented = false;
180
+ const decrementKey = async () => {
181
+ if (!decremented) {
182
+ await options.store.decrement(key);
183
+ decremented = true;
184
+ }
185
+ };
186
+ if (options.skipFailedRequests) {
187
+ response.on("finish", async () => {
188
+ if (!options.requestWasSuccessful(request, response))
189
+ await decrementKey();
190
+ });
191
+ response.on("close", async () => {
192
+ if (!response.writableEnded)
193
+ await decrementKey();
194
+ });
195
+ response.on("error", async () => {
179
196
  await decrementKey();
180
- });
197
+ });
198
+ }
199
+ if (options.skipSuccessfulRequests) {
200
+ response.on("finish", async () => {
201
+ if (options.requestWasSuccessful(request, response))
202
+ await decrementKey();
203
+ });
204
+ }
181
205
  }
182
- }
183
- if (maxHits && totalHits === maxHits + 1) {
184
- options.onLimitReached(request, response, options);
185
- }
186
- if (maxHits && totalHits > maxHits) {
187
- if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
188
- response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
206
+ if (maxHits && totalHits === maxHits + 1) {
207
+ options.onLimitReached(request, response, options);
189
208
  }
190
- options.handler(request, response, next, options);
191
- return;
209
+ if (maxHits && totalHits > maxHits) {
210
+ if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
211
+ response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
212
+ }
213
+ options.handler(request, response, next, options);
214
+ return;
215
+ }
216
+ next();
192
217
  }
193
- next();
194
- });
218
+ );
195
219
  middleware.resetKey = options.store.resetKey.bind(options.store);
196
220
  return middleware;
197
221
  };
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "6.5.2",
3
+ "version": "6.7.0",
4
4
  "description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
5
5
  "author": {
6
6
  "name": "Nathan Friedly",
7
7
  "url": "http://nfriedly.com/"
8
8
  },
9
9
  "license": "MIT",
10
- "homepage": "https://github.com/nfriedly/express-rate-limit",
11
- "repository": "https://github.com/nfriedly/express-rate-limit",
10
+ "homepage": "https://github.com/express-rate-limit/express-rate-limit",
11
+ "repository": "https://github.com/express-rate-limit/express-rate-limit",
12
12
  "keywords": [
13
13
  "express-rate-limit",
14
14
  "express",
@@ -71,24 +71,24 @@
71
71
  "express": "^4 || ^5"
72
72
  },
73
73
  "devDependencies": {
74
- "@jest/globals": "28.1.3",
75
- "@types/express": "4.17.13",
76
- "@types/jest": "28.1.6",
77
- "@types/node": "18.0.6",
74
+ "@jest/globals": "29.3.1",
75
+ "@types/express": "4.17.14",
76
+ "@types/jest": "29.2.3",
77
+ "@types/node": "18.11.9",
78
78
  "@types/supertest": "2.0.12",
79
79
  "cross-env": "7.0.3",
80
- "del-cli": "4.0.1",
81
- "dts-bundle-generator": "6.12.0",
82
- "esbuild": "0.14.49",
83
- "express": "4.18.1",
84
- "husky": "8.0.1",
85
- "jest": "28.1.3",
80
+ "del-cli": "5.0.0",
81
+ "dts-bundle-generator": "7.0.0",
82
+ "esbuild": "0.15.14",
83
+ "express": "4.18.2",
84
+ "husky": "8.0.2",
85
+ "jest": "29.3.1",
86
86
  "lint-staged": "13.0.3",
87
87
  "npm-run-all": "4.1.5",
88
- "supertest": "6.2.4",
89
- "ts-jest": "28.0.7",
88
+ "supertest": "6.3.1",
89
+ "ts-jest": "29.0.3",
90
90
  "ts-node": "10.9.1",
91
- "typescript": "4.7.4",
91
+ "typescript": "4.8.4",
92
92
  "xo": "0.49.0"
93
93
  },
94
94
  "xo": {
@@ -100,7 +100,8 @@
100
100
  "@typescript-eslint/consistent-indexed-object-style": [
101
101
  "error",
102
102
  "index-signature"
103
- ]
103
+ ],
104
+ "n/no-unsupported-features/es-syntax": 0
104
105
  }
105
106
  },
106
107
  "prettier": {
package/readme.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # <div align="center"> Express Rate Limit </div>
2
2
 
3
+ ---
4
+
5
+ Sponsored by [Zuplo](https://zuplo.link/express-rate-limit) a fully-managed API
6
+ Gateway for developers. Add
7
+ [dynamic rate-limiting](https://zuplo.link/dynamic-rate-limiting),
8
+ authentication and more to any API in minutes. Learn more at
9
+ [zuplo.com](https://zuplo.link/express-rate-limit)
10
+
11
+ ---
12
+
3
13
  <div align="center">
4
14
 
5
- [![Tests](https://github.com/nfriedly/express-rate-limit/workflows/Test/badge.svg)](https://github.com/nfriedly/express-rate-limit/actions)
15
+ [![Tests](https://github.com/express-rate-limit/express-rate-limit/workflows/Test/badge.svg)](https://github.com/express-rate-limit/express-rate-limit/actions)
6
16
  [![npm version](https://img.shields.io/npm/v/express-rate-limit.svg)](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
7
17
  [![npm downloads](https://img.shields.io/npm/dm/express-rate-limit)](https://www.npmjs.com/package/express-rate-limit)
8
18
 
@@ -41,9 +51,9 @@ From Github Releases:
41
51
 
42
52
  ```sh
43
53
  # Using npm
44
- > npm install https://github.com/nfriedly/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
54
+ > npm install https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
45
55
  # Using yarn or pnpm
46
- > yarn/pnpm add https://github.com/nfriedly/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
56
+ > yarn/pnpm add https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
47
57
  ```
48
58
 
49
59
  Replace `{version}` with the version of the package that you want to your, e.g.:
@@ -180,10 +190,9 @@ app.get('/ip', (request, response) => response.send(request.ip))
180
190
  ```
181
191
 
182
192
  Go to `/ip` and see the IP address returned in the response. If it matches your
183
- IP address (which you can get by going to http://ip.nfriedly.com/ or
184
- https://api.ipify.org/), then the number of proxies is correct and the rate
185
- limiter should now work correctly. If not, then keep increasing the number until
186
- it does.
193
+ public IP address, then the number of proxies is correct and the rate limiter
194
+ should now work correctly. If not, then keep increasing the number until it
195
+ does.
187
196
 
188
197
  For more information about the `trust proxy` setting, take a look at the
189
198
  [official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
@@ -387,7 +396,7 @@ objects that is called when a client has reached their rate limit, and will be
387
396
  rate limited on their next request.
388
397
 
389
398
  This method was
390
- [deprecated in v6](https://github.com/nfriedly/express-rate-limit/releases/v6.0.0) -
399
+ [deprecated in v6](https://github.com/express-rate-limit/express-rate-limit/releases/v6.0.0) -
391
400
  Please use a custom `handler` that checks the number of hits instead.
392
401
 
393
402
  ### `skip`
@@ -456,7 +465,7 @@ Here is a list of external stores:
456
465
  | [`precise-memory-rate-limit`](https://www.npmjs.com/package/precise-memory-rate-limit) | A memory store similar to the built-in one, except that it stores a distinct timestamp for each key. | Legacy |
457
466
 
458
467
  Take a look at
459
- [this guide](https://github.com/nfriedly/express-rate-limit/wiki/Creating-Your-Own-Store)
468
+ [this guide](https://github.com/express-rate-limit/express-rate-limit/wiki/Creating-Your-Own-Store)
460
469
  if you wish to create your own store.
461
470
 
462
471
  ## Request API
@@ -480,9 +489,10 @@ method.
480
489
  ## Issues and Contributing
481
490
 
482
491
  If you encounter a bug or want to see something added/changed, please go ahead
483
- and [open an issue](https://github.com/nfriedly/express-rate-limit/issues/new)!
492
+ and
493
+ [open an issue](https://github.com/nfriexpress-rate-limitedly/express-rate-limit/issues/new)!
484
494
  If you need help with something, feel free to
485
- [start a discussion](https://github.com/nfriedly/express-rate-limit/discussions/new)!
495
+ [start a discussion](https://github.com/express-rate-limit/express-rate-limit/discussions/new)!
486
496
 
487
497
  If you wish to contribute to the library, thanks! First, please read
488
498
  [the contributing guide](contributing.md). Then you can pick up any issue and