fet: Add changes lol
Signed-off-by: Florian BOUILLON <f.bouillon@aptatio.com>
This commit is contained in:
3
src/libs/README.md
Normal file
3
src/libs/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Libs
|
||||
|
||||
Globally independent objects/classes/functions that MUST be unit testable by themselve
|
@ -1,6 +1,6 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import StatusCode from './HTTP/StatusCode'
|
||||
import { buildRFC7807 } from './RFCs/RFC7807'
|
||||
import StatusCode from './StatusCode'
|
||||
import ResponseBuilder from './ResponseBuilder'
|
||||
|
||||
interface StorageItem {
|
||||
pointsRemaining: number
|
||||
@ -33,36 +33,40 @@ export default class RateLimiter {
|
||||
|
||||
private storage: Record<string, StorageItem> = {}
|
||||
|
||||
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: RateLimiter.points,
|
||||
timeReset: now + RateLimiter.timeSpan
|
||||
pointsRemaining: this.points,
|
||||
timeReset: now + this.timeSpan
|
||||
}
|
||||
}
|
||||
if (item.timeReset <= now) {
|
||||
item.timeReset = now + RateLimiter.timeSpan
|
||||
item.pointsRemaining = RateLimiter.points
|
||||
item.timeReset = now + this.timeSpan
|
||||
item.pointsRemaining = this.points
|
||||
}
|
||||
item.pointsRemaining -= value
|
||||
this.storage[key] = item
|
||||
const headers: RateLimitHeaders = {
|
||||
"X-RateLimit-Limit": RateLimiter.points.toFixed(0),
|
||||
"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'
|
||||
})
|
||||
resp.headers.append('Retry-After', (item.timeReset - now).toFixed(0))
|
||||
objectLoop(headers, (value, key) => {
|
||||
resp.headers.append(key, value)
|
||||
})
|
||||
}, res)
|
||||
res.addHeader('Retry-After', (item.timeReset - now).toFixed(0))
|
||||
res.addHeaders(headers as any)
|
||||
return resp
|
||||
}
|
||||
return headers
|
||||
|
@ -18,18 +18,14 @@ export default class ResponseBuilder {
|
||||
return this
|
||||
}
|
||||
|
||||
private _headers: Headers = new Headers()
|
||||
public headers(headers: HeadersInit ) {
|
||||
if (headers instanceof Headers) {
|
||||
this._headers = headers
|
||||
} else {
|
||||
this._headers = new Headers(headers)
|
||||
}
|
||||
private _headers: Record<string, string> = {}
|
||||
public headers(headers: Record<string, string>) {
|
||||
this._headers = headers
|
||||
return this
|
||||
}
|
||||
|
||||
public addHeader(key: string, value: string) {
|
||||
this._headers.append(key, value)
|
||||
this._headers[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
@ -41,7 +37,7 @@ export default class ResponseBuilder {
|
||||
}
|
||||
|
||||
public removeHeader(key: string) {
|
||||
this._headers.delete(key)
|
||||
delete this._headers[key]
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
* @returns a number if parsing happened correctly or undefined
|
||||
*/
|
||||
function parseNumber(str: string): number | undefined {
|
||||
if (!/^(\d|\.)+$/g.test(str)) {
|
||||
if (!/^-?(\d|\.)+$/g.test(str)) {
|
||||
return undefined
|
||||
}
|
||||
const float = parseFloat(str)
|
||||
@ -53,10 +53,10 @@ export function getParams(data: string) {
|
||||
const lines = data.split('\n').filter((it) => it.startsWith(';') && it.includes('='))
|
||||
// create the config object
|
||||
const obj: Record<string, string | number> = {}
|
||||
// loop through eacj config
|
||||
// loop through each config
|
||||
for (const line of lines) {
|
||||
// get its key and value
|
||||
const [key, value] = line.split('=', 2).map((it) => it.slice(1).trim())
|
||||
const [key, value] = line.slice(1).split(/ *= */, 2).map((it) => it.trim())
|
||||
// sip if it has no key or value
|
||||
if (!key || !value) {
|
||||
continue
|
||||
@ -78,10 +78,10 @@ export function getParams(data: string) {
|
||||
if (offset > 0) {
|
||||
realKey = `${realKey}_${offset}`
|
||||
}
|
||||
// detect key collisions
|
||||
if (obj[realKey] && obj[realKey] !== realValue) {
|
||||
throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
|
||||
}
|
||||
// detect key collisions (it will never happens with the while above)
|
||||
// if (obj[realKey] && obj[realKey] !== realValue) {
|
||||
// throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
|
||||
// }
|
||||
// set the value to the key
|
||||
obj[realKey] = realValue
|
||||
// transform the time to a number of seconds
|
||||
|
Reference in New Issue
Block a user