/**
* Creates a rate limiter for Sentry event reporting, keyed by an arbitrary
* string (typically the Sentry fingerprint or error message). State lives in
* a Map per rate limiter instance: one instance per process is the expected
* usage.
*
* @param {Function} [nowFn=Date.now] clock function (injectable for tests)
* @returns {Object} rate limiter with one method, {@link shouldReport}.
*/
export function createSentryRateLimiter (nowFn = Date.now) {
const tracking = new Map()
return {
/**
* Record an event and decide whether it should be reported to Sentry.
*
* @param {String} key rate-limit key; typically the error fingerprint or
* message so that Sentry grouping lines up with our suppression.
* @param {Number} windowMs rate-limit window in ms; within this window
* after a report, subsequent events for the same key are suppressed.
* @returns {{report: Boolean, suppressedCount: Number}} when `report` is
* true, `suppressedCount` is the number of events suppressed for this
* key since the last report (0 if none were suppressed). When `report`
* is false, `suppressedCount` is 0 (caller should not surface it).
*/
shouldReport (key, windowMs) {
const now = nowFn()
const state = tracking.get(key)
if (state && now - state.lastReported < windowMs) {
state.suppressed++
return { report: false, suppressedCount: 0 }
}
const suppressedCount = state?.suppressed ?? 0
tracking.set(key, { lastReported: now, suppressed: 0 })
return { report: true, suppressedCount }
}
}
}