package.build.cjs.ratelimit.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utils Show documentation
Show all versions of utils Show documentation
Utilities for all Sentry JavaScript SDKs
Object.defineProperty(exports, '__esModule', { value: true });
// Intentionally keeping the key broad, as we don't know for sure what rate limit headers get returned from backend
const DEFAULT_RETRY_AFTER = 60 * 1000; // 60 seconds
/**
* Extracts Retry-After value from the request header or returns default value
* @param header string representation of 'Retry-After' header
* @param now current unix timestamp
*
*/
function parseRetryAfterHeader(header, now = Date.now()) {
const headerDelay = parseInt(`${header}`, 10);
if (!isNaN(headerDelay)) {
return headerDelay * 1000;
}
const headerDate = Date.parse(`${header}`);
if (!isNaN(headerDate)) {
return headerDate - now;
}
return DEFAULT_RETRY_AFTER;
}
/**
* Gets the time that the given category is disabled until for rate limiting.
* In case no category-specific limit is set but a general rate limit across all categories is active,
* that time is returned.
*
* @return the time in ms that the category is disabled until or 0 if there's no active rate limit.
*/
function disabledUntil(limits, dataCategory) {
return limits[dataCategory] || limits.all || 0;
}
/**
* Checks if a category is rate limited
*/
function isRateLimited(limits, dataCategory, now = Date.now()) {
return disabledUntil(limits, dataCategory) > now;
}
/**
* Update ratelimits from incoming headers.
*
* @return the updated RateLimits object.
*/
function updateRateLimits(
limits,
{ statusCode, headers },
now = Date.now(),
) {
const updatedRateLimits = {
...limits,
};
// "The name is case-insensitive."
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/get
const rateLimitHeader = headers && headers['x-sentry-rate-limits'];
const retryAfterHeader = headers && headers['retry-after'];
if (rateLimitHeader) {
/**
* rate limit headers are of the form
* ,,..
* where each is of the form
* : : : :
* where
* is a delay in seconds
* is the event type(s) (error, transaction, etc) being rate limited and is of the form
* ;;...
* is what's being limited (org, project, or key) - ignored by SDK
* is an arbitrary string like "org_quota" - ignored by SDK
* Semicolon-separated list of metric namespace identifiers. Defines which namespace(s) will be affected.
* Only present if rate limit applies to the metric_bucket data category.
*/
for (const limit of rateLimitHeader.trim().split(',')) {
const [retryAfter, categories, , , namespaces] = limit.split(':', 5) ;
const headerDelay = parseInt(retryAfter, 10);
const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default
if (!categories) {
updatedRateLimits.all = now + delay;
} else {
for (const category of categories.split(';')) {
if (category === 'metric_bucket') {
// namespaces will be present when category === 'metric_bucket'
if (!namespaces || namespaces.split(';').includes('custom')) {
updatedRateLimits[category] = now + delay;
}
} else {
updatedRateLimits[category] = now + delay;
}
}
}
}
} else if (retryAfterHeader) {
updatedRateLimits.all = now + parseRetryAfterHeader(retryAfterHeader, now);
} else if (statusCode === 429) {
updatedRateLimits.all = now + 60 * 1000;
}
return updatedRateLimits;
}
exports.DEFAULT_RETRY_AFTER = DEFAULT_RETRY_AFTER;
exports.disabledUntil = disabledUntil;
exports.isRateLimited = isRateLimited;
exports.parseRetryAfterHeader = parseRetryAfterHeader;
exports.updateRateLimits = updateRateLimits;
//# sourceMappingURL=ratelimit.js.map