mirror of
https://github.com/sct/overseerr.git
synced 2025-12-27 00:34:56 +01:00
* refactor: switch ExternalAPI to Fetch API * fix: add missing auth token in Plex request * fix: send proper URL params * ci: try to fix format checker * ci: ci: try to fix format checker * ci: try to fix format checker * refactor: make tautulli use the ExternalAPI class * refactor: add rate limit to fetch api * refactor: add rate limit to fetch api * refactor: switch server from axios to fetch api * refactor: switch frontend from axios to fetch api * fix: switch from URL objects to strings * fix: use the right search params for ExternalAPI * fix: better log for ExternalAPI errors * feat: add retry to external API requests * fix: try to fix network errors with IPv6 * fix: imageProxy rate limit * revert: remove retry to external API requests * feat: set IPv4 first as an option * fix(jellyfinapi): add missing argument in JellyfinAPI constructor * refactor: clean the rate limit utility
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
export type RateLimitOptions = {
|
|
maxRPS: number;
|
|
id?: string;
|
|
};
|
|
|
|
type RateLimiteState<T extends (...args: Parameters<T>) => Promise<U>, U> = {
|
|
queue: {
|
|
args: Parameters<T>;
|
|
resolve: (value: U) => void;
|
|
}[];
|
|
activeRequests: number;
|
|
timer: NodeJS.Timeout | null;
|
|
};
|
|
|
|
const rateLimitById: Record<string, unknown> = {};
|
|
|
|
/**
|
|
* Add a rate limit to a function so it doesn't exceed a maximum number of requests per second. Function calls exceeding the rate will be delayed.
|
|
* @param fn The function to rate limit
|
|
* @param options.maxRPS Maximum number of Requests Per Second
|
|
* @param options.id An ID to share between rate limits, so it uses the same request queue.
|
|
* @returns The function with a rate limit
|
|
*/
|
|
export default function rateLimit<
|
|
T extends (...args: Parameters<T>) => Promise<U>,
|
|
U
|
|
>(fn: T, options: RateLimitOptions): (...args: Parameters<T>) => Promise<U> {
|
|
const state: RateLimiteState<T, U> = (rateLimitById[
|
|
options.id || ''
|
|
] as RateLimiteState<T, U>) || { queue: [], activeRequests: 0, timer: null };
|
|
if (options.id) {
|
|
rateLimitById[options.id] = state;
|
|
}
|
|
|
|
const processQueue = () => {
|
|
if (state.queue.length === 0) {
|
|
if (state.timer) {
|
|
clearInterval(state.timer);
|
|
state.timer = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
while (state.activeRequests < options.maxRPS) {
|
|
state.activeRequests++;
|
|
const item = state.queue.shift();
|
|
if (!item) break;
|
|
const { args, resolve } = item;
|
|
fn(...args)
|
|
.then(resolve)
|
|
.finally(() => {
|
|
state.activeRequests--;
|
|
if (state.queue.length > 0) {
|
|
if (!state.timer) {
|
|
state.timer = setInterval(processQueue, 1000);
|
|
}
|
|
} else {
|
|
if (state.timer) {
|
|
clearInterval(state.timer);
|
|
state.timer = null;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
return (...args: Parameters<T>): Promise<U> => {
|
|
return new Promise<U>((resolve) => {
|
|
state.queue.push({ args, resolve });
|
|
processQueue();
|
|
});
|
|
};
|
|
}
|