ratelimit.js 3.1 KB

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