rate-limiter-flexible
Version:
Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM
64 lines (53 loc) • 1.99 kB
JavaScript
const RateLimiterEtcdTransactionFailedError = require('./component/RateLimiterEtcdTransactionFailedError');
const RateLimiterEtcdNonAtomic = require('./RateLimiterEtcdNonAtomic');
const MAX_TRANSACTION_TRIES = 5;
class RateLimiterEtcd extends RateLimiterEtcdNonAtomic {
/**
* Resolve with object used for {@link _getRateLimiterRes} to generate {@link RateLimiterRes}.
*/
async _upsert(rlKey, points, msDuration, forceExpire = false) {
const expire = msDuration > 0 ? Date.now() + msDuration : null;
let newValue = { points, expire };
let oldValue;
// If we need to force the expiration, just set the key.
if (forceExpire) {
await this.client
.put(rlKey)
.value(JSON.stringify(newValue));
} else {
// First try to add a new key
const added = await this.client
.if(rlKey, 'Version', '===', '0')
.then(this.client
.put(rlKey)
.value(JSON.stringify(newValue)))
.commit()
.then(result => !!result.succeeded);
// If the key already existed, try to update it in a transaction
if (!added) {
let success = false;
for (let i = 0; i < MAX_TRANSACTION_TRIES; i++) {
// eslint-disable-next-line no-await-in-loop
oldValue = await this._get(rlKey);
newValue = { points: oldValue.points + points, expire };
// eslint-disable-next-line no-await-in-loop
success = await this.client
.if(rlKey, 'Value', '===', JSON.stringify(oldValue))
.then(this.client
.put(rlKey)
.value(JSON.stringify(newValue)))
.commit()
.then(result => !!result.succeeded);
if (success) {
break;
}
}
if (!success) {
throw new RateLimiterEtcdTransactionFailedError('Could not set new value in a transaction.');
}
}
}
return newValue;
}
}
module.exports = RateLimiterEtcd;