mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-19 13:03:31 +01:00
feat(REST): dynamic rate limit offsets (#10099)
* feat(REST): dynamic rate limit offsets * chore: update tests * chore: better doc comment Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * fix: don't overlook globalReset Co-authored-by: ckohen <chaikohen@gmail.com> --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: ckohen <chaikohen@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import type { REST } from '../REST.js';
|
||||
import type { IHandler } from '../interfaces/Handler.js';
|
||||
import { RESTEvents } from '../utils/constants.js';
|
||||
import type { ResponseLike, HandlerRequestData, RouteData, RateLimitData } from '../utils/types.js';
|
||||
import { onRateLimit, sleep } from '../utils/utils.js';
|
||||
import { normalizeRateLimitOffset, onRateLimit, sleep } from '../utils/utils.js';
|
||||
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,8 @@ export class BurstHandler implements IHandler {
|
||||
const retry = res.headers.get('Retry-After');
|
||||
|
||||
// Amount of time in milliseconds until we should retry if rate limited (globally or otherwise)
|
||||
if (retry) retryAfter = Number(retry) * 1_000 + this.manager.options.offset;
|
||||
const offset = normalizeRateLimitOffset(this.manager.options.offset, routeId.bucketRoute);
|
||||
if (retry) retryAfter = Number(retry) * 1_000 + offset;
|
||||
|
||||
// Count the invalid requests
|
||||
if (status === 401 || status === 403 || status === 429) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { REST } from '../REST.js';
|
||||
import type { IHandler } from '../interfaces/Handler.js';
|
||||
import { RESTEvents } from '../utils/constants.js';
|
||||
import type { RateLimitData, ResponseLike, HandlerRequestData, RouteData } from '../utils/types.js';
|
||||
import { hasSublimit, onRateLimit, sleep } from '../utils/utils.js';
|
||||
import { hasSublimit, normalizeRateLimitOffset, onRateLimit, sleep } from '../utils/utils.js';
|
||||
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
||||
|
||||
const enum QueueType {
|
||||
@@ -104,8 +104,9 @@ export class SequentialHandler implements IHandler {
|
||||
/**
|
||||
* The time until queued requests can continue
|
||||
*/
|
||||
private get timeToReset(): number {
|
||||
return this.reset + this.manager.options.offset - Date.now();
|
||||
private getTimeToReset(routeId: RouteData): number {
|
||||
const offset = normalizeRateLimitOffset(this.manager.options.offset, routeId.bucketRoute);
|
||||
return this.reset + offset - Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,9 +210,11 @@ export class SequentialHandler implements IHandler {
|
||||
let delay: Promise<void>;
|
||||
|
||||
if (isGlobal) {
|
||||
const offset = normalizeRateLimitOffset(this.manager.options.offset, routeId.bucketRoute);
|
||||
|
||||
// Set RateLimitData based on the global limit
|
||||
limit = this.manager.options.globalRequestsPerSecond;
|
||||
timeout = this.manager.globalReset + this.manager.options.offset - Date.now();
|
||||
timeout = this.manager.globalReset + offset - Date.now();
|
||||
// If this is the first task to reach the global timeout, set the global delay
|
||||
if (!this.manager.globalDelay) {
|
||||
// The global delay function clears the global delay state when it is resolved
|
||||
@@ -222,7 +225,7 @@ export class SequentialHandler implements IHandler {
|
||||
} else {
|
||||
// Set RateLimitData based on the route-specific limit
|
||||
limit = this.limit;
|
||||
timeout = this.timeToReset;
|
||||
timeout = this.getTimeToReset(routeId);
|
||||
delay = sleep(timeout);
|
||||
}
|
||||
|
||||
@@ -284,15 +287,17 @@ export class SequentialHandler implements IHandler {
|
||||
const retry = res.headers.get('Retry-After');
|
||||
const scope = (res.headers.get('X-RateLimit-Scope') ?? 'user') as RateLimitData['scope'];
|
||||
|
||||
const offset = normalizeRateLimitOffset(this.manager.options.offset, routeId.bucketRoute);
|
||||
|
||||
// Update the total number of requests that can be made before the rate limit resets
|
||||
this.limit = limit ? Number(limit) : Number.POSITIVE_INFINITY;
|
||||
// Update the number of remaining requests that can be made before the rate limit resets
|
||||
this.remaining = remaining ? Number(remaining) : 1;
|
||||
// Update the time when this rate limit resets (reset-after is in seconds)
|
||||
this.reset = reset ? Number(reset) * 1_000 + Date.now() + this.manager.options.offset : Date.now();
|
||||
this.reset = reset ? Number(reset) * 1_000 + Date.now() + offset : Date.now();
|
||||
|
||||
// Amount of time in milliseconds until we should retry if rate limited (globally or otherwise)
|
||||
if (retry) retryAfter = Number(retry) * 1_000 + this.manager.options.offset;
|
||||
if (retry) retryAfter = Number(retry) * 1_000 + offset;
|
||||
|
||||
// Handle buckets via the hash header retroactively
|
||||
if (hash && hash !== this.hash) {
|
||||
@@ -341,13 +346,15 @@ export class SequentialHandler implements IHandler {
|
||||
let timeout: number;
|
||||
|
||||
if (isGlobal) {
|
||||
const offset = normalizeRateLimitOffset(this.manager.options.offset, routeId.bucketRoute);
|
||||
|
||||
// Set RateLimitData based on the global limit
|
||||
limit = this.manager.options.globalRequestsPerSecond;
|
||||
timeout = this.manager.globalReset + this.manager.options.offset - Date.now();
|
||||
timeout = this.manager.globalReset + offset - Date.now();
|
||||
} else {
|
||||
// Set RateLimitData based on the route-specific limit
|
||||
limit = this.limit;
|
||||
timeout = this.timeToReset;
|
||||
timeout = this.getTimeToReset(routeId);
|
||||
}
|
||||
|
||||
await onRateLimit(this.manager, {
|
||||
|
||||
Reference in New Issue
Block a user