diff --git a/packages/rest/src/lib/errors/RateLimitError.ts b/packages/rest/src/lib/errors/RateLimitError.ts index ecc408c1c..9f8045eac 100644 --- a/packages/rest/src/lib/errors/RateLimitError.ts +++ b/packages/rest/src/lib/errors/RateLimitError.ts @@ -17,7 +17,22 @@ export class RateLimitError extends Error implements RateLimitData { public global: boolean; - public constructor({ timeToReset, limit, method, hash, url, route, majorParameter, global }: RateLimitData) { + public retryAfter: number; + + public sublimitTimeout: number; + + public constructor({ + timeToReset, + limit, + method, + hash, + url, + route, + majorParameter, + global, + retryAfter, + sublimitTimeout, + }: RateLimitData) { super(); this.timeToReset = timeToReset; this.limit = limit; @@ -27,6 +42,8 @@ export class RateLimitError extends Error implements RateLimitData { this.route = route; this.majorParameter = majorParameter; this.global = global; + this.retryAfter = retryAfter; + this.sublimitTimeout = sublimitTimeout; } /** diff --git a/packages/rest/src/lib/handlers/BurstHandler.ts b/packages/rest/src/lib/handlers/BurstHandler.ts index e534c1e28..835739807 100644 --- a/packages/rest/src/lib/handlers/BurstHandler.ts +++ b/packages/rest/src/lib/handlers/BurstHandler.ts @@ -102,16 +102,20 @@ export class BurstHandler implements IHandler { } else if (status === 429) { // Unexpected ratelimit const isGlobal = res.headers.has('X-RateLimit-Global'); + await onRateLimit(this.manager, { - timeToReset: retryAfter, - limit: Number.POSITIVE_INFINITY, + global: isGlobal, method, - hash: this.hash, url, route: routeId.bucketRoute, majorParameter: this.majorParameter, - global: isGlobal, + hash: this.hash, + limit: Number.POSITIVE_INFINITY, + timeToReset: retryAfter, + retryAfter, + sublimitTimeout: 0, }); + this.debug( [ 'Encountered unexpected 429 rate limit', diff --git a/packages/rest/src/lib/handlers/SequentialHandler.ts b/packages/rest/src/lib/handlers/SequentialHandler.ts index 216af00af..da9aefa8c 100644 --- a/packages/rest/src/lib/handlers/SequentialHandler.ts +++ b/packages/rest/src/lib/handlers/SequentialHandler.ts @@ -227,19 +227,23 @@ export class SequentialHandler implements IHandler { } const rateLimitData: RateLimitData = { - timeToReset: timeout, - limit, + global: isGlobal, method: options.method ?? 'get', - hash: this.hash, url, route: routeId.bucketRoute, majorParameter: this.majorParameter, - global: isGlobal, + hash: this.hash, + limit, + timeToReset: timeout, + retryAfter: timeout, + sublimitTimeout: 0, }; + // Let library users know they have hit a rate limit this.manager.emit(RESTEvents.RateLimited, rateLimitData); // Determine whether a RateLimitError should be thrown await onRateLimit(this.manager, rateLimitData); + // When not erroring, emit debug for what is happening if (isGlobal) { this.debug(`Global rate limit hit, blocking all requests for ${timeout}ms`); @@ -345,15 +349,18 @@ export class SequentialHandler implements IHandler { } await onRateLimit(this.manager, { - timeToReset: timeout, - limit, + global: isGlobal, method, - hash: this.hash, url, route: routeId.bucketRoute, majorParameter: this.majorParameter, - global: isGlobal, + hash: this.hash, + limit, + timeToReset: timeout, + retryAfter, + sublimitTimeout: sublimitTimeout ?? 0, }); + this.debug( [ 'Encountered unexpected 429 rate limit', @@ -368,6 +375,7 @@ export class SequentialHandler implements IHandler { ` Sublimit : ${sublimitTimeout ? `${sublimitTimeout}ms` : 'None'}`, ].join('\n'), ); + // If caused by a sublimit, wait it out here so other requests on the route can be handled if (sublimitTimeout) { // Normally the sublimit queue will not exist, however, if a sublimit is hit while in the sublimit queue, it will diff --git a/packages/rest/src/lib/utils/types.ts b/packages/rest/src/lib/utils/types.ts index 5b1c60fb7..07070c850 100644 --- a/packages/rest/src/lib/utils/types.ts +++ b/packages/rest/src/lib/utils/types.ts @@ -155,12 +155,23 @@ export interface RateLimitData { * The HTTP method being performed */ method: string; + /** + * The time, in milliseconds, that will need to pass before this specific request can be retried + */ + retryAfter: number; /** * The route being hit in this request */ route: string; /** - * The time, in milliseconds, until the request-lock is reset + * 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 + * + * This is only present on certain sublimits, and `0` otherwise + */ + sublimitTimeout: number; + /** + * The time, in milliseconds, until the route's request-lock is reset */ timeToReset: number; /**