ratelimit.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. // Intentionally keeping the key broad, as we don't know for sure what rate limit headers get returned from backend
  3. const DEFAULT_RETRY_AFTER = 60 * 1000; // 60 seconds
  4. /**
  5. * Extracts Retry-After value from the request header or returns default value
  6. * @param header string representation of 'Retry-After' header
  7. * @param now current unix timestamp
  8. *
  9. */
  10. function parseRetryAfterHeader(header, now = Date.now()) {
  11. const headerDelay = parseInt(`${header}`, 10);
  12. if (!isNaN(headerDelay)) {
  13. return headerDelay * 1000;
  14. }
  15. const headerDate = Date.parse(`${header}`);
  16. if (!isNaN(headerDate)) {
  17. return headerDate - now;
  18. }
  19. return DEFAULT_RETRY_AFTER;
  20. }
  21. /**
  22. * Gets the time that the given category is disabled until for rate limiting.
  23. * In case no category-specific limit is set but a general rate limit across all categories is active,
  24. * that time is returned.
  25. *
  26. * @return the time in ms that the category is disabled until or 0 if there's no active rate limit.
  27. */
  28. function disabledUntil(limits, category) {
  29. return limits[category] || limits.all || 0;
  30. }
  31. /**
  32. * Checks if a category is rate limited
  33. */
  34. function isRateLimited(limits, category, now = Date.now()) {
  35. return disabledUntil(limits, category) > now;
  36. }
  37. /**
  38. * Update ratelimits from incoming headers.
  39. *
  40. * @return the updated RateLimits object.
  41. */
  42. function updateRateLimits(
  43. limits,
  44. { statusCode, headers },
  45. now = Date.now(),
  46. ) {
  47. const updatedRateLimits = {
  48. ...limits,
  49. };
  50. // "The name is case-insensitive."
  51. // https://developer.mozilla.org/en-US/docs/Web/API/Headers/get
  52. const rateLimitHeader = headers && headers['x-sentry-rate-limits'];
  53. const retryAfterHeader = headers && headers['retry-after'];
  54. if (rateLimitHeader) {
  55. /**
  56. * rate limit headers are of the form
  57. * <header>,<header>,..
  58. * where each <header> is of the form
  59. * <retry_after>: <categories>: <scope>: <reason_code>
  60. * where
  61. * <retry_after> is a delay in seconds
  62. * <categories> is the event type(s) (error, transaction, etc) being rate limited and is of the form
  63. * <category>;<category>;...
  64. * <scope> is what's being limited (org, project, or key) - ignored by SDK
  65. * <reason_code> is an arbitrary string like "org_quota" - ignored by SDK
  66. */
  67. for (const limit of rateLimitHeader.trim().split(',')) {
  68. const [retryAfter, categories] = limit.split(':', 2);
  69. const headerDelay = parseInt(retryAfter, 10);
  70. const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default
  71. if (!categories) {
  72. updatedRateLimits.all = now + delay;
  73. } else {
  74. for (const category of categories.split(';')) {
  75. updatedRateLimits[category] = now + delay;
  76. }
  77. }
  78. }
  79. } else if (retryAfterHeader) {
  80. updatedRateLimits.all = now + parseRetryAfterHeader(retryAfterHeader, now);
  81. } else if (statusCode === 429) {
  82. updatedRateLimits.all = now + 60 * 1000;
  83. }
  84. return updatedRateLimits;
  85. }
  86. exports.DEFAULT_RETRY_AFTER = DEFAULT_RETRY_AFTER;
  87. exports.disabledUntil = disabledUntil;
  88. exports.isRateLimited = isRateLimited;
  89. exports.parseRetryAfterHeader = parseRetryAfterHeader;
  90. exports.updateRateLimits = updateRateLimits;
  91. //# sourceMappingURL=ratelimit.js.map