express-rate-limit 6.6.0 → 6.7.1

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,66 @@ 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.1](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.1)
10
10
 
11
- ## Changed
11
+ ### Fixed
12
+
13
+ - Fixed compatibility with TypeScript's TypeScript new `node16` module
14
+ resolution strategy (See
15
+ [#355](https://github.com/express-rate-limit/express-rate-limit/issues/355))
16
+
17
+ ### Changed
18
+
19
+ - Bumped development dependencies.
20
+ - Added `node` 20 to list of versions the CI jobs run on.
21
+
22
+ No functional changes.
23
+
24
+ ## [6.7.0](https://github.com/express-rate-limit/express-rate-limit/releases/tag/v6.7.0)
25
+
26
+ ### Changed
27
+
28
+ - Updated links to point to the new `express-rate-limit` organization on GitHub.
29
+ - Added advertisement to `readme.md` for project sponsor
30
+ [Zuplo](https://zuplo.link/express-rate-limit).
31
+ - Updated to `typescript` version 5 and bumped other dependencies.
32
+ - Dropped `node` 12, and added `node` 19 to the list of versions the CI jobs run
33
+ on.
34
+
35
+ No functional changes.
36
+
37
+ ## [6.6.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.6.0)
38
+
39
+ ### Added
40
+
41
+ - Added `shutdown` method to the Store interface and the MemoryStore.
42
+
43
+ ## [6.5.2](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.2)
44
+
45
+ ### Fixed
46
+
47
+ - Fixed an issue with missing types in ESM monorepos.
48
+
49
+ ## [6.5.1](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.5.1)
50
+
51
+ ### Added
12
52
 
13
53
  - The message option can now be a (sync/asynx) function that returns a value
14
54
  (#311)
55
+
56
+ ### Changed
57
+
15
58
  - Updated all dependencies
16
59
 
60
+ Note: 6.5.0 was not released due to CI automation issues.
61
+
17
62
  ## [6.4.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.3.0)
18
63
 
19
64
  ### Added
20
65
 
21
66
  - Adds Express 5 (`5.0.0-beta.1`) as a supported peer dependency (#304)
22
67
 
23
- ## Changed
68
+ ### Changed
24
69
 
25
70
  - Tests are now run on Node 12, 14, 16 and 18 on CI (#305)
26
71
  - Updated all development dependencies (#306)
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
7
  var __export = (target, all) => {
7
8
  for (var name in all)
8
9
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -16,6 +17,10 @@ var __copyProps = (to, from, except, desc) => {
16
17
  return to;
17
18
  };
18
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __publicField = (obj, key, value) => {
21
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
22
+ return value;
23
+ };
19
24
 
20
25
  // source/index.ts
21
26
  var source_exports = {};
@@ -28,11 +33,34 @@ module.exports = __toCommonJS(source_exports);
28
33
 
29
34
  // source/memory-store.ts
30
35
  var calculateNextResetTime = (windowMs) => {
31
- const resetTime = new Date();
36
+ const resetTime = /* @__PURE__ */ new Date();
32
37
  resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
33
38
  return resetTime;
34
39
  };
35
40
  var MemoryStore = class {
41
+ constructor() {
42
+ /**
43
+ * The duration of time before which all hit counts are reset (in milliseconds).
44
+ */
45
+ __publicField(this, "windowMs");
46
+ /**
47
+ * The map that stores the number of hits for each client in memory.
48
+ */
49
+ __publicField(this, "hits");
50
+ /**
51
+ * The time at which all hit counts will be reset.
52
+ */
53
+ __publicField(this, "resetTime");
54
+ /**
55
+ * Reference to the active timer.
56
+ */
57
+ __publicField(this, "interval");
58
+ }
59
+ /**
60
+ * Method that initializes the store.
61
+ *
62
+ * @param options {Options} - The options used to setup the middleware.
63
+ */
36
64
  init(options) {
37
65
  this.windowMs = options.windowMs;
38
66
  this.resetTime = calculateNextResetTime(this.windowMs);
@@ -43,6 +71,15 @@ var MemoryStore = class {
43
71
  if (this.interval.unref)
44
72
  this.interval.unref();
45
73
  }
74
+ /**
75
+ * Method to increment a client's hit counter.
76
+ *
77
+ * @param key {string} - The identifier for a client.
78
+ *
79
+ * @returns {IncrementResponse} - The number of hits and reset time for that client.
80
+ *
81
+ * @public
82
+ */
46
83
  async increment(key) {
47
84
  var _a;
48
85
  const totalHits = ((_a = this.hits[key]) != null ? _a : 0) + 1;
@@ -52,25 +89,54 @@ var MemoryStore = class {
52
89
  resetTime: this.resetTime
53
90
  };
54
91
  }
92
+ /**
93
+ * Method to decrement a client's hit counter.
94
+ *
95
+ * @param key {string} - The identifier for a client.
96
+ *
97
+ * @public
98
+ */
55
99
  async decrement(key) {
56
100
  const current = this.hits[key];
57
101
  if (current)
58
102
  this.hits[key] = current - 1;
59
103
  }
104
+ /**
105
+ * Method to reset a client's hit counter.
106
+ *
107
+ * @param key {string} - The identifier for a client.
108
+ *
109
+ * @public
110
+ */
60
111
  async resetKey(key) {
61
112
  delete this.hits[key];
62
113
  }
114
+ /**
115
+ * Method to reset everyone's hit counter.
116
+ *
117
+ * @public
118
+ */
63
119
  async resetAll() {
64
120
  this.hits = {};
65
121
  this.resetTime = calculateNextResetTime(this.windowMs);
66
122
  }
123
+ /**
124
+ * Method to stop the timer (if currently running) and prevent any memory
125
+ * leaks.
126
+ *
127
+ * @public
128
+ */
67
129
  shutdown() {
68
130
  clearInterval(this.interval);
69
131
  }
70
132
  };
71
133
 
72
134
  // source/lib.ts
73
- var isLegacyStore = (store) => typeof store.incr === "function" && typeof store.increment !== "function";
135
+ var isLegacyStore = (store) => (
136
+ // Check that `incr` exists but `increment` does not - store authors might want
137
+ // to keep both around for backwards compatibility.
138
+ typeof store.incr === "function" && typeof store.increment !== "function"
139
+ );
74
140
  var promisifyStore = (passedStore) => {
75
141
  if (!isLegacyStore(passedStore)) {
76
142
  return passedStore;
@@ -79,11 +145,14 @@ var promisifyStore = (passedStore) => {
79
145
  class PromisifiedStore {
80
146
  async increment(key) {
81
147
  return new Promise((resolve, reject) => {
82
- legacyStore.incr(key, (error, totalHits, resetTime) => {
83
- if (error)
84
- reject(error);
85
- resolve({ totalHits, resetTime });
86
- });
148
+ legacyStore.incr(
149
+ key,
150
+ (error, totalHits, resetTime) => {
151
+ if (error)
152
+ reject(error);
153
+ resolve({ totalHits, resetTime });
154
+ }
155
+ );
87
156
  });
88
157
  }
89
158
  async decrement(key) {
@@ -116,24 +185,34 @@ var parseOptions = (passedOptions) => {
116
185
  skip: (_request, _response) => false,
117
186
  keyGenerator(request, _response) {
118
187
  if (!request.ip) {
119
- 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.");
188
+ console.error(
189
+ "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."
190
+ );
120
191
  }
121
192
  return request.ip;
122
193
  },
123
194
  async handler(request, response, _next, _optionsUsed) {
124
195
  response.status(config.statusCode);
125
- const message = typeof config.message === "function" ? await config.message(request, response) : config.message;
196
+ const message = typeof config.message === "function" ? await config.message(
197
+ request,
198
+ response
199
+ ) : config.message;
126
200
  if (!response.writableEnded) {
127
201
  response.send(message != null ? message : "Too many requests, please try again later.");
128
202
  }
129
203
  },
130
204
  onLimitReached(_request, _response, _optionsUsed) {
131
205
  },
206
+ // Allow the options object to be overriden by the options passed to the middleware.
132
207
  ...notUndefinedOptions,
208
+ // Note that this field is declared after the user's options are spread in,
209
+ // so that this field doesn't get overriden with an un-promisified store!
133
210
  store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
134
211
  };
135
- 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") {
136
- throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
212
+ if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
213
+ throw new TypeError(
214
+ "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
215
+ );
137
216
  }
138
217
  return config;
139
218
  };
@@ -148,79 +227,92 @@ var rateLimit = (passedOptions) => {
148
227
  const options = parseOptions(passedOptions != null ? passedOptions : {});
149
228
  if (typeof options.store.init === "function")
150
229
  options.store.init(options);
151
- const middleware = handleAsyncErrors(async (request, response, next) => {
152
- const skip = await options.skip(request, response);
153
- if (skip) {
154
- next();
155
- return;
156
- }
157
- const augmentedRequest = request;
158
- const key = await options.keyGenerator(request, response);
159
- const { totalHits, resetTime } = await options.store.increment(key);
160
- const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
161
- const maxHits = await retrieveQuota;
162
- augmentedRequest[options.requestPropertyName] = {
163
- limit: maxHits,
164
- current: totalHits,
165
- remaining: Math.max(maxHits - totalHits, 0),
166
- resetTime
167
- };
168
- if (options.legacyHeaders && !response.headersSent) {
169
- response.setHeader("X-RateLimit-Limit", maxHits);
170
- response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
171
- if (resetTime instanceof Date) {
172
- response.setHeader("Date", new Date().toUTCString());
173
- response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
230
+ const middleware = handleAsyncErrors(
231
+ async (request, response, next) => {
232
+ const skip = await options.skip(request, response);
233
+ if (skip) {
234
+ next();
235
+ return;
174
236
  }
175
- }
176
- if (options.standardHeaders && !response.headersSent) {
177
- response.setHeader("RateLimit-Limit", maxHits);
178
- response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
179
- if (resetTime) {
180
- const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
181
- response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
237
+ const augmentedRequest = request;
238
+ const key = await options.keyGenerator(request, response);
239
+ const { totalHits, resetTime } = await options.store.increment(key);
240
+ const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
241
+ const maxHits = await retrieveQuota;
242
+ augmentedRequest[options.requestPropertyName] = {
243
+ limit: maxHits,
244
+ current: totalHits,
245
+ remaining: Math.max(maxHits - totalHits, 0),
246
+ resetTime
247
+ };
248
+ if (options.legacyHeaders && !response.headersSent) {
249
+ response.setHeader("X-RateLimit-Limit", maxHits);
250
+ response.setHeader(
251
+ "X-RateLimit-Remaining",
252
+ augmentedRequest[options.requestPropertyName].remaining
253
+ );
254
+ if (resetTime instanceof Date) {
255
+ response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
256
+ response.setHeader(
257
+ "X-RateLimit-Reset",
258
+ Math.ceil(resetTime.getTime() / 1e3)
259
+ );
260
+ }
182
261
  }
183
- }
184
- if (options.skipFailedRequests || options.skipSuccessfulRequests) {
185
- let decremented = false;
186
- const decrementKey = async () => {
187
- if (!decremented) {
188
- await options.store.decrement(key);
189
- decremented = true;
262
+ if (options.standardHeaders && !response.headersSent) {
263
+ response.setHeader("RateLimit-Limit", maxHits);
264
+ response.setHeader(
265
+ "RateLimit-Remaining",
266
+ augmentedRequest[options.requestPropertyName].remaining
267
+ );
268
+ if (resetTime) {
269
+ const deltaSeconds = Math.ceil(
270
+ (resetTime.getTime() - Date.now()) / 1e3
271
+ );
272
+ response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
190
273
  }
191
- };
192
- if (options.skipFailedRequests) {
193
- response.on("finish", async () => {
194
- if (!options.requestWasSuccessful(request, response))
195
- await decrementKey();
196
- });
197
- response.on("close", async () => {
198
- if (!response.writableEnded)
199
- await decrementKey();
200
- });
201
- response.on("error", async () => {
202
- await decrementKey();
203
- });
204
274
  }
205
- if (options.skipSuccessfulRequests) {
206
- response.on("finish", async () => {
207
- if (options.requestWasSuccessful(request, response))
275
+ if (options.skipFailedRequests || options.skipSuccessfulRequests) {
276
+ let decremented = false;
277
+ const decrementKey = async () => {
278
+ if (!decremented) {
279
+ await options.store.decrement(key);
280
+ decremented = true;
281
+ }
282
+ };
283
+ if (options.skipFailedRequests) {
284
+ response.on("finish", async () => {
285
+ if (!options.requestWasSuccessful(request, response))
286
+ await decrementKey();
287
+ });
288
+ response.on("close", async () => {
289
+ if (!response.writableEnded)
290
+ await decrementKey();
291
+ });
292
+ response.on("error", async () => {
208
293
  await decrementKey();
209
- });
294
+ });
295
+ }
296
+ if (options.skipSuccessfulRequests) {
297
+ response.on("finish", async () => {
298
+ if (options.requestWasSuccessful(request, response))
299
+ await decrementKey();
300
+ });
301
+ }
210
302
  }
211
- }
212
- if (maxHits && totalHits === maxHits + 1) {
213
- options.onLimitReached(request, response, options);
214
- }
215
- if (maxHits && totalHits > maxHits) {
216
- if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
217
- response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
303
+ if (maxHits && totalHits === maxHits + 1) {
304
+ options.onLimitReached(request, response, options);
305
+ }
306
+ if (maxHits && totalHits > maxHits) {
307
+ if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
308
+ response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
309
+ }
310
+ options.handler(request, response, next, options);
311
+ return;
218
312
  }
219
- options.handler(request, response, next, options);
220
- return;
313
+ next();
221
314
  }
222
- next();
223
- });
315
+ );
224
316
  middleware.resetKey = options.store.resetKey.bind(options.store);
225
317
  return middleware;
226
318
  };