discord-self-lite 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -23,6 +23,9 @@ class RestManager {
|
|
|
23
23
|
this.globalRateLimit = null; // { reset }
|
|
24
24
|
this.requestQueue = [];
|
|
25
25
|
this.processingQueue = false;
|
|
26
|
+
|
|
27
|
+
// Circuit breaker state for heavily rate-limited routes
|
|
28
|
+
this.circuitBreakers = new Map(); // routeKey -> { failures, lastFailure, openUntil }
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -33,7 +36,56 @@ class RestManager {
|
|
|
33
36
|
* @throws {Error} If the request fails
|
|
34
37
|
*/
|
|
35
38
|
async request(endpoint, options = {}) {
|
|
39
|
+
// Check rate limits before queuing the request with more aggressive logic
|
|
40
|
+
const method = options.method || "GET";
|
|
41
|
+
const routeKey = this.getRouteKey(endpoint, method);
|
|
42
|
+
|
|
43
|
+
// Check circuit breaker - reject immediately if route is temporarily disabled
|
|
44
|
+
const circuitBreaker = this.circuitBreakers.get(routeKey);
|
|
45
|
+
if (circuitBreaker && circuitBreaker.openUntil > Date.now()) {
|
|
46
|
+
const remainingTime = circuitBreaker.openUntil - Date.now();
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Circuit breaker open for ${routeKey}. Retry in ${remainingTime}ms`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.isRateLimited(endpoint, method)) {
|
|
53
|
+
const rateLimit = this.rateLimits.get(routeKey);
|
|
54
|
+
const globalLimited =
|
|
55
|
+
this.globalRateLimit && Date.now() < this.globalRateLimit.reset;
|
|
56
|
+
|
|
57
|
+
// Calculate delay with minimum 2s buffer and more conservative timing
|
|
58
|
+
const baseDelay = globalLimited
|
|
59
|
+
? this.globalRateLimit.reset - Date.now()
|
|
60
|
+
: rateLimit
|
|
61
|
+
? rateLimit.reset - Date.now()
|
|
62
|
+
: 1000;
|
|
63
|
+
|
|
64
|
+
// Add buffer time and ensure minimum delay
|
|
65
|
+
const delay = Math.max(baseDelay + 2000, 3000); // Minimum 3s delay
|
|
66
|
+
|
|
67
|
+
console.log(
|
|
68
|
+
`🚫 Pre-emptive rate limit wait: ${delay}ms for ${routeKey} (${endpoint})`,
|
|
69
|
+
);
|
|
70
|
+
await this.sleep(delay);
|
|
71
|
+
|
|
72
|
+
// Double-check after waiting - if still limited, reject immediately
|
|
73
|
+
if (this.isRateLimited(endpoint, method)) {
|
|
74
|
+
throw new Error(`Endpoint still rate limited after wait: ${routeKey}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
36
78
|
return new Promise((resolve, reject) => {
|
|
79
|
+
// Prevent queue from growing too large during rate limit storms
|
|
80
|
+
if (this.requestQueue.length >= 50) {
|
|
81
|
+
reject(
|
|
82
|
+
new Error(
|
|
83
|
+
`Request queue full (${this.requestQueue.length} requests). Rate limiting too aggressive.`,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
37
89
|
this.requestQueue.push({
|
|
38
90
|
endpoint,
|
|
39
91
|
options,
|
|
@@ -107,8 +159,8 @@ class RestManager {
|
|
|
107
159
|
const result = await this.makeRequest(endpoint, options);
|
|
108
160
|
resolve(result);
|
|
109
161
|
|
|
110
|
-
//
|
|
111
|
-
await this.sleep(
|
|
162
|
+
// Increased delay between requests to be more conservative with rate limits
|
|
163
|
+
await this.sleep(500);
|
|
112
164
|
} catch (error) {
|
|
113
165
|
reject(error);
|
|
114
166
|
}
|
|
@@ -152,25 +204,52 @@ class RestManager {
|
|
|
152
204
|
response.headers.get("x-ratelimit-global") === "true";
|
|
153
205
|
const routeKey = this.getRouteKey(endpoint, options.method || "GET");
|
|
154
206
|
|
|
207
|
+
// Exponential backoff: increase delay based on attempt number
|
|
208
|
+
const backoffDelay = retryAfter * Math.pow(1.5, attempt);
|
|
209
|
+
|
|
155
210
|
console.log(
|
|
156
211
|
`🚫 Rate limited! ${
|
|
157
212
|
isGlobal ? "Global" : `Route (${routeKey})`
|
|
158
|
-
} limit for ${endpoint}, retry after ${
|
|
213
|
+
} limit for ${endpoint}, retry after ${backoffDelay}ms (attempt ${attempt + 1})`,
|
|
159
214
|
);
|
|
160
215
|
|
|
216
|
+
// Circuit breaker: track consecutive failures
|
|
217
|
+
if (!isGlobal) {
|
|
218
|
+
const breaker = this.circuitBreakers.get(routeKey) || {
|
|
219
|
+
failures: 0,
|
|
220
|
+
lastFailure: 0,
|
|
221
|
+
openUntil: 0,
|
|
222
|
+
};
|
|
223
|
+
breaker.failures++;
|
|
224
|
+
breaker.lastFailure = Date.now();
|
|
225
|
+
|
|
226
|
+
// Open circuit breaker after 3 consecutive failures within 30 seconds
|
|
227
|
+
if (
|
|
228
|
+
breaker.failures >= 3 &&
|
|
229
|
+
Date.now() - breaker.lastFailure < 30000
|
|
230
|
+
) {
|
|
231
|
+
breaker.openUntil = Date.now() + 60000; // Open for 60 seconds
|
|
232
|
+
console.log(
|
|
233
|
+
`🔌 Circuit breaker OPENED for ${routeKey} - too many rate limits`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.circuitBreakers.set(routeKey, breaker);
|
|
238
|
+
}
|
|
239
|
+
|
|
161
240
|
if (isGlobal) {
|
|
162
|
-
this.globalRateLimit = { reset: Date.now() +
|
|
241
|
+
this.globalRateLimit = { reset: Date.now() + backoffDelay };
|
|
163
242
|
} else {
|
|
164
243
|
// Update route-specific rate limit for 429 responses
|
|
165
244
|
this.rateLimits.set(routeKey, {
|
|
166
245
|
remaining: 0,
|
|
167
|
-
reset: Date.now() +
|
|
246
|
+
reset: Date.now() + backoffDelay,
|
|
168
247
|
limit: this.rateLimits.get(routeKey)?.limit || 5, // Default limit if unknown
|
|
169
248
|
updatedAt: Date.now(),
|
|
170
249
|
});
|
|
171
250
|
}
|
|
172
251
|
|
|
173
|
-
await this.sleep(
|
|
252
|
+
await this.sleep(backoffDelay);
|
|
174
253
|
attempt++;
|
|
175
254
|
continue;
|
|
176
255
|
}
|
|
@@ -184,9 +263,16 @@ class RestManager {
|
|
|
184
263
|
|
|
185
264
|
// Handle 204 No Content (successful but no body)
|
|
186
265
|
if (response.status === 204) {
|
|
266
|
+
// Reset circuit breaker on successful response
|
|
267
|
+
const routeKey = this.getRouteKey(endpoint, options.method || "GET");
|
|
268
|
+
this.circuitBreakers.delete(routeKey);
|
|
187
269
|
return null;
|
|
188
270
|
}
|
|
189
271
|
|
|
272
|
+
// Reset circuit breaker on successful response
|
|
273
|
+
const routeKey = this.getRouteKey(endpoint, options.method || "GET");
|
|
274
|
+
this.circuitBreakers.delete(routeKey);
|
|
275
|
+
|
|
190
276
|
return await response.json();
|
|
191
277
|
} catch (error) {
|
|
192
278
|
if (attempt === maxRetries - 1) {
|
|
@@ -258,6 +344,23 @@ class RestManager {
|
|
|
258
344
|
this.rateLimits.delete(key);
|
|
259
345
|
}
|
|
260
346
|
}
|
|
347
|
+
|
|
348
|
+
// Clean up expired circuit breakers
|
|
349
|
+
for (const [key, breaker] of this.circuitBreakers.entries()) {
|
|
350
|
+
if (now > breaker.openUntil) {
|
|
351
|
+
this.circuitBreakers.delete(key);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Sleep for a specified number of milliseconds
|
|
358
|
+
* @private
|
|
359
|
+
* @param {number} ms - Milliseconds to sleep
|
|
360
|
+
* @returns {Promise<void>}
|
|
361
|
+
*/
|
|
362
|
+
sleep(ms) {
|
|
363
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
261
364
|
}
|
|
262
365
|
|
|
263
366
|
/**
|