feat: add retry to external API requests
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -56,7 +56,7 @@ app
|
||||
}
|
||||
|
||||
// Load Settings
|
||||
const settings = getSettings().load();
|
||||
const settings = getSettings();
|
||||
restartFlag.initializeSettings(settings.main);
|
||||
|
||||
// Migrate library types
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
13
server/lib/settings/migrations/0002_add_retry_count.ts
Normal file
13
server/lib/settings/migrations/0002_add_retry_count.ts
Normal 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
23
server/utils/retry.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user