From 6df233de14e343578fdee3c99a85494e898c7ecc Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Thu, 16 Nov 2023 12:49:05 +0200 Subject: [PATCH] feat: present x-ratelimit-scope for 429s hit (#9973) * feat: present x-ratelimit-scope for 429s hit * fix: get scope from headers for burst too --- packages/rest/src/lib/errors/RateLimitError.ts | 4 ++++ packages/rest/src/lib/handlers/BurstHandler.ts | 5 ++++- packages/rest/src/lib/handlers/SequentialHandler.ts | 4 ++++ packages/rest/src/lib/utils/types.ts | 7 +++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/rest/src/lib/errors/RateLimitError.ts b/packages/rest/src/lib/errors/RateLimitError.ts index 9f8045eace58..305256d61700 100644 --- a/packages/rest/src/lib/errors/RateLimitError.ts +++ b/packages/rest/src/lib/errors/RateLimitError.ts @@ -21,6 +21,8 @@ export class RateLimitError extends Error implements RateLimitData { public sublimitTimeout: number; + public scope: RateLimitData['scope']; + public constructor({ timeToReset, limit, @@ -32,6 +34,7 @@ export class RateLimitError extends Error implements RateLimitData { global, retryAfter, sublimitTimeout, + scope, }: RateLimitData) { super(); this.timeToReset = timeToReset; @@ -44,6 +47,7 @@ export class RateLimitError extends Error implements RateLimitData { this.global = global; this.retryAfter = retryAfter; this.sublimitTimeout = sublimitTimeout; + this.scope = scope; } /** diff --git a/packages/rest/src/lib/handlers/BurstHandler.ts b/packages/rest/src/lib/handlers/BurstHandler.ts index 83573980786f..bed42999c278 100644 --- a/packages/rest/src/lib/handlers/BurstHandler.ts +++ b/packages/rest/src/lib/handlers/BurstHandler.ts @@ -2,7 +2,7 @@ import type { RequestInit } from 'undici'; import type { REST } from '../REST.js'; import type { IHandler } from '../interfaces/Handler.js'; import { RESTEvents } from '../utils/constants.js'; -import type { ResponseLike, HandlerRequestData, RouteData } from '../utils/types.js'; +import type { ResponseLike, HandlerRequestData, RouteData, RateLimitData } from '../utils/types.js'; import { onRateLimit, sleep } from '../utils/utils.js'; import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js'; @@ -102,6 +102,7 @@ export class BurstHandler implements IHandler { } else if (status === 429) { // Unexpected ratelimit const isGlobal = res.headers.has('X-RateLimit-Global'); + const scope = (res.headers.get('X-RateLimit-Scope') ?? 'user') as RateLimitData['scope']; await onRateLimit(this.manager, { global: isGlobal, @@ -114,6 +115,7 @@ export class BurstHandler implements IHandler { timeToReset: retryAfter, retryAfter, sublimitTimeout: 0, + scope, }); this.debug( @@ -128,6 +130,7 @@ export class BurstHandler implements IHandler { ` Limit : ${Number.POSITIVE_INFINITY}`, ` Retry After : ${retryAfter}ms`, ` Sublimit : None`, + ` Scope : ${scope}`, ].join('\n'), ); diff --git a/packages/rest/src/lib/handlers/SequentialHandler.ts b/packages/rest/src/lib/handlers/SequentialHandler.ts index da9aefa8cf11..9472d8a6c5e3 100644 --- a/packages/rest/src/lib/handlers/SequentialHandler.ts +++ b/packages/rest/src/lib/handlers/SequentialHandler.ts @@ -237,6 +237,7 @@ export class SequentialHandler implements IHandler { timeToReset: timeout, retryAfter: timeout, sublimitTimeout: 0, + scope: 'user', }; // Let library users know they have hit a rate limit @@ -281,6 +282,7 @@ export class SequentialHandler implements IHandler { const reset = res.headers.get('X-RateLimit-Reset-After'); const hash = res.headers.get('X-RateLimit-Bucket'); const retry = res.headers.get('Retry-After'); + const scope = (res.headers.get('X-RateLimit-Scope') ?? 'user') as RateLimitData['scope']; // Update the total number of requests that can be made before the rate limit resets this.limit = limit ? Number(limit) : Number.POSITIVE_INFINITY; @@ -359,6 +361,7 @@ export class SequentialHandler implements IHandler { timeToReset: timeout, retryAfter, sublimitTimeout: sublimitTimeout ?? 0, + scope, }); this.debug( @@ -373,6 +376,7 @@ export class SequentialHandler implements IHandler { ` Limit : ${limit}`, ` Retry After : ${retryAfter}ms`, ` Sublimit : ${sublimitTimeout ? `${sublimitTimeout}ms` : 'None'}`, + ` Scope : ${scope}`, ].join('\n'), ); diff --git a/packages/rest/src/lib/utils/types.ts b/packages/rest/src/lib/utils/types.ts index fa9ea033745d..0b204a01da68 100644 --- a/packages/rest/src/lib/utils/types.ts +++ b/packages/rest/src/lib/utils/types.ts @@ -164,6 +164,13 @@ export interface RateLimitData { * The route being hit in this request */ route: string; + /** + * The scope of the rate limit that was hit. + * + * This can be `user` for rate limits that are per client, `global` for rate limits that affect all clients or `shared` for rate limits that + * are shared per resource. + */ + scope: 'global' | 'shared' | 'user'; /** * The time, in milliseconds, that will need to pass before the sublimit lock for the route resets, and requests that fall under a sublimit * can be retried