feat: add retry to external API requests

This commit is contained in:
gauthier-th
2024-07-08 17:55:24 +02:00
committed by Gauthier
parent 943d84e4d5
commit 98c5500967
6 changed files with 85 additions and 1 deletions

View File

@@ -1,4 +1,6 @@
import { getSettings } from '@server/lib/settings';
import rateLimit from '@server/utils/rateLimit';
import retry from '@server/utils/retry';
import type NodeCache from 'node-cache';
// 5 minute default TTL (in seconds)
@@ -37,6 +39,11 @@ class ExternalAPI {
this.fetch = fetch;
}
const settings = getSettings();
if (settings.main.retryCount) {
this.fetch = retry(this.fetch, settings.main.retryCount);
}
this.baseUrl = baseUrl;
this.params = params;
this.defaultHeaders = {

View File

@@ -56,7 +56,7 @@ app
}
// Load Settings
const settings = getSettings().load();
const settings = getSettings();
restartFlag.initializeSettings(settings.main);
// Migrate library types

View File

@@ -118,6 +118,7 @@ export interface MainSettings {
mediaServerType: number;
partialRequestsEnabled: boolean;
locale: string;
retryCount: number;
}
interface PublicSettings {
@@ -324,6 +325,7 @@ class Settings {
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,
locale: 'en',
retryCount: 0,
},
plex: {
name: '',
@@ -656,6 +658,7 @@ class Settings {
}
}
let loaded = false;
let settings: Settings | undefined;
export const getSettings = (initialSettings?: AllSettings): Settings => {
@@ -663,6 +666,11 @@ export const getSettings = (initialSettings?: AllSettings): Settings => {
settings = new Settings(initialSettings);
}
if (!loaded) {
settings.load();
loaded = true;
}
return settings;
};

View File

@@ -0,0 +1,13 @@
import type { AllSettings } from '@server/lib/settings';
const migrateRetryCount = (settings: any): AllSettings => {
return {
...settings,
main: {
...settings.main,
retryCount: settings.main.retryCount ?? 0,
},
};
};
export default migrateRetryCount;

23
server/utils/retry.ts Normal file
View File

@@ -0,0 +1,23 @@
export default function retry<
T extends (...args: Parameters<T>) => Promise<U>,
U
>(fn: T, retryCount: number): (...args: Parameters<T>) => Promise<U> {
const fnWithRetries = async (
retryCount: number,
...args: Parameters<T>
): Promise<U> => {
try {
return await fn(...args);
} catch (e) {
if (retryCount > 1) {
return fnWithRetries(retryCount - 1, ...args);
} else {
throw e;
}
}
};
return (...args: Parameters<T>): Promise<U> => {
return fnWithRetries(retryCount, ...args);
};
}

View File

@@ -47,6 +47,11 @@ const messages = defineMessages('components.Settings.SettingsMain', {
cacheImages: 'Enable Image Caching',
cacheImagesTip:
'Cache externally sourced images (requires a significant amount of disk space)',
retryCount: 'Retry Count',
retryCountTip:
'Number of retry when a network request to an external service fails',
retryCountHoverTip:
'Do NOT enable this setting unless you understand what you are doing!',
trustProxy: 'Enable Proxy Support',
trustProxyTip:
'Allow Jellyseerr to correctly register client IP addresses behind a proxy',
@@ -137,6 +142,7 @@ const SettingsMain = () => {
partialRequestsEnabled: data?.partialRequestsEnabled,
trustProxy: data?.trustProxy,
cacheImages: data?.cacheImages,
retryCount: data?.retryCount,
}}
enableReinitialize
validationSchema={MainSettingsSchema}
@@ -158,6 +164,7 @@ const SettingsMain = () => {
partialRequestsEnabled: values.partialRequestsEnabled,
trustProxy: values.trustProxy,
cacheImages: values.cacheImages,
retryCount: values.retryCount,
}),
});
if (!res.ok) throw new Error();
@@ -339,6 +346,32 @@ const SettingsMain = () => {
/>
</div>
</div>
<div className="form-row">
<label htmlFor="retryCount" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.retryCount)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<span className="label-tip">
{intl.formatMessage(messages.retryCountTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="retryCount"
name="retryCount"
type="text"
inputMode="numeric"
/>
</div>
{errors.retryCount &&
touched.retryCount &&
typeof errors.retryCount === 'string' && (
<div className="error">{errors.retryCount}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="locale" className="text-label">
{intl.formatMessage(messages.locale)}