fix sync to keycloak
This commit is contained in:
parent
81288f8db3
commit
76fc488d25
3 changed files with 303 additions and 10 deletions
|
|
@ -1,5 +1,121 @@
|
|||
import { DecodedJwt, createDecoder } from "fast-jwt";
|
||||
|
||||
/**
|
||||
* RateLimiter
|
||||
* Limits the rate of API calls to avoid overwhelming the server
|
||||
*/
|
||||
export class RateLimiter {
|
||||
private requestsPerSecond: number;
|
||||
private requestTimes: number[] = [];
|
||||
|
||||
constructor(requestsPerSecond: number = 10) {
|
||||
this.requestsPerSecond = requestsPerSecond;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttle requests to stay within rate limit
|
||||
* Waits if rate limit would be exceeded
|
||||
*/
|
||||
async throttle(): Promise<void> {
|
||||
const now = Date.now();
|
||||
// Remove timestamps older than 1 second
|
||||
this.requestTimes = this.requestTimes.filter((t) => now - t < 1000);
|
||||
|
||||
if (this.requestTimes.length >= this.requestsPerSecond) {
|
||||
const oldestRequest = this.requestTimes[0];
|
||||
const waitTime = 1000 - (now - oldestRequest);
|
||||
if (waitTime > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
||||
}
|
||||
}
|
||||
|
||||
this.requestTimes.push(Date.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the rate limiter (e.g., after a long pause)
|
||||
*/
|
||||
reset(): void {
|
||||
this.requestTimes = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an error is a network error (retryable)
|
||||
* @param error - Error to check
|
||||
* @returns true if error is network-related and retryable
|
||||
*/
|
||||
function isNetworkError(error: any): boolean {
|
||||
if (!error) return false;
|
||||
|
||||
// Check for fetch network errors
|
||||
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for ECONNREFUSED, ETIMEDOUT, etc.
|
||||
if (error.code && ["ECONNREFUSED", "ETIMEDOUT", "ECONNRESET", "ENOTFOUND"].includes(error.code)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an HTTP status code is retryable
|
||||
* @param status - HTTP status code
|
||||
* @returns true if status code indicates a temporary error
|
||||
*/
|
||||
function isRetryableStatus(status: number): boolean {
|
||||
// Retry on 5xx errors (server errors) and 429 (rate limit)
|
||||
return status >= 500 || status === 429;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry wrapper with exponential backoff
|
||||
* Retries failed operations with increasing delay between attempts
|
||||
*
|
||||
* @param fn - Function to execute
|
||||
* @param maxRetries - Maximum number of retry attempts
|
||||
* @param baseDelay - Base delay in milliseconds (doubles each retry)
|
||||
* @returns Promise with result of fn
|
||||
*/
|
||||
export async function withRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries: number = 3,
|
||||
baseDelay: number = 1000,
|
||||
): Promise<T> {
|
||||
let lastError: any;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error: any) {
|
||||
lastError = error;
|
||||
|
||||
// Check if error is retryable
|
||||
const isRetryable = isNetworkError(error) || isRetryableStatus(error?.status);
|
||||
|
||||
if (!isRetryable) {
|
||||
// Don't retry on permanent errors (4xx except 429)
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
// Calculate delay with exponential backoff
|
||||
const delay = baseDelay * Math.pow(2, attempt);
|
||||
console.log(
|
||||
`[withRetry] Attempt ${attempt + 1}/${maxRetries + 1} failed, retrying in ${delay}ms...`,
|
||||
error.message || error,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
const KC_URL = process.env.KC_URL;
|
||||
const KC_REALMS = process.env.KC_REALMS;
|
||||
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue