UNPKG

@akinolae/node-rate-limiter

Version:
216 lines (187 loc) 5.03 kB
import { Request } from 'express'; enum Intervals { // Query every 1 minute Query = 1 * 60 * 1000, request = 10, } type RequestStore = { callID: string; request: number; ttl: number; lastCall: number; requestIp: string; }; type Requestparams = { ttl?: number; session_no?: number; request: Request; }; type RequestStoreObject = { [key: string]: Array<RequestStore>; }; let requestBucket: RequestStoreObject; /** * @param {*} id * @returns a single call that matches the provided ID * checks if the call id exists in the interval-list */ const regexCheck = (defaultVal: string, val: string) => new RegExp(defaultVal).test(val); const findRequestbyId = (params: { requestIp: string; functionId: string; }): RequestStore | undefined => { const { requestIp, functionId } = params; if (!requestIp || !functionId) { throw new Error('INTERVAL_CACHE_ERROR:CallId and functionId is required'); } const requestFromBucket = requestBucket !== undefined ? requestBucket[requestIp] : []; if (Array.isArray(requestFromBucket) && requestFromBucket.length > 0) { for (const interval of requestFromBucket) { if (regexCheck(interval.callID, functionId)) { return { ...interval, requestIp }; } else { return; } } } }; /** * @param {IntervalObj} params * filters the call list array, removes duplicate before adding a new call object * checks if the call id exists in the interval-list */ const addRequestToList = (params: { requestIp: string; requestObject: RequestStore; }) => { const { requestIp, requestObject, ...rest } = params; // Check that the callid exists before performing any mutation const callidExists = Object.keys(requestBucket || {}).some( (id) => id === requestIp, ); if (!callidExists) { requestBucket = { ...requestBucket, ...rest, [requestIp]: [requestObject], }; } else { const currentInterval = [ ...requestBucket[requestIp].filter( (a) => a?.callID !== requestObject.callID, ), requestObject, ]; requestBucket = { ...requestBucket, [requestIp]: currentInterval, }; } return requestBucket; }; /** * * @param params * @returns * manages the request coming in, checks that each request updates ones */ const manageRequestFn = (params: { callIDexists: RequestStore; currentRequestId: string; urlFn: string; session: number; interval: number; }) => { const { callIDexists, currentRequestId, urlFn, interval, session } = params; let requestBody: any; const date = new Date(); //add fn() to scope if it doesn't exist if (!callIDexists || !Object.keys(callIDexists).length) { requestBody = { requestIp: currentRequestId, requestObject: { callID: urlFn, request: session, ttl: interval, lastCall: +date, }, }; } else if ( callIDexists.request <= 1 && date.getTime() - callIDexists.lastCall > interval ) { requestBody = { requestIp: callIDexists.requestIp, requestObject: { callID: callIDexists.callID, request: session, ttl: callIDexists.ttl, lastCall: +date, }, }; } else { if (!callIDexists.request) { throw new Error( `TOO MANY REQUESTS: call ${urlFn} has too many requests try again in ${Math.floor( callIDexists.ttl / 60000, )} minute(s)`, ); } else { const newRequestLimit = callIDexists.request - 1; requestBody = { requestIp: callIDexists.requestIp, requestObject: { callID: callIDexists.callID, request: newRequestLimit, ttl: callIDexists.ttl, lastCall: callIDexists.lastCall, }, }; } } return requestBody; }; /** * @param {*} params * does the necessary checks and also updates the state of each request */ const limitCorefn = (args: Requestparams) => { /** * Checks to know if fn by callId exists in scope * Adds call to the scope if it doesn't exist */ let urlFn: string; const validUrl = ['graphql']; const { session_no, ttl, request } = args; const reqIsGraphql = validUrl.some((url: string) => regexCheck(url, request.baseUrl), ); if (reqIsGraphql) { const _req = Object.keys(request.body); if ( _req.some((val: string) => ['query'].includes(val.toLocaleLowerCase())) ) { urlFn = request.body[_req[0]].trimStart().split(' ')[1].split('(')[0]; } } else { urlFn = request.url; } const interval = ttl ?? Intervals.Query; const session = session_no ?? Intervals.request; const currentRequestId = `${request.ip}-${urlFn}`; const callIDexists = findRequestbyId({ requestIp: currentRequestId, functionId: urlFn, }); const requestBody = manageRequestFn({ callIDexists, currentRequestId, interval, session, urlFn, }); addRequestToList(requestBody); }; export { limitCorefn as requestLimitCore };