index.cjs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. "use strict";
  2. var __defProp = Object.defineProperty;
  3. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  4. var __getOwnPropNames = Object.getOwnPropertyNames;
  5. var __hasOwnProp = Object.prototype.hasOwnProperty;
  6. var __export = (target, all) => {
  7. for (var name in all)
  8. __defProp(target, name, { get: all[name], enumerable: true });
  9. };
  10. var __copyProps = (to, from, except, desc) => {
  11. if (from && typeof from === "object" || typeof from === "function") {
  12. for (let key of __getOwnPropNames(from))
  13. if (!__hasOwnProp.call(to, key) && key !== except)
  14. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  15. }
  16. return to;
  17. };
  18. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  19. // source/index.ts
  20. var source_exports = {};
  21. __export(source_exports, {
  22. MemoryStore: () => MemoryStore,
  23. default: () => lib_default,
  24. rateLimit: () => lib_default
  25. });
  26. module.exports = __toCommonJS(source_exports);
  27. // source/headers.ts
  28. var getResetSeconds = (resetTime, windowMs) => {
  29. let resetSeconds = void 0;
  30. if (resetTime) {
  31. const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
  32. resetSeconds = Math.max(0, deltaSeconds);
  33. } else if (windowMs) {
  34. resetSeconds = Math.ceil(windowMs / 1e3);
  35. }
  36. return resetSeconds;
  37. };
  38. var setLegacyHeaders = (response, info) => {
  39. if (response.headersSent)
  40. return;
  41. response.setHeader("X-RateLimit-Limit", info.limit.toString());
  42. response.setHeader("X-RateLimit-Remaining", info.remaining.toString());
  43. if (info.resetTime instanceof Date) {
  44. response.setHeader("Date", (/* @__PURE__ */ new Date()).toUTCString());
  45. response.setHeader(
  46. "X-RateLimit-Reset",
  47. Math.ceil(info.resetTime.getTime() / 1e3).toString()
  48. );
  49. }
  50. };
  51. var setDraft6Headers = (response, info, windowMs) => {
  52. if (response.headersSent)
  53. return;
  54. const windowSeconds = Math.ceil(windowMs / 1e3);
  55. const resetSeconds = getResetSeconds(info.resetTime);
  56. response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
  57. response.setHeader("RateLimit-Limit", info.limit.toString());
  58. response.setHeader("RateLimit-Remaining", info.remaining.toString());
  59. if (resetSeconds)
  60. response.setHeader("RateLimit-Reset", resetSeconds.toString());
  61. };
  62. var setDraft7Headers = (response, info, windowMs) => {
  63. if (response.headersSent)
  64. return;
  65. const windowSeconds = Math.ceil(windowMs / 1e3);
  66. const resetSeconds = getResetSeconds(info.resetTime, windowMs);
  67. response.setHeader("RateLimit-Policy", `${info.limit};w=${windowSeconds}`);
  68. response.setHeader(
  69. "RateLimit",
  70. `limit=${info.limit}, remaining=${info.remaining}, reset=${resetSeconds}`
  71. );
  72. };
  73. var setRetryAfterHeader = (response, info, windowMs) => {
  74. if (response.headersSent)
  75. return;
  76. const resetSeconds = getResetSeconds(info.resetTime, windowMs);
  77. response.setHeader("Retry-After", resetSeconds.toString());
  78. };
  79. // source/validations.ts
  80. var import_node_net = require("net");
  81. var ValidationError = class extends Error {
  82. /**
  83. * The code must be a string, in snake case and all capital, that starts with
  84. * the substring `ERR_ERL_`.
  85. *
  86. * The message must be a string, starting with an uppercase character,
  87. * describing the issue in detail.
  88. */
  89. constructor(code, message) {
  90. const url = `https://express-rate-limit.github.io/${code}/`;
  91. super(`${message} See ${url} for more information.`);
  92. this.name = this.constructor.name;
  93. this.code = code;
  94. this.help = url;
  95. }
  96. };
  97. var ChangeWarning = class extends ValidationError {
  98. };
  99. var usedStores = /* @__PURE__ */ new Set();
  100. var singleCountKeys = /* @__PURE__ */ new WeakMap();
  101. var validations = {
  102. // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
  103. enabled: {
  104. default: true
  105. },
  106. // Should be EnabledValidations type, but that's a circular reference
  107. disable() {
  108. for (const k of Object.keys(this.enabled))
  109. this.enabled[k] = false;
  110. },
  111. /**
  112. * Checks whether the IP address is valid, and that it does not have a port
  113. * number in it.
  114. *
  115. * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_invalid_ip_address.
  116. *
  117. * @param ip {string | undefined} - The IP address provided by Express as request.ip.
  118. *
  119. * @returns {void}
  120. */
  121. ip(ip) {
  122. if (ip === void 0) {
  123. throw new ValidationError(
  124. "ERR_ERL_UNDEFINED_IP_ADDRESS",
  125. `An undefined 'request.ip' was detected. This might indicate a misconfiguration or the connection being destroyed prematurely.`
  126. );
  127. }
  128. if (!(0, import_node_net.isIP)(ip)) {
  129. throw new ValidationError(
  130. "ERR_ERL_INVALID_IP_ADDRESS",
  131. `An invalid 'request.ip' (${ip}) was detected. Consider passing a custom 'keyGenerator' function to the rate limiter.`
  132. );
  133. }
  134. },
  135. /**
  136. * Makes sure the trust proxy setting is not set to `true`.
  137. *
  138. * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_permissive_trust_proxy.
  139. *
  140. * @param request {Request} - The Express request object.
  141. *
  142. * @returns {void}
  143. */
  144. trustProxy(request) {
  145. if (request.app.get("trust proxy") === true) {
  146. throw new ValidationError(
  147. "ERR_ERL_PERMISSIVE_TRUST_PROXY",
  148. `The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.`
  149. );
  150. }
  151. },
  152. /**
  153. * Makes sure the trust proxy setting is set in case the `X-Forwarded-For`
  154. * header is present.
  155. *
  156. * See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes#err_erl_unset_trust_proxy.
  157. *
  158. * @param request {Request} - The Express request object.
  159. *
  160. * @returns {void}
  161. */
  162. xForwardedForHeader(request) {
  163. if (request.headers["x-forwarded-for"] && request.app.get("trust proxy") === false) {
  164. throw new ValidationError(
  165. "ERR_ERL_UNEXPECTED_X_FORWARDED_FOR",
  166. `The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.`
  167. );
  168. }
  169. },
  170. /**
  171. * Ensures totalHits value from store is a positive integer.
  172. *
  173. * @param hits {any} - The `totalHits` returned by the store.
  174. */
  175. positiveHits(hits) {
  176. if (typeof hits !== "number" || hits < 1 || hits !== Math.round(hits)) {
  177. throw new ValidationError(
  178. "ERR_ERL_INVALID_HITS",
  179. `The totalHits value returned from the store must be a positive integer, got ${hits}`
  180. );
  181. }
  182. },
  183. /**
  184. * Ensures a single store instance is not used with multiple express-rate-limit instances
  185. */
  186. unsharedStore(store) {
  187. if (usedStores.has(store)) {
  188. const maybeUniquePrefix = store?.localKeys ? "" : " (with a unique prefix)";
  189. throw new ValidationError(
  190. "ERR_ERL_STORE_REUSE",
  191. `A Store instance must not be shared across multiple rate limiters. Create a new instance of ${store.constructor.name}${maybeUniquePrefix} for each limiter instead.`
  192. );
  193. }
  194. usedStores.add(store);
  195. },
  196. /**
  197. * Ensures a given key is incremented only once per request.
  198. *
  199. * @param request {Request} - The Express request object.
  200. * @param store {Store} - The store class.
  201. * @param key {string} - The key used to store the client's hit count.
  202. *
  203. * @returns {void}
  204. */
  205. singleCount(request, store, key) {
  206. let storeKeys = singleCountKeys.get(request);
  207. if (!storeKeys) {
  208. storeKeys = /* @__PURE__ */ new Map();
  209. singleCountKeys.set(request, storeKeys);
  210. }
  211. const storeKey = store.localKeys ? store : store.constructor.name;
  212. let keys = storeKeys.get(storeKey);
  213. if (!keys) {
  214. keys = [];
  215. storeKeys.set(storeKey, keys);
  216. }
  217. const prefixedKey = `${store.prefix ?? ""}${key}`;
  218. if (keys.includes(prefixedKey)) {
  219. throw new ValidationError(
  220. "ERR_ERL_DOUBLE_COUNT",
  221. `The hit count for ${key} was incremented more than once for a single request.`
  222. );
  223. }
  224. keys.push(prefixedKey);
  225. },
  226. /**
  227. * Warns the user that the behaviour for `max: 0` / `limit: 0` is
  228. * changing in the next major release.
  229. *
  230. * @param limit {number} - The maximum number of hits per client.
  231. *
  232. * @returns {void}
  233. */
  234. limit(limit) {
  235. if (limit === 0) {
  236. throw new ChangeWarning(
  237. "WRN_ERL_MAX_ZERO",
  238. `Setting limit or max to 0 disables rate limiting in express-rate-limit v6 and older, but will cause all requests to be blocked in v7`
  239. );
  240. }
  241. },
  242. /**
  243. * Warns the user that the `draft_polli_ratelimit_headers` option is deprecated
  244. * and will be removed in the next major release.
  245. *
  246. * @param draft_polli_ratelimit_headers {any | undefined} - The now-deprecated setting that was used to enable standard headers.
  247. *
  248. * @returns {void}
  249. */
  250. draftPolliHeaders(draft_polli_ratelimit_headers) {
  251. if (draft_polli_ratelimit_headers) {
  252. throw new ChangeWarning(
  253. "WRN_ERL_DEPRECATED_DRAFT_POLLI_HEADERS",
  254. `The draft_polli_ratelimit_headers configuration option is deprecated and has been removed in express-rate-limit v7, please set standardHeaders: 'draft-6' instead.`
  255. );
  256. }
  257. },
  258. /**
  259. * Warns the user that the `onLimitReached` option is deprecated and
  260. * will be removed in the next major release.
  261. *
  262. * @param onLimitReached {any | undefined} - The maximum number of hits per client.
  263. *
  264. * @returns {void}
  265. */
  266. onLimitReached(onLimitReached) {
  267. if (onLimitReached) {
  268. throw new ChangeWarning(
  269. "WRN_ERL_DEPRECATED_ON_LIMIT_REACHED",
  270. `The onLimitReached configuration option is deprecated and has been removed in express-rate-limit v7.`
  271. );
  272. }
  273. },
  274. /**
  275. * Warns the user when the selected headers option requires a reset time but
  276. * the store does not provide one.
  277. *
  278. * @param resetTime {Date | undefined} - The timestamp when the client's hit count will be reset.
  279. *
  280. * @returns {void}
  281. */
  282. headersResetTime(resetTime) {
  283. if (!resetTime) {
  284. throw new ValidationError(
  285. "ERR_ERL_HEADERS_NO_RESET",
  286. `standardHeaders: 'draft-7' requires a 'resetTime', but the store did not provide one. The 'windowMs' value will be used instead, which may cause clients to wait longer than necessary.`
  287. );
  288. }
  289. },
  290. /**
  291. * Checks the options.validate setting to ensure that only recognized
  292. * validations are enabled or disabled.
  293. *
  294. * If any unrecognized values are found, an error is logged that
  295. * includes the list of supported vaidations.
  296. */
  297. validationsConfig() {
  298. const supportedValidations = Object.keys(this).filter(
  299. (k) => !["enabled", "disable"].includes(k)
  300. );
  301. supportedValidations.push("default");
  302. for (const key of Object.keys(this.enabled)) {
  303. if (!supportedValidations.includes(key)) {
  304. throw new ValidationError(
  305. "ERR_ERL_UNKNOWN_VALIDATION",
  306. `options.validate.${key} is not recognized. Supported validate options are: ${supportedValidations.join(
  307. ", "
  308. )}.`
  309. );
  310. }
  311. }
  312. },
  313. /**
  314. * Checks to see if the instance was created inside of a request handler,
  315. * which would prevent it from working correctly, with the default memory
  316. * store (or any other store with localKeys.)
  317. */
  318. creationStack(store) {
  319. const { stack } = new Error(
  320. "express-rate-limit validation check (set options.validate.creationStack=false to disable)"
  321. );
  322. if (stack?.includes("Layer.handle [as handle_request]")) {
  323. if (!store.localKeys) {
  324. throw new ValidationError(
  325. "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
  326. "express-rate-limit instance should *usually* be created at app initialization, not when responding to a request."
  327. );
  328. }
  329. throw new ValidationError(
  330. "ERR_ERL_CREATED_IN_REQUEST_HANDLER",
  331. `express-rate-limit instance should be created at app initialization, not when responding to a request.`
  332. );
  333. }
  334. }
  335. };
  336. var getValidations = (_enabled) => {
  337. let enabled;
  338. if (typeof _enabled === "boolean") {
  339. enabled = {
  340. default: _enabled
  341. };
  342. } else {
  343. enabled = {
  344. default: true,
  345. ..._enabled
  346. };
  347. }
  348. const wrappedValidations = {
  349. enabled
  350. };
  351. for (const [name, validation] of Object.entries(validations)) {
  352. if (typeof validation === "function")
  353. wrappedValidations[name] = (...args) => {
  354. if (!(enabled[name] ?? enabled.default)) {
  355. return;
  356. }
  357. try {
  358. ;
  359. validation.apply(
  360. wrappedValidations,
  361. args
  362. );
  363. } catch (error) {
  364. if (error instanceof ChangeWarning)
  365. console.warn(error);
  366. else
  367. console.error(error);
  368. }
  369. };
  370. }
  371. return wrappedValidations;
  372. };
  373. // source/memory-store.ts
  374. var MemoryStore = class {
  375. constructor() {
  376. /**
  377. * These two maps store usage (requests) and reset time by key (for example, IP
  378. * addresses or API keys).
  379. *
  380. * They are split into two to avoid having to iterate through the entire set to
  381. * determine which ones need reset. Instead, `Client`s are moved from `previous`
  382. * to `current` as they hit the endpoint. Once `windowMs` has elapsed, all clients
  383. * left in `previous`, i.e., those that have not made any recent requests, are
  384. * known to be expired and can be deleted in bulk.
  385. */
  386. this.previous = /* @__PURE__ */ new Map();
  387. this.current = /* @__PURE__ */ new Map();
  388. /**
  389. * Confirmation that the keys incremented in once instance of MemoryStore
  390. * cannot affect other instances.
  391. */
  392. this.localKeys = true;
  393. }
  394. /**
  395. * Method that initializes the store.
  396. *
  397. * @param options {Options} - The options used to setup the middleware.
  398. */
  399. init(options) {
  400. this.windowMs = options.windowMs;
  401. if (this.interval)
  402. clearInterval(this.interval);
  403. this.interval = setInterval(() => {
  404. this.clearExpired();
  405. }, this.windowMs);
  406. if (this.interval.unref)
  407. this.interval.unref();
  408. }
  409. /**
  410. * Method to fetch a client's hit count and reset time.
  411. *
  412. * @param key {string} - The identifier for a client.
  413. *
  414. * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
  415. *
  416. * @public
  417. */
  418. async get(key) {
  419. return this.current.get(key) ?? this.previous.get(key);
  420. }
  421. /**
  422. * Method to increment a client's hit counter.
  423. *
  424. * @param key {string} - The identifier for a client.
  425. *
  426. * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client.
  427. *
  428. * @public
  429. */
  430. async increment(key) {
  431. const client = this.getClient(key);
  432. const now = Date.now();
  433. if (client.resetTime.getTime() <= now) {
  434. this.resetClient(client, now);
  435. }
  436. client.totalHits++;
  437. return client;
  438. }
  439. /**
  440. * Method to decrement a client's hit counter.
  441. *
  442. * @param key {string} - The identifier for a client.
  443. *
  444. * @public
  445. */
  446. async decrement(key) {
  447. const client = this.getClient(key);
  448. if (client.totalHits > 0)
  449. client.totalHits--;
  450. }
  451. /**
  452. * Method to reset a client's hit counter.
  453. *
  454. * @param key {string} - The identifier for a client.
  455. *
  456. * @public
  457. */
  458. async resetKey(key) {
  459. this.current.delete(key);
  460. this.previous.delete(key);
  461. }
  462. /**
  463. * Method to reset everyone's hit counter.
  464. *
  465. * @public
  466. */
  467. async resetAll() {
  468. this.current.clear();
  469. this.previous.clear();
  470. }
  471. /**
  472. * Method to stop the timer (if currently running) and prevent any memory
  473. * leaks.
  474. *
  475. * @public
  476. */
  477. shutdown() {
  478. clearInterval(this.interval);
  479. void this.resetAll();
  480. }
  481. /**
  482. * Recycles a client by setting its hit count to zero, and reset time to
  483. * `windowMs` milliseconds from now.
  484. *
  485. * NOT to be confused with `#resetKey()`, which removes a client from both the
  486. * `current` and `previous` maps.
  487. *
  488. * @param client {Client} - The client to recycle.
  489. * @param now {number} - The current time, to which the `windowMs` is added to get the `resetTime` for the client.
  490. *
  491. * @return {Client} - The modified client that was passed in, to allow for chaining.
  492. */
  493. resetClient(client, now = Date.now()) {
  494. client.totalHits = 0;
  495. client.resetTime.setTime(now + this.windowMs);
  496. return client;
  497. }
  498. /**
  499. * Retrieves or creates a client, given a key. Also ensures that the client being
  500. * returned is in the `current` map.
  501. *
  502. * @param key {string} - The key under which the client is (or is to be) stored.
  503. *
  504. * @returns {Client} - The requested client.
  505. */
  506. getClient(key) {
  507. if (this.current.has(key))
  508. return this.current.get(key);
  509. let client;
  510. if (this.previous.has(key)) {
  511. client = this.previous.get(key);
  512. this.previous.delete(key);
  513. } else {
  514. client = { totalHits: 0, resetTime: /* @__PURE__ */ new Date() };
  515. this.resetClient(client);
  516. }
  517. this.current.set(key, client);
  518. return client;
  519. }
  520. /**
  521. * Move current clients to previous, create a new map for current.
  522. *
  523. * This function is called every `windowMs`.
  524. */
  525. clearExpired() {
  526. this.previous = this.current;
  527. this.current = /* @__PURE__ */ new Map();
  528. }
  529. };
  530. // source/lib.ts
  531. var isLegacyStore = (store) => (
  532. // Check that `incr` exists but `increment` does not - store authors might want
  533. // to keep both around for backwards compatibility.
  534. typeof store.incr === "function" && typeof store.increment !== "function"
  535. );
  536. var promisifyStore = (passedStore) => {
  537. if (!isLegacyStore(passedStore)) {
  538. return passedStore;
  539. }
  540. const legacyStore = passedStore;
  541. class PromisifiedStore {
  542. async increment(key) {
  543. return new Promise((resolve, reject) => {
  544. legacyStore.incr(
  545. key,
  546. (error, totalHits, resetTime) => {
  547. if (error)
  548. reject(error);
  549. resolve({ totalHits, resetTime });
  550. }
  551. );
  552. });
  553. }
  554. async decrement(key) {
  555. return legacyStore.decrement(key);
  556. }
  557. async resetKey(key) {
  558. return legacyStore.resetKey(key);
  559. }
  560. /* istanbul ignore next */
  561. async resetAll() {
  562. if (typeof legacyStore.resetAll === "function")
  563. return legacyStore.resetAll();
  564. }
  565. }
  566. return new PromisifiedStore();
  567. };
  568. var getOptionsFromConfig = (config) => {
  569. const { validations: validations2, ...directlyPassableEntries } = config;
  570. return {
  571. ...directlyPassableEntries,
  572. validate: validations2.enabled
  573. };
  574. };
  575. var omitUndefinedOptions = (passedOptions) => {
  576. const omittedOptions = {};
  577. for (const k of Object.keys(passedOptions)) {
  578. const key = k;
  579. if (passedOptions[key] !== void 0) {
  580. omittedOptions[key] = passedOptions[key];
  581. }
  582. }
  583. return omittedOptions;
  584. };
  585. var parseOptions = (passedOptions) => {
  586. const notUndefinedOptions = omitUndefinedOptions(passedOptions);
  587. const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
  588. validations2.validationsConfig();
  589. validations2.draftPolliHeaders(
  590. // @ts-expect-error see the note above.
  591. notUndefinedOptions.draft_polli_ratelimit_headers
  592. );
  593. validations2.onLimitReached(notUndefinedOptions.onLimitReached);
  594. let standardHeaders = notUndefinedOptions.standardHeaders ?? false;
  595. if (standardHeaders === true)
  596. standardHeaders = "draft-6";
  597. const config = {
  598. windowMs: 60 * 1e3,
  599. limit: passedOptions.max ?? 5,
  600. // `max` is deprecated, but support it anyways.
  601. message: "Too many requests, please try again later.",
  602. statusCode: 429,
  603. legacyHeaders: passedOptions.headers ?? true,
  604. requestPropertyName: "rateLimit",
  605. skipFailedRequests: false,
  606. skipSuccessfulRequests: false,
  607. requestWasSuccessful: (_request, response) => response.statusCode < 400,
  608. skip: (_request, _response) => false,
  609. keyGenerator(request, _response) {
  610. validations2.ip(request.ip);
  611. validations2.trustProxy(request);
  612. validations2.xForwardedForHeader(request);
  613. return request.ip;
  614. },
  615. async handler(request, response, _next, _optionsUsed) {
  616. response.status(config.statusCode);
  617. const message = typeof config.message === "function" ? await config.message(
  618. request,
  619. response
  620. ) : config.message;
  621. if (!response.writableEnded) {
  622. response.send(message);
  623. }
  624. },
  625. // Allow the default options to be overriden by the options passed to the middleware.
  626. ...notUndefinedOptions,
  627. // `standardHeaders` is resolved into a draft version above, use that.
  628. standardHeaders,
  629. // Note that this field is declared after the user's options are spread in,
  630. // so that this field doesn't get overriden with an un-promisified store!
  631. store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore()),
  632. // Print an error to the console if a few known misconfigurations are detected.
  633. validations: validations2
  634. };
  635. 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") {
  636. throw new TypeError(
  637. "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
  638. );
  639. }
  640. return config;
  641. };
  642. var handleAsyncErrors = (fn) => async (request, response, next) => {
  643. try {
  644. await Promise.resolve(fn(request, response, next)).catch(next);
  645. } catch (error) {
  646. next(error);
  647. }
  648. };
  649. var rateLimit = (passedOptions) => {
  650. const config = parseOptions(passedOptions ?? {});
  651. const options = getOptionsFromConfig(config);
  652. config.validations.creationStack(config.store);
  653. config.validations.unsharedStore(config.store);
  654. if (typeof config.store.init === "function")
  655. config.store.init(options);
  656. const middleware = handleAsyncErrors(
  657. async (request, response, next) => {
  658. const skip = await config.skip(request, response);
  659. if (skip) {
  660. next();
  661. return;
  662. }
  663. const augmentedRequest = request;
  664. const key = await config.keyGenerator(request, response);
  665. const { totalHits, resetTime } = await config.store.increment(key);
  666. config.validations.positiveHits(totalHits);
  667. config.validations.singleCount(request, config.store, key);
  668. const retrieveLimit = typeof config.limit === "function" ? config.limit(request, response) : config.limit;
  669. const limit = await retrieveLimit;
  670. config.validations.limit(limit);
  671. const info = {
  672. limit,
  673. used: totalHits,
  674. remaining: Math.max(limit - totalHits, 0),
  675. resetTime
  676. };
  677. Object.defineProperty(info, "current", {
  678. configurable: false,
  679. enumerable: false,
  680. value: totalHits
  681. });
  682. augmentedRequest[config.requestPropertyName] = info;
  683. if (config.legacyHeaders && !response.headersSent) {
  684. setLegacyHeaders(response, info);
  685. }
  686. if (config.standardHeaders && !response.headersSent) {
  687. if (config.standardHeaders === "draft-6") {
  688. setDraft6Headers(response, info, config.windowMs);
  689. } else if (config.standardHeaders === "draft-7") {
  690. config.validations.headersResetTime(info.resetTime);
  691. setDraft7Headers(response, info, config.windowMs);
  692. }
  693. }
  694. if (config.skipFailedRequests || config.skipSuccessfulRequests) {
  695. let decremented = false;
  696. const decrementKey = async () => {
  697. if (!decremented) {
  698. await config.store.decrement(key);
  699. decremented = true;
  700. }
  701. };
  702. if (config.skipFailedRequests) {
  703. response.on("finish", async () => {
  704. if (!await config.requestWasSuccessful(request, response))
  705. await decrementKey();
  706. });
  707. response.on("close", async () => {
  708. if (!response.writableEnded)
  709. await decrementKey();
  710. });
  711. response.on("error", async () => {
  712. await decrementKey();
  713. });
  714. }
  715. if (config.skipSuccessfulRequests) {
  716. response.on("finish", async () => {
  717. if (await config.requestWasSuccessful(request, response))
  718. await decrementKey();
  719. });
  720. }
  721. }
  722. config.validations.disable();
  723. if (totalHits > limit) {
  724. if (config.legacyHeaders || config.standardHeaders) {
  725. setRetryAfterHeader(response, info, config.windowMs);
  726. }
  727. config.handler(request, response, next, options);
  728. return;
  729. }
  730. next();
  731. }
  732. );
  733. const getThrowFn = () => {
  734. throw new Error("The current store does not support the get/getKey method");
  735. };
  736. middleware.resetKey = config.store.resetKey.bind(config.store);
  737. middleware.getKey = typeof config.store.get === "function" ? config.store.get.bind(config.store) : getThrowFn;
  738. return middleware;
  739. };
  740. var lib_default = rateLimit;
  741. // Annotate the CommonJS export names for ESM import in node:
  742. 0 && (module.exports = {
  743. MemoryStore,
  744. rateLimit
  745. });
  746. module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;