fix: option to toggle on/off system proxy env variables (#2724)

fix: option to toggle on/off system proxy env variables
This commit is contained in:
lohit 2024-08-30 11:44:29 +05:30 committed by GitHub
parent 93080de2a8
commit c1ec95dc29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 547 additions and 388 deletions

View File

@ -6,14 +6,28 @@ import * as Yup from 'yup';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { IconEye, IconEyeOff } from '@tabler/icons'; import { IconEye, IconEyeOff } from '@tabler/icons';
import { useState } from 'react'; import { useState } from 'react';
import { useMemo } from 'react';
import { cloneDeep } from 'lodash';
const ProxySettings = ({ proxyConfig: _proxyConfig, onUpdate }) => {
const proxyConfig = useMemo(() => {
const proxyConfigCopy = cloneDeep(_proxyConfig);
// backward compatibility check
if (proxyConfigCopy?.enabled == 'true') proxyConfigCopy.enabled = true;
if (proxyConfigCopy?.enabled == 'false') proxyConfigCopy.enabled = false;
proxyConfigCopy.enabled = ['string', 'boolean'].includes(typeof proxyConfigCopy?.enabled)
? proxyConfigCopy?.enabled
: 'global';
return proxyConfigCopy;
}, [_proxyConfig]);
const ProxySettings = ({ proxyConfig, onUpdate }) => {
const proxySchema = Yup.object({ const proxySchema = Yup.object({
enabled: Yup.string().oneOf(['global', 'true', 'false']), enabled: Yup.mixed().oneOf([false, true, 'global']),
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string() hostname: Yup.string()
.when('enabled', { .when('enabled', {
is: 'true', is: true,
then: (hostname) => hostname.required('Specify the hostname for your proxy.'), then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable() otherwise: (hostname) => hostname.nullable()
}) })
@ -26,7 +40,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
.transform((_, val) => (val ? Number(val) : null)), .transform((_, val) => (val ? Number(val) : null)),
auth: Yup.object() auth: Yup.object()
.when('enabled', { .when('enabled', {
is: 'true', is: true,
then: Yup.object({ then: Yup.object({
enabled: Yup.boolean(), enabled: Yup.boolean(),
username: Yup.string() username: Yup.string()
@ -49,7 +63,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
enabled: proxyConfig.enabled || 'global', enabled: proxyConfig.enabled,
protocol: proxyConfig.protocol || 'http', protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '', hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '', port: proxyConfig.port || '',
@ -65,13 +79,6 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
proxySchema proxySchema
.validate(values, { abortEarly: true }) .validate(values, { abortEarly: true })
.then((validatedProxy) => { .then((validatedProxy) => {
// serialize 'enabled' to boolean
if (validatedProxy.enabled === 'true') {
validatedProxy.enabled = true;
} else if (validatedProxy.enabled === 'false') {
validatedProxy.enabled = false;
}
onUpdate(validatedProxy); onUpdate(validatedProxy);
}) })
.catch((error) => { .catch((error) => {
@ -84,7 +91,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
useEffect(() => { useEffect(() => {
formik.setValues({ formik.setValues({
enabled: proxyConfig.enabled === true ? 'true' : proxyConfig.enabled === false ? 'false' : 'global', enabled: proxyConfig.enabled,
protocol: proxyConfig.protocol || 'http', protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '', hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '', port: proxyConfig.port || '',
@ -118,214 +125,229 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
/> />
</label> </label>
<div className="flex items-center"> <div className="flex items-center">
<label className="flex items-center"> <label className="flex items-center cursor-pointer">
<input <input
type="radio" type="radio"
name="enabled" name="enabled"
value="global" value="global"
checked={formik.values.enabled === 'global'} checked={formik.values.enabled === 'global'}
onChange={formik.handleChange} onChange={formik.handleChange}
className="mr-1" className="mr-1 cursor-pointer"
/> />
global global
</label> </label>
<label className="flex items-center ml-4"> <label className="flex items-center ml-4 cursor-pointer">
<input <input
type="radio" type="radio"
name="enabled" name="enabled"
value={'true'} value={'true'}
checked={formik.values.enabled === 'true'} checked={formik.values.enabled === true}
onChange={formik.handleChange} onChange={(e) => {
className="mr-1" formik.setFieldValue('enabled', true);
}}
className="mr-1 cursor-pointer"
/> />
enabled enabled
</label> </label>
<label className="flex items-center ml-4"> <label className="flex items-center ml-4 cursor-pointer">
<input <input
type="radio" type="radio"
name="enabled" name="enabled"
value={'false'} value={'false'}
checked={formik.values.enabled === 'false'} checked={formik.values.enabled === false}
onChange={formik.handleChange} onChange={(e) => {
className="mr-1" formik.setFieldValue('enabled', false);
}}
className="mr-1 cursor-pointer"
/> />
disabled disabled
</label> </label>
</div> </div>
</div> </div>
<div className="mb-3 flex items-center"> {formik.values.enabled === 'global' ? (
<label className="settings-label" htmlFor="protocol"> <div className="opacity-50">Global proxy can be configured in the app preferences.</div>
Protocol ) : null}
</label> {formik.values.enabled === true ? (
<div className="flex items-center"> <>
<label className="flex items-center"> <div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input <input
type="radio" id="hostname"
name="protocol" type="text"
value="http" name="hostname"
checked={formik.values.protocol === 'http'} className="block textbox"
onChange={formik.handleChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[1.70rem] relative">
<input
id="auth.password"
type={passwordVisible ? 'text' : 'password'}
name="auth.password"
className="outline-none bg-transparent w-[10.5rem]"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={formik.values.auth.password} onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange} onChange={formik.handleChange}
/> />
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={1.5} /> : <IconEye size={18} strokeWidth={1.5} />}
</button>
</div> </div>
{formik.touched.auth?.password && formik.errors.auth?.password ? ( <div>
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div> <div className="mb-3 flex items-center">
) : null} <label className="settings-label" htmlFor="auth.username">
</div> Username
</div> </label>
<div className="mb-3 flex items-center"> <input
<label className="settings-label" htmlFor="bypassProxy"> id="auth.username"
Proxy Bypass type="text"
</label> name="auth.username"
<input className="block textbox"
id="bypassProxy" autoComplete="off"
type="text" autoCorrect="off"
name="bypassProxy" autoCapitalize="off"
className="block textbox" spellCheck="false"
autoComplete="off" value={formik.values.auth.username}
autoCorrect="off" onChange={formik.handleChange}
autoCapitalize="off" />
spellCheck="false" {formik.touched.auth?.username && formik.errors.auth?.username ? (
onChange={formik.handleChange} <div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
value={formik.values.bypassProxy || ''} ) : null}
/> </div>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? ( <div className="mb-3 flex items-center">
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div> <label className="settings-label" htmlFor="auth.password">
) : null} Password
</div> </label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[1.70rem] relative">
<input
id="auth.password"
type={passwordVisible ? 'text' : 'password'}
name="auth.password"
className="outline-none bg-transparent w-[10.5rem]"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.password}
onChange={formik.handleChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? (
<IconEyeOff size={18} strokeWidth={1.5} />
) : (
<IconEye size={18} strokeWidth={1.5} />
)}
</button>
</div>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</div>
</>
) : null}
<div className="mt-6"> <div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary"> <button type="submit" className="submit btn btn-sm btn-secondary">
Save Save

View File

@ -2,7 +2,7 @@ import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
.settings-label { .settings-label {
width: 80px; width: 100px;
} }
.textbox { .textbox {

View File

@ -8,17 +8,33 @@ import StyledWrapper from './StyledWrapper';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { IconEye, IconEyeOff } from '@tabler/icons'; import { IconEye, IconEyeOff } from '@tabler/icons';
import { useState } from 'react'; import { useState } from 'react';
import { useMemo } from 'react';
import { cloneDeep } from 'lodash';
const ProxySettings = ({ close }) => { const ProxySettings = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences); const _preferences = useSelector((state) => state.app.preferences);
const systemProxyEnvVariables = useSelector((state) => state.app.systemProxyEnvVariables);
const { http_proxy, https_proxy, no_proxy } = systemProxyEnvVariables || {};
const dispatch = useDispatch(); const dispatch = useDispatch();
const preferences = useMemo(() => {
const preferencesCopy = cloneDeep(_preferences);
// backward compatibility check
if (typeof preferencesCopy?.proxy?.enabled === 'boolean') {
preferencesCopy.proxy.mode = preferencesCopy?.proxy?.enabled ? 'on' : 'off';
} else {
preferencesCopy.proxy.mode =
typeof preferencesCopy?.proxy?.mode === 'string' ? preferencesCopy?.proxy?.mode : 'off';
}
return preferencesCopy;
}, [_preferences]);
const proxySchema = Yup.object({ const proxySchema = Yup.object({
enabled: Yup.boolean(), mode: Yup.string().oneOf(['off', 'on', 'system']),
protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']), protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string() hostname: Yup.string()
.when('enabled', { .when('enabled', {
is: true, is: 'on',
then: (hostname) => hostname.required('Specify the hostname for your proxy.'), then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable() otherwise: (hostname) => hostname.nullable()
}) })
@ -31,7 +47,7 @@ const ProxySettings = ({ close }) => {
.transform((_, val) => (val ? Number(val) : null)), .transform((_, val) => (val ? Number(val) : null)),
auth: Yup.object() auth: Yup.object()
.when('enabled', { .when('enabled', {
is: true, is: 'on',
then: Yup.object({ then: Yup.object({
enabled: Yup.boolean(), enabled: Yup.boolean(),
username: Yup.string() username: Yup.string()
@ -54,7 +70,7 @@ const ProxySettings = ({ close }) => {
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
enabled: preferences.proxy.enabled || false, mode: preferences.proxy.mode,
protocol: preferences.proxy.protocol || 'http', protocol: preferences.proxy.protocol || 'http',
hostname: preferences.proxy.hostname || '', hostname: preferences.proxy.hostname || '',
port: preferences.proxy.port || 0, port: preferences.proxy.port || 0,
@ -94,7 +110,7 @@ const ProxySettings = ({ close }) => {
useEffect(() => { useEffect(() => {
formik.setValues({ formik.setValues({
enabled: preferences.proxy.enabled || false, mode: preferences.proxy.mode,
protocol: preferences.proxy.protocol || 'http', protocol: preferences.proxy.protocol || 'http',
hostname: preferences.proxy.hostname || '', hostname: preferences.proxy.hostname || '',
port: preferences.proxy.port || '', port: preferences.proxy.port || '',
@ -109,188 +125,252 @@ const ProxySettings = ({ close }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<h1 className="font-medium mb-3">Global Proxy Settings</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="enabled">
Enabled
</label>
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
</div>
<div className="mb-3 flex items-center"> <div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol"> <label className="settings-label" htmlFor="protocol">
Protocol Mode
</label> </label>
<div className="flex items-center"> <div className="flex items-center">
<label className="flex items-center"> <label className="flex items-center cursor-pointer">
<input <input
type="radio" type="radio"
name="protocol" name="mode"
value="http" value="false"
checked={formik.values.protocol === 'http'} checked={formik.values.mode === 'off'}
onChange={formik.handleChange} onChange={(e) => {
className="mr-1" formik.setFieldValue('mode', 'off');
}}
className="mr-1 cursor-pointer"
/> />
HTTP off
</label> </label>
<label className="flex items-center ml-4"> <label className="flex items-center ml-4 cursor-pointer">
<input <input
type="radio" type="radio"
name="protocol" name="mode"
value="https" value="true"
checked={formik.values.protocol === 'https'} checked={formik.values.mode === 'on'}
onChange={formik.handleChange} onChange={(e) => {
className="mr-1" formik.setFieldValue('mode', 'on');
}}
className="mr-1 cursor-pointer"
/> />
HTTPS on
</label> </label>
<label className="flex items-center ml-4"> <label className="flex items-center ml-4 cursor-pointer">
<input <input
type="radio" type="radio"
name="protocol" name="mode"
value="socks4" value="system"
checked={formik.values.protocol === 'socks4'} checked={formik.values.mode === 'system'}
onChange={formik.handleChange} onChange={formik.handleChange}
className="mr-1" className="mr-1 cursor-pointer"
/> />
SOCKS4 system
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS5
</label> </label>
</div> </div>
</div> </div>
{formik?.values?.mode === 'system' ? (
<div className="mb-3 flex items-center"> <div className="mb-3 flex items-start pb-3">
<label className="settings-label" htmlFor="hostname"> <div className="flex flex-col gap-2 justify-start items-start">
Hostname <div className="mb-3 flex items-center">
</label> <label className="settings-label" htmlFor="http_proxy">
<input http_proxy
id="hostname" </label>
type="text" <div className="opacity-80">{http_proxy || '-'}</div>
name="hostname" </div>
className="block textbox" <div className="mb-3 flex items-center">
autoComplete="off" <label className="settings-label" htmlFor="https_proxy">
autoCorrect="off" https_proxy
autoCapitalize="off" </label>
spellCheck="false" <div className="opacity-80">{https_proxy || '-'}</div>
onChange={formik.handleChange} </div>
value={formik.values.hostname || ''} <div className="mb-3 flex items-center">
/> <label className="settings-label" htmlFor="no_proxy">
{formik.touched.hostname && formik.errors.hostname ? ( no_proxy
<div className="ml-3 text-red-500">{formik.errors.hostname}</div> </label>
) : null} <div className="opacity-80">{no_proxy || '-'}</div>
</div> </div>
<div className="mb-3 flex items-center"> </div>
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</div> </div>
<div className="mb-3 flex items-center"> ) : null}
<label className="settings-label" htmlFor="auth.password"> {formik?.values?.mode === 'on' ? (
Password <>
</label> <div className="mb-3 flex items-center">
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative"> <label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input <input
id="auth.password" id="hostname"
type={passwordVisible ? `text` : 'password'} type="text"
name="auth.password" name="hostname"
className="outline-none w-[10.5rem] bg-transparent" className="block textbox"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={formik.values.auth.password} onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange} onChange={formik.handleChange}
/> />
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
</button>
</div> </div>
{formik.touched.auth?.password && formik.errors.auth?.password ? ( <div>
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div> <div className="mb-3 flex items-center">
) : null} <label className="settings-label" htmlFor="auth.username">
</div> Username
</div> </label>
<div className="mb-3 flex items-center"> <input
<label className="settings-label" htmlFor="bypassProxy"> id="auth.username"
Proxy Bypass type="text"
</label> name="auth.username"
<input className="block textbox"
id="bypassProxy" autoComplete="off"
type="text" autoCorrect="off"
name="bypassProxy" autoCapitalize="off"
className="block textbox" spellCheck="false"
autoComplete="off" value={formik.values.auth.username}
autoCorrect="off" onChange={formik.handleChange}
autoCapitalize="off" />
spellCheck="false" {formik.touched.auth?.username && formik.errors.auth?.username ? (
onChange={formik.handleChange} <div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
value={formik.values.bypassProxy || ''} ) : null}
/> </div>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? ( <div className="mb-3 flex items-center">
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div> <label className="settings-label" htmlFor="auth.password">
) : null} Password
</div> </label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
<input
id="auth.password"
type={passwordVisible ? `text` : 'password'}
name="auth.password"
className="outline-none w-[10.5rem] bg-transparent"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.password}
onChange={formik.handleChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
</button>
</div>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</div>
</>
) : null}
<div className="mt-6"> <div className="mt-6">
<button type="submit" className="submit btn btn-md btn-secondary"> <button type="submit" className="submit btn btn-md btn-secondary">
Save Save

View File

@ -1,5 +1,10 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { showPreferences, updateCookies, updatePreferences } from 'providers/ReduxStore/slices/app'; import {
showPreferences,
updateCookies,
updatePreferences,
updateSystemProxyEnvVariables
} from 'providers/ReduxStore/slices/app';
import { import {
brunoConfigUpdateEvent, brunoConfigUpdateEvent,
collectionAddDirectoryEvent, collectionAddDirectoryEvent,
@ -136,6 +141,10 @@ const useIpcEvents = () => {
dispatch(updatePreferences(val)); dispatch(updatePreferences(val));
}); });
const removeSystemProxyEnvUpdatesListener = ipcRenderer.on('main:load-system-proxy-env', (val) => {
dispatch(updateSystemProxyEnvVariables(val));
});
const removeCookieUpdateListener = ipcRenderer.on('main:cookies-update', (val) => { const removeCookieUpdateListener = ipcRenderer.on('main:cookies-update', (val) => {
dispatch(updateCookies(val)); dispatch(updateCookies(val));
}); });
@ -155,6 +164,7 @@ const useIpcEvents = () => {
removeShowPreferencesListener(); removeShowPreferencesListener();
removePreferencesUpdatesListener(); removePreferencesUpdatesListener();
removeCookieUpdateListener(); removeCookieUpdateListener();
removeSystemProxyEnvUpdatesListener();
}; };
}, [isElectron]); }, [isElectron]);
}; };

View File

@ -27,7 +27,8 @@ const initialState = {
} }
}, },
cookies: [], cookies: [],
taskQueue: [] taskQueue: [],
systemProxyEnvVariables: {}
}; };
export const appSlice = createSlice({ export const appSlice = createSlice({
@ -72,6 +73,9 @@ export const appSlice = createSlice({
}, },
removeAllTasksFromQueue: (state) => { removeAllTasksFromQueue: (state) => {
state.taskQueue = []; state.taskQueue = [];
},
updateSystemProxyEnvVariables: (state, action) => {
state.systemProxyEnvVariables = action.payload;
} }
} }
}); });
@ -89,7 +93,8 @@ export const {
updateCookies, updateCookies,
insertTaskIntoQueue, insertTaskIntoQueue,
removeTaskFromQueue, removeTaskFromQueue,
removeAllTasksFromQueue removeAllTasksFromQueue,
updateSystemProxyEnvVariables
} = appSlice.actions; } = appSlice.actions;
export const savePreferences = (preferences) => (dispatch, getState) => { export const savePreferences = (preferences) => (dispatch, getState) => {

View File

@ -8,7 +8,9 @@ const axios = require('axios');
*/ */
function makeAxiosInstance() { function makeAxiosInstance() {
/** @type {axios.AxiosInstance} */ /** @type {axios.AxiosInstance} */
const instance = axios.create(); const instance = axios.create({
proxy: false
});
instance.interceptors.request.use((config) => { instance.interceptors.request.use((config) => {
config.headers['request-start-time'] = Date.now(); config.headers['request-start-time'] = Date.now();

View File

@ -49,7 +49,9 @@ const checkConnection = (host, port) =>
*/ */
function makeAxiosInstance() { function makeAxiosInstance() {
/** @type {axios.AxiosInstance} */ /** @type {axios.AxiosInstance} */
const instance = axios.create(); const instance = axios.create({
proxy: false
});
instance.interceptors.request.use(async (config) => { instance.interceptors.request.use(async (config) => {
const url = URL.parse(config.url); const url = URL.parse(config.url);

View File

@ -167,49 +167,75 @@ const configureRequest = async (
// proxy configuration // proxy configuration
let proxyConfig = get(brunoConfig, 'proxy', {}); let proxyConfig = get(brunoConfig, 'proxy', {});
let proxyEnabled = get(proxyConfig, 'enabled', 'global'); let proxyMode = get(proxyConfig, 'enabled', 'global');
if (proxyEnabled === 'global') { if (proxyMode === 'global') {
proxyConfig = preferencesUtil.getGlobalProxyConfig(); proxyConfig = preferencesUtil.getGlobalProxyConfig();
proxyEnabled = get(proxyConfig, 'enabled', false); proxyMode = get(proxyConfig, 'mode', false);
} }
const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'bypassProxy', ''));
if (proxyEnabled === true && shouldProxy) {
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false);
const socksEnabled = proxyProtocol.includes('socks');
let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`; // proxyMode is true, if the collection-level proxy is enabled.
let proxyUri; // proxyMode is 'on', if the app-level proxy mode is turned on.
if (proxyAuthEnabled) { if (proxyMode === true || proxyMode === 'on') {
const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions); const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'bypassProxy', ''));
const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions); if (shouldProxy) {
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false);
const socksEnabled = proxyProtocol.includes('socks');
let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`;
let proxyUri;
if (proxyAuthEnabled) {
const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions);
const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions);
proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}${uriPort}`; proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}${uriPort}`;
} else { } else {
proxyUri = `${proxyProtocol}://${proxyHostname}${uriPort}`; proxyUri = `${proxyProtocol}://${proxyHostname}${uriPort}`;
}
if (socksEnabled) {
request.httpsAgent = new SocksProxyAgent(
proxyUri,
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
);
request.httpAgent = new SocksProxyAgent(proxyUri);
} else {
request.httpsAgent = new PatchedHttpsProxyAgent(
proxyUri,
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
);
request.httpAgent = new HttpProxyAgent(proxyUri);
}
} }
} else if (proxyMode === 'system') {
if (socksEnabled) { const { http_proxy, https_proxy, no_proxy } = preferencesUtil.getSystemProxyEnvVariables();
request.httpsAgent = new SocksProxyAgent( const shouldUseSystemProxy = shouldUseProxy(request.url, no_proxy || '');
proxyUri, if (shouldUseSystemProxy) {
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined try {
); if (http_proxy?.length) {
request.httpAgent = new SocksProxyAgent(proxyUri); new URL(http_proxy);
} else { request.httpAgent = new HttpProxyAgent(http_proxy);
request.httpsAgent = new PatchedHttpsProxyAgent( }
proxyUri, } catch (error) {
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined throw new Error('Invalid system http_proxy');
); }
request.httpAgent = new HttpProxyAgent(proxyUri); try {
if (https_proxy?.length) {
new URL(https_proxy);
request.httpsAgent = new PatchedHttpsProxyAgent(
https_proxy,
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
);
}
} catch (error) {
throw new Error('Invalid system https_proxy');
}
} }
} else if (Object.keys(httpsAgentRequestFields).length > 0) { } else if (Object.keys(httpsAgentRequestFields).length > 0) {
request.httpsAgent = new https.Agent({ request.httpsAgent = new https.Agent({
...httpsAgentRequestFields ...httpsAgentRequestFields
}); });
} }
const axiosInstance = makeAxiosInstance(); const axiosInstance = makeAxiosInstance();
if (request.oauth2) { if (request.oauth2) {

View File

@ -1,5 +1,5 @@
const { ipcMain } = require('electron'); const { ipcMain } = require('electron');
const { getPreferences, savePreferences } = require('../store/preferences'); const { getPreferences, savePreferences, preferencesUtil } = require('../store/preferences');
const { isDirectory } = require('../utils/filesystem'); const { isDirectory } = require('../utils/filesystem');
const { openCollection } = require('../app/collections'); const { openCollection } = require('../app/collections');
``; ``;
@ -9,6 +9,10 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
const preferences = getPreferences(); const preferences = getPreferences();
mainWindow.webContents.send('main:load-preferences', preferences); mainWindow.webContents.send('main:load-preferences', preferences);
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });
// reload last opened collections // reload last opened collections
const lastOpened = lastOpenedCollections.getAll(); const lastOpened = lastOpenedCollections.getAll();

View File

@ -27,7 +27,7 @@ const defaultPreferences = {
codeFontSize: 14 codeFontSize: 14
}, },
proxy: { proxy: {
enabled: false, mode: 'off',
protocol: 'http', protocol: 'http',
hostname: '', hostname: '',
port: null, port: null,
@ -59,7 +59,7 @@ const preferencesSchema = Yup.object().shape({
codeFontSize: Yup.number().min(1).max(32).nullable() codeFontSize: Yup.number().min(1).max(32).nullable()
}), }),
proxy: Yup.object({ proxy: Yup.object({
enabled: Yup.boolean(), mode: Yup.string().oneOf(['off', 'on', 'system']),
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string().max(1024), hostname: Yup.string().max(1024),
port: Yup.number().min(1).max(65535).nullable(), port: Yup.number().min(1).max(65535).nullable(),
@ -136,6 +136,14 @@ const preferencesUtil = {
}, },
shouldSendCookies: () => { shouldSendCookies: () => {
return get(getPreferences(), 'request.sendCookies', true); return get(getPreferences(), 'request.sendCookies', true);
},
getSystemProxyEnvVariables: () => {
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
return {
http_proxy: http_proxy || HTTP_PROXY,
https_proxy: https_proxy || HTTPS_PROXY,
no_proxy: no_proxy || NO_PROXY
};
} }
}; };