UNPKG

riot-ratelimiter

Version:

A rate limiter handling rate-limits enforced by the riot-games api

189 lines (188 loc) 7.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const index_1 = require("../RateLimiter/index"); var RATELIMIT_TYPE; (function (RATELIMIT_TYPE) { RATELIMIT_TYPE[RATELIMIT_TYPE["APP"] = 0] = "APP"; RATELIMIT_TYPE[RATELIMIT_TYPE["METHOD"] = 1] = "METHOD"; RATELIMIT_TYPE[RATELIMIT_TYPE["SYNC"] = 2] = "SYNC"; RATELIMIT_TYPE[RATELIMIT_TYPE["BACKOFF"] = 3] = "BACKOFF"; })(RATELIMIT_TYPE = exports.RATELIMIT_TYPE || (exports.RATELIMIT_TYPE = {})); exports.RATELIMIT_TYPE_STRINGS = { [RATELIMIT_TYPE.METHOD]: 'method', [RATELIMIT_TYPE.APP]: 'app', [RATELIMIT_TYPE.SYNC]: 'sync', [RATELIMIT_TYPE.BACKOFF]: 'backoff' }; exports.RATELIMIT_INIT_SECONDS = 7200; exports.FACTOR_REQUEST_MARGIN_BELOW_5_SEC = 0.75; exports.FACTOR_REQUEST_MARGIN_ABOVE_5_SEC = 0.9; class RateLimit { constructor({ requests, seconds, type = RATELIMIT_TYPE.APP, count = 0 }, { debug = false } = {}) { this.timestampLastReset = Date.now(); this._requests = requests; this._seconds = seconds; this._type = type; this._count = count; this.startResetTimer(); this._debug = debug; this.timestampLastReset = Date.now(); this.requestsSafeBurst = (this.seconds <= 5) ? Math.floor(this.requests * exports.FACTOR_REQUEST_MARGIN_BELOW_5_SEC) : Math.floor(this.requests * exports.FACTOR_REQUEST_MARGIN_ABOVE_5_SEC); this.limiters = []; } get requests() { return this._requests; } get seconds() { return this._seconds; } get type() { return this._type; } get count() { return this._count; } get debug() { return this._debug; } static getRateLimitTypeString(type) { return exports.RATELIMIT_TYPE_STRINGS[type]; } addLimiter(limiter) { this.limiters.push(limiter); } reloadLimiters() { this.limiters = this.limiters.filter((limiter) => { return limiter.getLimits().find(limit => limit.equals(this)); }); } dispose() { clearTimeout(this.resetTimeout); this.limiters.forEach(limiter => limiter.notifyAboutRemovedLimit(this)); } static calcMSUntilReset(limitIntervalSeconds, timestampLastLimitReset = 0) { const timeSinceLastResetMS = Date.now() - timestampLastLimitReset; let remainingInterval = limitIntervalSeconds * 1000 - timeSinceLastResetMS; if (remainingInterval < 0) { remainingInterval *= -1; remainingInterval %= limitIntervalSeconds * 1000; } return remainingInterval; } check(strategy) { return this.getRemainingRequests(strategy) !== 0; } getSecondsUntilReset() { const remaingSeconds = ((this.seconds * 1000) - (Date.now() - this.timestampLastReset)) / 1000; return remaingSeconds > 0 ? remaingSeconds : 0; } getMaximumRequests(strategy) { if (this.isUsingSafetyMargin(strategy)) { return this.requestsSafeBurst; } else { return this.requests; } } getRemainingRequests(strategy) { let available; if (this.isUsingSafetyMargin(strategy)) { available = this.requestsSafeBurst; } else { available = this.requests; } let remaining = available - this._count; return remaining > 0 ? remaining : 0; } isUsingSafetyMargin(strategy) { return strategy === index_1.STRATEGY.BURST && this.type !== RATELIMIT_TYPE.SYNC && this.type !== RATELIMIT_TYPE.BACKOFF; } getSpreadInterval() { const remainingExecutionsInIntervall = this._requests - this._count; return RateLimit.calcMSUntilReset(this._seconds, this.timestampLastReset) / ((remainingExecutionsInIntervall > 0) ? remainingExecutionsInIntervall : 1); } increment(count = 0) { if (count > 0) { this._count += count; } else { this._count++; } } reset() { if (this.type === RATELIMIT_TYPE.BACKOFF) { this.limiters.forEach(limiter => { limiter.notifyAboutBackoffFinished(this); }); this.dispose(); } else { this._count = 0; this.timestampLastReset = Date.now(); if (!this.check(index_1.STRATEGY.BURST)) { if (this.debug) { console.log('resetting exceeded limit', this.toString()); } this.limiters.forEach(limiter => { limiter.notifyAboutExceededLimitReset(); }); } this.restartTimeout(); } } toString() { return `${exports.RATELIMIT_TYPE_STRINGS[this._type]} RateLimit: ${this._count}/${this._requests}:${this._seconds} | resetting in ${this.getSecondsUntilReset()}`; } update({ requests = this._requests, seconds = this._seconds, type = this._type, count = this._count }) { const wasExceededBeforeUpdate = !this.check(index_1.STRATEGY.BURST); this.updateValues({ requests, seconds, type, count }); const isExceededAfterUpdate = !this.check(index_1.STRATEGY.BURST); if (isExceededAfterUpdate || (!isExceededAfterUpdate && wasExceededBeforeUpdate)) { this.restartTimeout(); } this.notifyLimiters(); } updateSilently(limit) { this.updateValues(limit); } updateValues(limit) { if (this._debug) { console.log(`updating ${this.toString()}: requests: ${this._requests} to ${limit.requests} seconds ${this._seconds} to ${limit.seconds} count ${this._count} to ${limit.count}`); } this._requests = limit.requests; this._seconds = limit.seconds; this._type = limit.type; this._count = limit.count; } startResetTimer() { if (!this.resetTimeout) { if (this.debug && !this.check(index_1.STRATEGY.BURST)) { console.log('starting resetTimeout for exceeded limit' + this._seconds * 1000, this.toString()); } this.resetTimeout = setTimeout(() => { this.reset(); }, this._seconds * 1000); this.resetTimeout.unref(); } } notifyLimiters() { this.limiters.forEach(limiter => { limiter.notifyAboutLimitUpdate(this); }); } static compare(limit1, limit2) { const compareLimits = limit2.requests - limit1.requests; let compareSeconds = 0; if (compareLimits === 0) { compareSeconds = limit2.seconds - limit1.seconds; } return compareSeconds + compareLimits; } compareTo(comparable) { return RateLimit.compare(this, comparable); } equals(limit) { if (limit.type === RATELIMIT_TYPE.BACKOFF || limit.type === RATELIMIT_TYPE.SYNC) { return this.type === limit.type; } return this.compareTo(limit) === 0; } restartTimeout() { clearTimeout(this.resetTimeout); this.resetTimeout = null; this.startResetTimer(); } } exports.RateLimit = RateLimit;