mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
* fix: use fs/promises for settings This PR switches from synchronous operations with the 'fs' module to asynchronous operations with the 'fs/promises' module. It also corrects a small error with hostname migration. * fix: add missing merge function of default and current config * feat: add bypass list, bypass local addresses and username/password to proxy setting This PR adds more options to the proxy setting, like username/password authentication, bypass list of domains and bypass local addresses. The UX is taken from *arrs. * fix: add error handling for proxy creating * fix: remove logs
112 lines
2.8 KiB
TypeScript
112 lines
2.8 KiB
TypeScript
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();
|
|
|
|
const skipUrl = (url: string) => {
|
|
const hostname = new URL(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) => {
|
|
const url = opts.origin?.toString();
|
|
return url && skipUrl(url)
|
|
? 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,
|
|
interceptors: {
|
|
Client: [noProxyInterceptor],
|
|
},
|
|
});
|
|
|
|
setGlobalDispatcher(proxyAgent);
|
|
} 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') {
|
|
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;
|
|
}
|