import StatusCode from './HTTP/StatusCode' import { buildRFC7807 } from './RFCs/RFC7807' import ResponseBuilder from './ResponseBuilder' interface StorageItem { pointsRemaining: number timeReset: number } export interface RateLimitHeaders { "Retry-After"?: string, "X-RateLimit-Limit": string, "X-RateLimit-Remaining": string, "X-RateLimit-Reset": string } export default class RateLimiter { /** * number of points that can be used per {timeSpan} */ public static points = 10 /** * timeSpan in seconds */ public static timeSpan = 60 private static instance: RateLimiter = new RateLimiter() public static getInstance(): RateLimiter { return this.instance } private storage: Record = {} public constructor( private points = RateLimiter.points, private timeSpan = RateLimiter.timeSpan ) {} public consume(key: string, value: number = 1): Response | RateLimitHeaders { let item = this.storage[key] const now = (new Date().getTime() / 1000) if (!item) { item = { pointsRemaining: this.points, timeReset: now + this.timeSpan } } if (item.timeReset <= now) { item.timeReset = now + this.timeSpan item.pointsRemaining = this.points } item.pointsRemaining -= value this.storage[key] = item const headers: RateLimitHeaders = { "X-RateLimit-Limit": this.points.toFixed(0), "X-RateLimit-Remaining": Math.max(item.pointsRemaining, 0).toFixed(0), "X-RateLimit-Reset": item.timeReset.toFixed(0) } if (item.pointsRemaining < 0) { const res = new ResponseBuilder() const resp = buildRFC7807({ type: '/docs/error/rate-limited', status: StatusCode.TOO_MANY_REQUESTS, title: 'You are being rate limited as you have done too many requests to the server' }, res) res.addHeader('Retry-After', (item.timeReset - now).toFixed(0)) res.addHeaders(headers as any) return resp } return headers } }