UNPKG

@routup/rate-limit

Version:
1 lines 17.3 kB
{"version":3,"file":"index.mjs","sources":["../src/request.ts","../src/constants.ts","../src/store/utils.ts","../src/store/memory.ts","../src/utils/options.ts","../src/handler.ts","../src/module.ts"],"sourcesContent":["import type { Request } from 'routup';\nimport type { RateLimitInfo } from './type';\n\nconst symbol = Symbol.for('ReqRateLimit');\nexport function useRequestRateLimitInfo(req: Request) : RateLimitInfo;\nexport function useRequestRateLimitInfo<K extends keyof RateLimitInfo>(req: Request, key: K) : RateLimitInfo[K];\nexport function useRequestRateLimitInfo(req: Request, key?: string) {\n if (symbol in req) {\n if (typeof key === 'string') {\n return (req as any)[symbol][key];\n }\n\n return (req as any)[symbol];\n }\n\n return {};\n}\n\nexport function setRequestRateLimitInfo<K extends keyof RateLimitInfo>(\n req: Request,\n key: K,\n value: RateLimitInfo[K]\n) : void;\nexport function setRequestRateLimitInfo(req: Request, record: RateLimitInfo) : void;\nexport function setRequestRateLimitInfo(req: Request, key: RateLimitInfo | string, value?: unknown) : void {\n if (symbol in req) {\n if (typeof key === 'object') {\n (req as any)[symbol] = key;\n } else {\n (req as any)[symbol][key] = value;\n }\n\n return;\n }\n\n if (typeof key === 'object') {\n (req as any)[symbol] = key;\n return;\n }\n\n (req as any)[symbol] = {\n [key]: value,\n };\n}\n","export const RETRY_AGAIN_MESSAGE = 'Too many requests, please try again later.';\n","export function calculateNextResetTime(windowMs: number): Date {\n const resetTime = new Date();\n resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);\n return resetTime;\n}\n","import type { IncrementResponse, Options } from '../type';\nimport type { Store } from './type';\nimport { calculateNextResetTime } from './utils';\n\nexport class MemoryStore implements Store {\n /**\n * The duration of time before which all hit counts are reset (in milliseconds).\n */\n windowMs!: number;\n\n /**\n * The map that stores the number of hits for each client in memory.\n */\n hits!: {\n [key: string]: number | undefined\n };\n\n /**\n * The time at which all hit counts will be reset.\n */\n resetTime!: Date;\n\n /**\n * Reference to the active timer.\n */\n interval?: NodeJS.Timer;\n\n /**\n * Method that initializes the store.\n *\n * @param options {Options} - The options used to setup the middleware.\n */\n init(options: Options): void {\n // Get the duration of a window from the options.\n this.windowMs = options.windowMs;\n\n // Then calculate the reset time using that.\n this.resetTime = calculateNextResetTime(this.windowMs);\n\n // Initialise the hit counter map.\n this.hits = {};\n\n // Reset hit counts for ALL clients every `windowMs` - this will also\n // re-calculate the `resetTime`\n this.interval = setInterval(async () => {\n await this.resetAll();\n }, this.windowMs);\n\n // Cleaning up the interval will be taken care of by the `shutdown` method.\n if (this.interval.unref) this.interval.unref();\n }\n\n /**\n * Method to increment a client's hit counter.\n *\n * @param key {string} - The identifier for a client.\n *\n * @returns {IncrementResponse} - The number of hits and reset time for that client.\n *\n * @public\n */\n async increment(key: string): Promise<IncrementResponse> {\n const totalHits = (this.hits[key] ?? 0) + 1;\n this.hits[key] = totalHits;\n\n return {\n totalHits,\n resetTime: this.resetTime,\n };\n }\n\n /**\n * Method to decrement a client's hit counter.\n *\n * @param key {string} - The identifier for a client.\n *\n * @public\n */\n async decrement(key: string): Promise<void> {\n const current = this.hits[key];\n\n if (current) this.hits[key] = current - 1;\n }\n\n /**\n * Method to reset a client's hit counter.\n *\n * @param key {string} - The identifier for a client.\n *\n * @public\n */\n async reset(key: string): Promise<void> {\n delete this.hits[key];\n }\n\n /**\n * Method to reset everyone's hit counter.\n *\n * @public\n */\n /* istanbul ignore next */\n async resetAll(): Promise<void> {\n this.hits = {};\n this.resetTime = calculateNextResetTime(this.windowMs);\n }\n}\n","import type { Next, Request, Response } from 'routup';\nimport { getRequestIP, send } from 'routup';\nimport { RETRY_AGAIN_MESSAGE } from '../constants';\nimport { MemoryStore } from '../store';\nimport type { Options, OptionsInput, ValueDeterminingMiddleware } from '../type';\n\nexport function buildHandlerOptions(input?: OptionsInput) : Options {\n input = input || {};\n\n const options : Options = {\n windowMs: 60 * 1000,\n max: 5,\n message: RETRY_AGAIN_MESSAGE,\n statusCode: 429,\n skipFailedRequest: false,\n skipSuccessfulRequest: false,\n requestWasSuccessful: (request: Request, response: Response): boolean => response.statusCode < 400,\n skip: (_request: Request, _response: Response): boolean => false,\n keyGenerator: (request: Request, _response: Response): string => getRequestIP(request, { trustProxy: true }),\n async handler(\n request: Request,\n response: Response,\n _next: Next,\n _optionsUsed: Options,\n ): Promise<void> {\n // Set the response status code\n response.statusCode = options.statusCode;\n // Call the `message` if it is a function.\n const message: unknown = typeof options.message === 'function' ?\n await (options.message as ValueDeterminingMiddleware<any>)(\n request,\n response,\n ) :\n options.message;\n\n // Send the response if writable.\n if (!response.writableEnded) {\n send(response, message ?? 'Too many requests, please try again later.');\n }\n },\n ...input,\n\n store: input.store || new MemoryStore(),\n };\n\n return options;\n}\n","import { HeaderName, coreHandler } from 'routup';\nimport { setRequestRateLimitInfo, useRequestRateLimitInfo } from './request';\nimport type { OptionsInput } from './type';\nimport { buildHandlerOptions } from './utils';\n\nexport function createHandler(input?: OptionsInput) {\n const options = buildHandlerOptions({\n ...(input || {}),\n });\n\n if (typeof options.store.init === 'function') {\n options.store.init(options);\n }\n\n return coreHandler(async (req, res, next) => {\n const skip = await options.skip(req, res);\n if (skip) {\n next();\n return;\n }\n\n const key = await options.keyGenerator(req, res);\n\n const { totalHits, resetTime } = await options.store.increment(key);\n\n const retrieveQuota = typeof options.max === 'function' ?\n options.max(req, res) :\n options.max;\n\n const maxHits = await retrieveQuota;\n\n setRequestRateLimitInfo(req, {\n limit: maxHits,\n current: totalHits,\n remaining: Math.max(maxHits - totalHits, 0),\n resetTime,\n });\n\n if (!res.headersSent) {\n res.setHeader(HeaderName.RATE_LIMIT_LIMIT, maxHits);\n res.setHeader(\n HeaderName.RATE_LIMIT_REMAINING,\n useRequestRateLimitInfo(req, 'remaining'),\n );\n\n if (resetTime) {\n const deltaSeconds = Math.ceil(\n (resetTime.getTime() - Date.now()) / 1000,\n );\n res.setHeader(HeaderName.RATE_LIMIT_RESET, Math.max(0, deltaSeconds));\n }\n }\n\n if (\n options.skipFailedRequest ||\n options.skipSuccessfulRequest\n ) {\n let decremented = false;\n const decrementKey = async () => {\n if (!decremented) {\n await options.store.decrement(key);\n decremented = true;\n\n setRequestRateLimitInfo(req, 'remaining', Math.max(maxHits - totalHits - 1, 0));\n }\n };\n\n if (options.skipFailedRequest) {\n res.on('finish', async () => {\n if (!options.requestWasSuccessful(req, res)) {\n await decrementKey();\n }\n });\n\n res.on('close', async () => {\n if (!res.writableEnded) {\n await decrementKey();\n }\n });\n\n res.on('error', async () => {\n await decrementKey();\n });\n }\n\n if (options.skipSuccessfulRequest) {\n res.on('finish', async () => {\n if (options.requestWasSuccessful(req, res)) {\n await decrementKey();\n }\n });\n }\n }\n\n if (\n maxHits &&\n totalHits > maxHits\n ) {\n if (!res.headersSent) {\n res.setHeader(\n HeaderName.RETRY_AFTER,\n Math.ceil(options.windowMs / 1000),\n );\n }\n\n options.handler(req, res, next, options);\n return;\n }\n\n next();\n });\n}\n","import type { Plugin } from 'routup';\nimport { createHandler } from './handler';\nimport type { OptionsInput } from './type';\n\nexport function rateLimit(options?: OptionsInput) : Plugin {\n return {\n name: 'rateLimit',\n install: (router) => {\n router.use(createHandler(options));\n },\n };\n}\n"],"names":["symbol","Symbol","for","useRequestRateLimitInfo","req","key","setRequestRateLimitInfo","value","RETRY_AGAIN_MESSAGE","calculateNextResetTime","windowMs","resetTime","Date","setMilliseconds","getMilliseconds","MemoryStore","init","options","hits","interval","setInterval","resetAll","unref","increment","totalHits","decrement","current","reset","buildHandlerOptions","input","max","message","statusCode","skipFailedRequest","skipSuccessfulRequest","requestWasSuccessful","request","response","skip","_request","_response","keyGenerator","getRequestIP","trustProxy","handler","_next","_optionsUsed","writableEnded","send","store","createHandler","coreHandler","res","next","retrieveQuota","maxHits","limit","remaining","Math","headersSent","setHeader","HeaderName","RATE_LIMIT_LIMIT","RATE_LIMIT_REMAINING","deltaSeconds","ceil","getTime","now","RATE_LIMIT_RESET","decremented","decrementKey","on","RETRY_AFTER","rateLimit","name","install","router","use"],"mappings":"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"}