const Timer = require('../util/timer'); class RateLimiter { /** * A utility for limiting the rate of repetitive send operations, such as * bluetooth messages being sent to hardware devices. It uses the token bucket * strategy: a counter accumulates tokens at a steady rate, and each send costs * a token. If no tokens remain, it's not okay to send. * @param {number} maxRate the maximum number of sends allowed per second * @constructor */ constructor (maxRate) { /** * The maximum number of tokens. * @type {number} */ this._maxTokens = maxRate; /** * The interval in milliseconds for refilling one token. It is calculated * so that the tokens will be filled to maximum in one second. * @type {number} */ this._refillInterval = 1000 / maxRate; /** * The current number of tokens in the bucket. * @type {number} */ this._count = this._maxTokens; this._timer = new Timer(); this._timer.start(); /** * The last time in milliseconds when the token count was updated. * @type {number} */ this._lastUpdateTime = this._timer.timeElapsed(); } /** * Check if it is okay to send a message, by updating the token count, * taking a token and then checking if we are still under the rate limit. * @return {boolean} true if we are under the rate limit */ okayToSend () { // Calculate the number of tokens to refill the bucket with, based on the // amount of time since the last refill. const now = this._timer.timeElapsed(); const timeSinceRefill = now - this._lastUpdateTime; const refillCount = Math.floor(timeSinceRefill / this._refillInterval); // If we're adding at least one token, reset _lastUpdateTime to now. // Otherwise, don't reset it so that we can continue measuring time until // the next refill. if (refillCount > 0) { this._lastUpdateTime = now; } // Refill the tokens up to the maximum this._count = Math.min(this._maxTokens, this._count + refillCount); // If we have at least one token, use one, and it's okay to send. if (this._count > 0) { this._count--; return true; } return false; } } module.exports = RateLimiter;