index.mjs 24 KB

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