fix: rewrite error handling for Axios and remove IPv4 first setting
This commit is contained in:
@@ -23,7 +23,6 @@
|
||||
"mediaServerType": 1,
|
||||
"partialRequestsEnabled": true,
|
||||
"enableSpecialEpisodes": false,
|
||||
"forceIpv4First": false,
|
||||
"locale": "en"
|
||||
},
|
||||
"plex": {
|
||||
@@ -179,4 +178,4 @@
|
||||
"schedule": "0 0 5 * * *"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,9 +191,6 @@ components:
|
||||
csrfProtection:
|
||||
type: boolean
|
||||
example: false
|
||||
forceIpv4First:
|
||||
type: boolean
|
||||
example: false
|
||||
trustProxy:
|
||||
type: boolean
|
||||
example: true
|
||||
|
||||
@@ -163,36 +163,36 @@ class JellyfinAPI extends ExternalAPI {
|
||||
} catch (e) {
|
||||
logger.debug('Failed to authenticate with headers', {
|
||||
label: 'Jellyfin API',
|
||||
error: e.cause.message ?? e.cause.statusText,
|
||||
error: e.response?.status,
|
||||
ip: ClientIP,
|
||||
});
|
||||
|
||||
if (!e.cause.status) {
|
||||
if (!e.response?.status) {
|
||||
throw new ApiError(404, ApiErrorCode.InvalidUrl);
|
||||
}
|
||||
|
||||
if (e.cause.status === 401) {
|
||||
throw new ApiError(e.cause.status, ApiErrorCode.InvalidCredentials);
|
||||
if (e.response?.status === 401) {
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidCredentials);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await authenticate(false);
|
||||
} catch (e) {
|
||||
if (e.cause.status === 401) {
|
||||
throw new ApiError(e.cause.status, ApiErrorCode.InvalidCredentials);
|
||||
if (e.response?.status === 401) {
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidCredentials);
|
||||
}
|
||||
|
||||
logger.error(
|
||||
'Something went wrong while authenticating with the Jellyfin server',
|
||||
`Something went wrong while authenticating with the Jellyfin server: ${e.message}`,
|
||||
{
|
||||
label: 'Jellyfin API',
|
||||
error: e.cause.message ?? e.cause.statusText,
|
||||
error: e.response?.status,
|
||||
ip: ClientIP,
|
||||
}
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause.status, ApiErrorCode.Unknown);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ class JellyfinAPI extends ExternalAPI {
|
||||
|
||||
return systemInfoResponse;
|
||||
} catch (e) {
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,11 +220,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return serverResponse.ServerName;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the server name from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting the server name from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.Unknown);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,11 +235,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return { users: userReponse };
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the account from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting the account from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,11 +251,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return userReponse;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the account from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting the account from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,10 +275,10 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return this.mapLibraries(mediaFolderResponse.Items);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting libraries from the Jellyfin server',
|
||||
`Something went wrong while getting libraries from the Jellyfin server: ${e.message}`,
|
||||
{
|
||||
label: 'Jellyfin API',
|
||||
error: e.cause.message ?? e.cause.statusText,
|
||||
error: e.response?.status,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -324,11 +324,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting library content from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e?.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,11 +349,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return itemResponse;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting library content from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,16 +371,16 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return itemResponse.Items?.[0];
|
||||
} catch (e) {
|
||||
if (availabilitySync.running) {
|
||||
if (e.cause?.status === 500) {
|
||||
if (e.response?.status === 500) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
logger.error(
|
||||
'Something went wrong while getting library content from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,11 +391,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
return seasonResponse.Items;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the list of seasons from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,11 +413,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while getting the list of episodes from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while getting the list of episodes from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.cause?.status, ApiErrorCode.InvalidAuthToken);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,8 +430,8 @@ class JellyfinAPI extends ExternalAPI {
|
||||
).AccessToken;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while creating an API key from the Jellyfin server',
|
||||
{ label: 'Jellyfin API', error: e.cause.message ?? e.cause.statusText }
|
||||
`Something went wrong while creating an API key from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
|
||||
@@ -214,20 +214,13 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
|
||||
}
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error(
|
||||
'Failed to add movie to Radarr. This might happen if the movie already exists, in which case you can safely ignore this error.',
|
||||
{
|
||||
label: 'Radarr',
|
||||
errorMessage: e.message,
|
||||
options,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
}
|
||||
);
|
||||
throw new Error('Failed to add movie to Radarr');
|
||||
|
||||
@@ -266,18 +266,11 @@ class SonarrAPI extends ServarrBase<{
|
||||
|
||||
return createdSeriesResponse.data;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Something went wrong while adding a series to Sonarr.', {
|
||||
label: 'Sonarr API',
|
||||
errorMessage: e.message,
|
||||
options,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
throw new Error('Failed to add series');
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import avatarproxy from '@server/routes/avatarproxy';
|
||||
import imageproxy from '@server/routes/imageproxy';
|
||||
import { appDataPermissions } from '@server/utils/appDataVolume';
|
||||
import { getAppVersion } from '@server/utils/appVersion';
|
||||
// import createCustomProxyAgent from '@server/utils/customProxyAgent';
|
||||
import restartFlag from '@server/utils/restartFlag';
|
||||
import { getClientIp } from '@supercharge/request-ip';
|
||||
import { TypeormStore } from 'connect-typeorm/out';
|
||||
@@ -72,20 +71,6 @@ app
|
||||
const settings = await getSettings().load();
|
||||
restartFlag.initializeSettings(settings);
|
||||
|
||||
// // Check if we force IPv4 first
|
||||
// if (
|
||||
// process.env.forceIpv4First === 'true' ||
|
||||
// settings.network.forceIpv4First
|
||||
// ) {
|
||||
// dns.setDefaultResultOrder('ipv4first');
|
||||
// net.setDefaultAutoSelectFamily(false);
|
||||
// }
|
||||
|
||||
// // Register HTTP proxy
|
||||
// if (settings.network.proxy.enabled) {
|
||||
// await createCustomProxyAgent(settings.network.proxy);
|
||||
// }
|
||||
|
||||
// Migrate library types
|
||||
if (
|
||||
settings.plex.libraries.length > 1 &&
|
||||
|
||||
@@ -309,19 +309,12 @@ class DiscordAgent
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending Discord notification', {
|
||||
label: 'Notifications',
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -144,19 +144,12 @@ class GotifyAgent
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending Gotify notification', {
|
||||
label: 'Notifications',
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -117,19 +117,12 @@ class LunaSeaAgent
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending LunaSea notification', {
|
||||
label: 'Notifications',
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -65,17 +65,10 @@ class PushoverAgent
|
||||
attachment_type: contentType,
|
||||
};
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error getting image payload', {
|
||||
label: 'Notifications',
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -245,19 +245,12 @@ class SlackAgent
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending Slack notification', {
|
||||
label: 'Notifications',
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -183,19 +183,12 @@ class TelegramAgent
|
||||
disable_notification: !!settings.options.sendSilently,
|
||||
} as TelegramMessagePayload | TelegramPhotoPayload);
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending Telegram notification', {
|
||||
label: 'Notifications',
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -226,20 +219,13 @@ class TelegramAgent
|
||||
!!payload.notifyUser.settings.telegramSendSilently,
|
||||
} as TelegramMessagePayload | TelegramPhotoPayload);
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending Telegram notification', {
|
||||
label: 'Notifications',
|
||||
recipient: payload.notifyUser.displayName,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -279,20 +265,13 @@ class TelegramAgent
|
||||
disable_notification: !!user.settings?.telegramSendSilently,
|
||||
} as TelegramMessagePayload | TelegramPhotoPayload);
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending Telegram notification', {
|
||||
label: 'Notifications',
|
||||
recipient: user.displayName,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -192,19 +192,12 @@ class WebhookAgent
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
logger.error('Error sending webhook notification', {
|
||||
label: 'Notifications',
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
response: errorData,
|
||||
response: e?.response?.data,
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -136,7 +136,6 @@ export interface MainSettings {
|
||||
|
||||
export interface NetworkSettings {
|
||||
csrfProtection: boolean;
|
||||
forceIpv4First: boolean;
|
||||
trustProxy: boolean;
|
||||
proxy: ProxySettings;
|
||||
}
|
||||
@@ -510,7 +509,6 @@ class Settings {
|
||||
network: {
|
||||
csrfProtection: false,
|
||||
trustProxy: false,
|
||||
forceIpv4First: false,
|
||||
proxy: {
|
||||
enabled: false,
|
||||
hostname: '',
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import type { ProxySettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import type { Dispatcher } from 'undici';
|
||||
import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici';
|
||||
|
||||
export default async function createCustomProxyAgent(
|
||||
proxySettings: ProxySettings
|
||||
) {
|
||||
const defaultAgent = new Agent({ keepAliveTimeout: 5000 });
|
||||
|
||||
const skipUrl = (url: string | URL) => {
|
||||
const hostname =
|
||||
typeof url === 'string' ? new URL(url).hostname : url.hostname;
|
||||
|
||||
if (proxySettings.bypassLocalAddresses && isLocalAddress(hostname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const address of proxySettings.bypassFilter.split(',')) {
|
||||
const trimmedAddress = address.trim();
|
||||
if (!trimmedAddress) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmedAddress.startsWith('*')) {
|
||||
const domain = trimmedAddress.slice(1);
|
||||
if (hostname.endsWith(domain)) {
|
||||
return true;
|
||||
}
|
||||
} else if (hostname === trimmedAddress) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const noProxyInterceptor = (
|
||||
dispatch: Dispatcher['dispatch']
|
||||
): Dispatcher['dispatch'] => {
|
||||
return (opts, handler) => {
|
||||
return opts.origin && skipUrl(opts.origin)
|
||||
? defaultAgent.dispatch(opts, handler)
|
||||
: dispatch(opts, handler);
|
||||
};
|
||||
};
|
||||
|
||||
const token =
|
||||
proxySettings.user && proxySettings.password
|
||||
? `Basic ${Buffer.from(
|
||||
`${proxySettings.user}:${proxySettings.password}`
|
||||
).toString('base64')}`
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const proxyAgent = new ProxyAgent({
|
||||
uri:
|
||||
(proxySettings.useSsl ? 'https://' : 'http://') +
|
||||
proxySettings.hostname +
|
||||
':' +
|
||||
proxySettings.port,
|
||||
token,
|
||||
keepAliveTimeout: 5000,
|
||||
});
|
||||
|
||||
setGlobalDispatcher(proxyAgent.compose(noProxyInterceptor));
|
||||
} catch (e) {
|
||||
logger.error('Failed to connect to the proxy: ' + e.message, {
|
||||
label: 'Proxy',
|
||||
});
|
||||
setGlobalDispatcher(defaultAgent);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('https://www.google.com', { method: 'HEAD' });
|
||||
if (res.ok) {
|
||||
logger.debug('HTTP(S) proxy connected successfully', { label: 'Proxy' });
|
||||
} else {
|
||||
logger.error('Proxy responded, but with a non-OK status: ' + res.status, {
|
||||
label: 'Proxy',
|
||||
});
|
||||
setGlobalDispatcher(defaultAgent);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Failed to connect to the proxy: ' + e.message + ': ' + e.cause,
|
||||
{ label: 'Proxy' }
|
||||
);
|
||||
setGlobalDispatcher(defaultAgent);
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalAddress(hostname: string) {
|
||||
if (
|
||||
hostname === 'localhost' ||
|
||||
hostname === '127.0.0.1' ||
|
||||
hostname === '::1'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const privateIpRanges = [
|
||||
/^10\./, // 10.x.x.x
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.x.x - 172.31.x.x
|
||||
/^192\.168\./, // 192.168.x.x
|
||||
];
|
||||
if (privateIpRanges.some((regex) => regex.test(hostname))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -17,8 +17,7 @@ class RestartFlag {
|
||||
return (
|
||||
this.networkSettings.csrfProtection !== networkSettings.csrfProtection ||
|
||||
this.networkSettings.trustProxy !== networkSettings.trustProxy ||
|
||||
this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled ||
|
||||
this.networkSettings.forceIpv4First !== networkSettings.forceIpv4First
|
||||
this.networkSettings.proxy.enabled !== networkSettings.proxy.enabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,9 +239,9 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
|
||||
setIsUpdating(true);
|
||||
|
||||
const res = await axios.delete(`/api/v1/blacklist/${tmdbId}`);
|
||||
try {
|
||||
await axios.delete(`/api/v1/blacklist/${tmdbId}`);
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
|
||||
@@ -251,7 +251,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
} catch {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
|
||||
@@ -39,9 +39,9 @@ const BlacklistBlock = ({
|
||||
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
|
||||
setIsUpdating(true);
|
||||
|
||||
const res = await axios.delete('/api/v1/blacklist/' + tmdbId);
|
||||
try {
|
||||
await axios.delete('/api/v1/blacklist/' + tmdbId);
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
|
||||
@@ -51,7 +51,7 @@ const BlacklistBlock = ({
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
} catch {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
|
||||
@@ -327,9 +327,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
title: movie?.title,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data) {
|
||||
if (response.data) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(messages.watchlistSuccess, {
|
||||
@@ -354,19 +352,17 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
const res = await axios.delete(`/api/v1/watchlist/${movie?.id}`);
|
||||
await axios.delete(`/api/v1/watchlist/${movie?.id}`);
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(messages.watchlistDeleted, {
|
||||
title: movie?.title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
}
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(messages.watchlistDeleted, {
|
||||
title: movie?.title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} catch (e) {
|
||||
addToast(intl.formatMessage(messages.watchlistError), {
|
||||
appearance: 'error',
|
||||
@@ -381,14 +377,14 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
const onClickHideItemBtn = async (): Promise<void> => {
|
||||
setIsBlacklistUpdating(true);
|
||||
|
||||
const res = await axios.post('/api/v1/blacklist', {
|
||||
tmdbId: movie?.id,
|
||||
mediaType: 'movie',
|
||||
title: movie?.title,
|
||||
user: user?.id,
|
||||
});
|
||||
try {
|
||||
await axios.post('/api/v1/blacklist', {
|
||||
tmdbId: movie?.id,
|
||||
mediaType: 'movie',
|
||||
title: movie?.title,
|
||||
user: user?.id,
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
@@ -400,21 +396,23 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
);
|
||||
|
||||
revalidate();
|
||||
} else if (res.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title: movie?.title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e?.response?.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title: movie?.title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setIsBlacklistUpdating(false);
|
||||
|
||||
@@ -65,7 +65,7 @@ const OverrideRuleTiles = ({
|
||||
for (const service of services) {
|
||||
const { hostname, port, apiKey, baseUrl, useSsl = false } = service;
|
||||
try {
|
||||
const response = await axios.post(
|
||||
const response = await axios.post<DVRTestResponse>(
|
||||
`/api/v1/settings/${
|
||||
radarrServices.includes(service as RadarrSettings)
|
||||
? 'radarr'
|
||||
|
||||
@@ -185,14 +185,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
if (errorData?.message === 'SYNC_ERROR_GROUPED_FOLDERS') {
|
||||
if (e?.response?.data?.message === 'SYNC_ERROR_GROUPED_FOLDERS') {
|
||||
toasts.addToast(
|
||||
intl.formatMessage(
|
||||
messages.jellyfinSyncFailedAutomaticGroupedFolders
|
||||
@@ -202,7 +195,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
appearance: 'warning',
|
||||
}
|
||||
);
|
||||
} else if (errorData?.message === 'SYNC_ERROR_NO_LIBRARIES') {
|
||||
} else if (e?.response?.data?.message === 'SYNC_ERROR_NO_LIBRARIES') {
|
||||
toasts.addToast(
|
||||
intl.formatMessage(messages.jellyfinSyncFailedNoLibrariesFound),
|
||||
{
|
||||
@@ -488,14 +481,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
if (errorData?.message === ApiErrorCode.InvalidUrl) {
|
||||
if (e?.response?.data?.message === ApiErrorCode.InvalidUrl) {
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
messages.invalidurlerror,
|
||||
|
||||
@@ -43,9 +43,6 @@ const messages = defineMessages('components.Settings.SettingsNetwork', {
|
||||
networkDisclaimer:
|
||||
'Network parameters from your container/system should be used instead of these settings. See the {docs} for more information.',
|
||||
docs: 'documentation',
|
||||
forceIpv4First: 'Force IPv4 Resolution First',
|
||||
forceIpv4FirstTip:
|
||||
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
|
||||
});
|
||||
|
||||
const SettingsNetwork = () => {
|
||||
@@ -90,7 +87,6 @@ const SettingsNetwork = () => {
|
||||
<Formik
|
||||
initialValues={{
|
||||
csrfProtection: data?.csrfProtection,
|
||||
forceIpv4First: data?.forceIpv4First,
|
||||
trustProxy: data?.trustProxy,
|
||||
proxyEnabled: data?.proxy?.enabled,
|
||||
proxyHostname: data?.proxy?.hostname,
|
||||
@@ -107,7 +103,6 @@ const SettingsNetwork = () => {
|
||||
try {
|
||||
await axios.post('/api/v1/settings/network', {
|
||||
csrfProtection: values.csrfProtection,
|
||||
forceIpv4First: values.forceIpv4First,
|
||||
trustProxy: values.trustProxy,
|
||||
proxy: {
|
||||
enabled: values.proxyEnabled,
|
||||
@@ -392,29 +387,6 @@ const SettingsNetwork = () => {
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
<div className="form-row">
|
||||
<label htmlFor="forceIpv4First" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.forceIpv4First)}
|
||||
</span>
|
||||
<SettingsBadge badgeType="advanced" className="mr-2" />
|
||||
<SettingsBadge badgeType="restartRequired" />
|
||||
<SettingsBadge badgeType="experimental" />
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.forceIpv4FirstTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="forceIpv4First"
|
||||
name="forceIpv4First"
|
||||
onChange={() => {
|
||||
setFieldValue('forceIpv4First', !values.forceIpv4First);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
|
||||
@@ -173,14 +173,13 @@ const TitleCard = ({
|
||||
const topNode = cardRef.current;
|
||||
|
||||
if (topNode) {
|
||||
const res = await axios.post('/api/v1/blacklist', {
|
||||
tmdbId: id,
|
||||
mediaType,
|
||||
title,
|
||||
user: user?.id,
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
try {
|
||||
await axios.post('/api/v1/blacklist', {
|
||||
tmdbId: id,
|
||||
mediaType,
|
||||
title,
|
||||
user: user?.id,
|
||||
});
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
@@ -191,21 +190,23 @@ const TitleCard = ({
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.BLACKLISTED);
|
||||
} else if (res.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e?.response?.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setIsUpdating(false);
|
||||
|
||||
@@ -411,40 +411,44 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
const onClickHideItemBtn = async (): Promise<void> => {
|
||||
setIsBlacklistUpdating(true);
|
||||
|
||||
const res = await axios.post('/api/v1/blacklist', {
|
||||
tmdbId: tv?.id,
|
||||
mediaType: 'tv',
|
||||
title: tv?.name,
|
||||
user: user?.id,
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
title: tv?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
|
||||
revalidate();
|
||||
} else if (res.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title: tv?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
try {
|
||||
const res = await axios.post('/api/v1/blacklist', {
|
||||
tmdbId: tv?.id,
|
||||
mediaType: 'tv',
|
||||
title: tv?.name,
|
||||
user: user?.id,
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
title: tv?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
|
||||
revalidate();
|
||||
}
|
||||
} catch (e) {
|
||||
if (e?.response?.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title: tv?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setIsBlacklistUpdating(false);
|
||||
|
||||
@@ -295,16 +295,9 @@ const UserList = () => {
|
||||
});
|
||||
setCreateModal({ isOpen: false });
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
errorData.errors?.includes('USER_EXISTS')
|
||||
e?.response?.data?.errors?.includes('USER_EXISTS')
|
||||
? messages.usercreatedfailedexisting
|
||||
: messages.usercreatedfailed
|
||||
),
|
||||
|
||||
@@ -197,14 +197,7 @@ const UserGeneralSettings = () => {
|
||||
appearance: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
if (errorData?.message === ApiErrorCode.InvalidEmail) {
|
||||
if (e?.response?.data?.message === ApiErrorCode.InvalidEmail) {
|
||||
if (values.email) {
|
||||
addToast(
|
||||
intl.formatMessage(messages.toastSettingsFailureEmail),
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '@heroicons/react/24/solid';
|
||||
import type { UserPushSubscription } from '@server/entity/UserPushSubscription';
|
||||
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
|
||||
import axios from 'axios';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -84,21 +85,12 @@ const UserWebPushSettings = () => {
|
||||
const parsedSub = JSON.parse(JSON.stringify(sub));
|
||||
|
||||
if (parsedSub.keys.p256dh && parsedSub.keys.auth) {
|
||||
const res = await fetch('/api/v1/user/registerPushSubscription', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
endpoint: parsedSub.endpoint,
|
||||
p256dh: parsedSub.keys.p256dh,
|
||||
auth: parsedSub.keys.auth,
|
||||
userAgent: navigator.userAgent,
|
||||
}),
|
||||
await axios.post('/api/v1/user/registerPushSubscription', {
|
||||
endpoint: parsedSub.endpoint,
|
||||
p256dh: parsedSub.keys.p256dh,
|
||||
auth: parsedSub.keys.auth,
|
||||
userAgent: navigator.userAgent,
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
setWebPushEnabled(true);
|
||||
addToast(intl.formatMessage(messages.webpushhasbeenenabled), {
|
||||
appearance: 'success',
|
||||
@@ -129,17 +121,11 @@ const UserWebPushSettings = () => {
|
||||
.then(async (subscription) => {
|
||||
const parsedSub = JSON.parse(JSON.stringify(subscription));
|
||||
|
||||
const res = await fetch(
|
||||
await axios.delete(
|
||||
`/api/v1/user/${user?.id}/pushSubscription/${
|
||||
p256dh ? p256dh : parsedSub.keys.p256dh
|
||||
}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
}
|
||||
}`
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
if (subscription && (p256dh === parsedSub.keys.p256dh || !p256dh)) {
|
||||
subscription.unsubscribe();
|
||||
setWebPushEnabled(false);
|
||||
@@ -188,17 +174,10 @@ const UserWebPushSettings = () => {
|
||||
.then(async (subscription) => {
|
||||
if (subscription) {
|
||||
const parsedKey = JSON.parse(JSON.stringify(subscription));
|
||||
const response = await fetch(
|
||||
`/api/v1/user/${user.id}/pushSubscription/${parsedKey.keys.p256dh}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
const currentUserPushSub = {
|
||||
data: (await response.json()) as UserPushSubscription,
|
||||
};
|
||||
const currentUserPushSub =
|
||||
await axios.get<UserPushSubscription>(
|
||||
`/api/v1/user/${user.id}/pushSubscription/${parsedKey.keys.p256dh}`
|
||||
);
|
||||
|
||||
if (currentUserPushSub.data.p256dh !== parsedKey.keys.p256dh) {
|
||||
return;
|
||||
@@ -233,30 +212,21 @@ const UserWebPushSettings = () => {
|
||||
enableReinitialize
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
await axios.post(
|
||||
`/api/v1/user/${user?.id}/settings/notifications`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
pgpKey: data?.pgpKey,
|
||||
discordId: data?.discordId,
|
||||
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||
pushoverUserKey: data?.pushoverUserKey,
|
||||
telegramChatId: data?.telegramChatId,
|
||||
telegramSendSilently: data?.telegramSendSilently,
|
||||
notificationTypes: {
|
||||
webpush: values.types,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pgpKey: data?.pgpKey,
|
||||
discordId: data?.discordId,
|
||||
pushbulletAccessToken: data?.pushbulletAccessToken,
|
||||
pushoverApplicationToken: data?.pushoverApplicationToken,
|
||||
pushoverUserKey: data?.pushoverUserKey,
|
||||
telegramChatId: data?.telegramChatId,
|
||||
telegramSendSilently: data?.telegramSendSilently,
|
||||
notificationTypes: {
|
||||
webpush: values.types,
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
mutate('/api/v1/settings/public');
|
||||
addToast(intl.formatMessage(messages.webpushsettingssaved), {
|
||||
appearance: 'success',
|
||||
|
||||
@@ -943,8 +943,6 @@
|
||||
"components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
|
||||
"components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
|
||||
"components.Settings.SettingsNetwork.docs": "documentation",
|
||||
"components.Settings.SettingsNetwork.forceIpv4First": "Force IPv4 Resolution First",
|
||||
"components.Settings.SettingsNetwork.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6",
|
||||
"components.Settings.SettingsNetwork.network": "Network",
|
||||
"components.Settings.SettingsNetwork.networkDisclaimer": "Network parameters from your container/system should be used instead of these settings. See the {docs} for more information.",
|
||||
"components.Settings.SettingsNetwork.networksettings": "Network Settings",
|
||||
|
||||
@@ -13,7 +13,6 @@ import { UserContext } from '@app/context/UserContext';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import '@app/styles/globals.css';
|
||||
import '@app/utils/fetchOverride';
|
||||
import { polyfillIntl } from '@app/utils/polyfillIntl';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces';
|
||||
@@ -239,7 +238,9 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
if (ctx.res) {
|
||||
// Check if app is initialized and redirect if necessary
|
||||
const response = await axios.get<PublicSettingsResponse>(
|
||||
`http://${process.env.HOST || 'localhost'}:${process.env.PORT || 5055}/api/v1/settings/public`
|
||||
`http://${process.env.HOST || 'localhost'}:${
|
||||
process.env.PORT || 5055
|
||||
}/api/v1/settings/public`
|
||||
);
|
||||
|
||||
currentSettings = response.data;
|
||||
@@ -257,7 +258,9 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
try {
|
||||
// Attempt to get the user by running a request to the local api
|
||||
const response = await axios.get<User>(
|
||||
`http://${process.env.HOST || 'localhost'}:${process.env.PORT || 5055}/api/v1/auth/me`,
|
||||
`http://${process.env.HOST || 'localhost'}:${
|
||||
process.env.PORT || 5055
|
||||
}/api/v1/auth/me`,
|
||||
{
|
||||
headers:
|
||||
ctx.req && ctx.req.headers.cookie
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
const getCsrfToken = (): string | null => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/);
|
||||
return match ? decodeURIComponent(match[1]) : null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const isSameOrigin = (url: RequestInfo | URL): boolean => {
|
||||
const parsedUrl = new URL(
|
||||
url instanceof Request ? url.url : url.toString(),
|
||||
window.location.origin
|
||||
);
|
||||
return parsedUrl.origin === window.location.origin;
|
||||
};
|
||||
|
||||
// We are using a custom fetch implementation to add the X-XSRF-TOKEN heade
|
||||
// to all requests. This is required when CSRF protection is enabled.
|
||||
if (typeof window !== 'undefined') {
|
||||
const originalFetch: typeof fetch = window.fetch;
|
||||
|
||||
(window as typeof globalThis).fetch = async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit
|
||||
): Promise<Response> => {
|
||||
if (!isSameOrigin(input)) {
|
||||
return originalFetch(input, init);
|
||||
}
|
||||
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const headers = {
|
||||
...(init?.headers || {}),
|
||||
...(csrfToken ? { 'XSRF-TOKEN': csrfToken } : {}),
|
||||
};
|
||||
|
||||
const newInit: RequestInit = {
|
||||
...init,
|
||||
headers,
|
||||
};
|
||||
|
||||
return originalFetch(input, newInit);
|
||||
};
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user