hono-rate-limiter
Version:
Rate limit middleware for Hono.
2 lines (1 loc) • 4.54 kB
JavaScript
const e=(e,t)=>{let i;if(e){const t=Math.ceil((e.getTime()-Date.now())/1e3);i=Math.max(0,t)}else t&&(i=Math.ceil(t/1e3));return i};class t{#e;init(e){this.#e=e.windowMs,this.interval&&clearInterval(this.interval),this.interval=setInterval((()=>{this.clearExpired()}),this.#e),this.interval.unref&&this.interval.unref()}get(e){return this.current.get(e)??this.previous.get(e)}increment(e){const t=this.getClient(e),i=Date.now();return t.resetTime.getTime()<=i&&this.resetClient(t,i),t.totalHits++,t}decrement(e){const t=this.getClient(e);t.totalHits>0&&t.totalHits--}resetKey(e){this.current.delete(e),this.previous.delete(e)}resetAll(){this.current.clear(),this.previous.clear()}shutdown(){clearInterval(this.interval),this.resetAll()}resetClient(e,t=Date.now()){return e.totalHits=0,e.resetTime.setTime(t+this.#e),e}getClient(e){const t=this.current.get(e);if(t)return t;let i;const s=this.previous.get(e);return s?(i=s,this.previous.delete(e)):(i={totalHits:0,resetTime:new Date},this.resetClient(i)),this.current.set(e,i),i}clearExpired(){this.previous=this.current,this.current=new Map}constructor(){this.previous=new Map,this.current=new Map}}const i=e=>!!e?.increment;function s(e,t){if(!i(e))throw new Error("The store is not correctly implemented!");"function"==typeof e.init&&e.init(t)}async function r(e,t,i){const s=await t(e),{totalHits:r,resetTime:a}=await i.increment(s);return{key:s,totalHits:r,resetTime:a}}function a(i){const{windowMs:a=6e4,limit:n=5,message:o="Too many requests, please try again later.",statusCode:c=429,standardHeaders:l="draft-6",requestPropertyName:u="rateLimit",requestStorePropertyName:m="rateLimitStore",skipFailedRequests:d=!1,skipSuccessfulRequests:w=!1,keyGenerator:h,skip:y=(()=>!1),requestWasSuccessful:f=(e=>e.res.status<400),handler:p=(async(e,t,i)=>{e.status(i.statusCode);const s="function"==typeof i.message?await i.message(e):i.message;return"string"==typeof s?e.text(s):e.json(s)}),store:g=new t}=i,M={windowMs:a,limit:n,message:o,statusCode:c,standardHeaders:l,requestPropertyName:u,requestStorePropertyName:m,skipFailedRequests:d,skipSuccessfulRequests:w,keyGenerator:h,skip:y,requestWasSuccessful:f,handler:p,store:g};return s(g,M),async(t,i)=>{if(await y(t))return void await i();const{key:s,totalHits:o,resetTime:c}=await r(t,h,g),q="function"==typeof n?n(t):n,k=await q,v={limit:k,used:o,remaining:Math.max(k-o,0),resetTime:c};t.set(u,v),t.set(m,{getKey:g.get?.bind(g),resetKey:g.resetKey.bind(g)}),l&&!t.finalized&&("draft-7"===l?((t,i,s)=>{if(t.finalized)return;const r=Math.ceil(s/1e3),a=e(i.resetTime,s);t.header("RateLimit-Policy",`${i.limit};w=${r}`),t.header("RateLimit",`limit=${i.limit}, remaining=${i.remaining}, reset=${a}`)})(t,v,a):((t,i,s)=>{if(t.finalized)return;const r=Math.ceil(s/1e3),a=e(i.resetTime);t.header("RateLimit-Policy",`${i.limit};w=${r}`),t.header("RateLimit-Limit",i.limit.toString()),t.header("RateLimit-Remaining",i.remaining.toString()),a&&t.header("RateLimit-Reset",a.toString())})(t,v,a));let T=!1;const R=async()=>{T||(await g.decrement(s),T=!0)},S=async()=>{if(d||w){const e=await f(t);(d&&!e||w&&e)&&await R()}};if(o>k)return l&&((t,i,s)=>{if(t.finalized)return;const r=e(i.resetTime,s);t.header("Retry-After",r?.toString())})(t,v,a),await S(),p(t,i,M);try{await i(),await S()}catch(e){d&&await R()}finally{t.finalized||await R()}}}function n(e){const{windowMs:i=6e4,limit:a=5,message:n="Too many requests, please try again later.",statusCode:o=1008,requestPropertyName:c="rateLimit",requestStorePropertyName:l="rateLimitStore",skipFailedRequests:u=!1,skipSuccessfulRequests:m=!1,keyGenerator:d,skip:w=(()=>!1),handler:h=(async(e,t,i)=>t.close(i.statusCode,i.message)),store:y=new t}=e,f={windowMs:i,limit:a,message:n,statusCode:o,requestPropertyName:c,requestStorePropertyName:l,skipFailedRequests:u,skipSuccessfulRequests:m,keyGenerator:d,skip:w,handler:h,store:y};return s(y,f),e=>async t=>{const i=await e(t);return{...i,onMessage:async(e,s)=>{if(await w(e,s))return void await(i.onMessage?.(e,s));const{key:n,totalHits:o,resetTime:p}=await r(t,d,y),g="function"==typeof a?a(t):a,M=await g,q={limit:M,used:o,remaining:Math.max(M-o,0),resetTime:p};t.set(c,q),t.set(l,{getKey:y.get?.bind(y),resetKey:y.resetKey.bind(y)});let k=!1;const v=async()=>{k||(await y.decrement(n),k=!0)},T=async()=>{m&&await v()};if(o>M)return await T(),h(e,s,f);try{await(i.onMessage?.(e,s)),await T()}catch(e){u&&await v()}},onError:async(e,s)=>{if(u){const e=await d(t);await y.decrement(e)}i.onError?.(e,s)}}}}export{a as rateLimiter,n as webSocketLimiter};