diff --git a/gui/src/renderer/components/Filter.tsx b/gui/src/renderer/components/Filter.tsx index e66d318e17a7..01be2e374769 100644 --- a/gui/src/renderer/components/Filter.tsx +++ b/gui/src/renderer/components/Filter.tsx @@ -4,8 +4,7 @@ import styled from 'styled-components'; import { colors } from '../../config.json'; import { Ownership } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; -import { useAppContext } from '../context'; -import { toRawNormalRelaySettings } from '../lib/constraint-updater'; +import { useRelaySettingsUpdater } from '../lib/constraint-updater'; import { EndpointType, filterLocations, @@ -39,8 +38,7 @@ const StyledNavigationScrollbars = styled(NavigationScrollbars)({ export default function Filter() { const history = useHistory(); - const { updateRelaySettings } = useAppContext(); - const relaySettings = useSelector((state) => state.settings.relaySettings); + const relaySettingsUpdater = useRelaySettingsUpdater(); const initialProviders = useSelector(providersSelector); const [providers, setProviders] = useState>(initialProviders); @@ -71,12 +69,13 @@ export default function Filter() { // Applies the changes by sending them to the daemon. const onApply = useCallback(async () => { - const settings = toRawNormalRelaySettings(relaySettings); - settings.providers = formattedProviderList; - settings.ownership = ownership; - await updateRelaySettings({ normal: settings }); + await relaySettingsUpdater((settings) => { + settings.providers = formattedProviderList; + settings.ownership = ownership; + return settings; + }); history.pop(); - }, [formattedProviderList, ownership, history, updateRelaySettings, relaySettings]); + }, [formattedProviderList, ownership, history, relaySettingsUpdater]); return ( diff --git a/gui/src/renderer/components/OpenVpnSettings.tsx b/gui/src/renderer/components/OpenVpnSettings.tsx index 40b3d6e511b6..723e32f84cb8 100644 --- a/gui/src/renderer/components/OpenVpnSettings.tsx +++ b/gui/src/renderer/components/OpenVpnSettings.tsx @@ -8,7 +8,6 @@ import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; import { removeNonNumericCharacters } from '../../shared/string-helpers'; import { useAppContext } from '../context'; -import { toRawNormalRelaySettings } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { useBoolean } from '../lib/utilityHooks'; @@ -28,6 +27,7 @@ import { TitleBarItem, } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; +import { useRelaySettingsUpdater } from '../lib/constraint-updater'; const MIN_MSSFIX_VALUE = 1000; const MAX_MSSFIX_VALUE = 1450; @@ -178,23 +178,24 @@ function TransportProtocolSelector() { function useProtocolAndPortUpdater() { const { updateRelaySettings } = useAppContext(); - const relaySettings = useSelector((state) => state.settings.relaySettings); + const relaySettingsUpdater = useRelaySettingsUpdater(); const updater = useCallback( async (protocol: RelayProtocol | null, port?: number | null) => { - const settings = toRawNormalRelaySettings(relaySettings); - settings.openvpnConstraints.protocol = wrapConstraint(protocol); - if (port) { - settings.openvpnConstraints.port = wrapConstraint(port); - } try { - await updateRelaySettings({ normal: settings }); + await relaySettingsUpdater((settings) => { + settings.openvpnConstraints.protocol = wrapConstraint(protocol); + if (port) { + settings.openvpnConstraints.port = wrapConstraint(port); + } + return settings; + }); } catch (e) { const error = e as Error; log.error('Failed to update relay settings', error.message); } }, - [updateRelaySettings, relaySettings], + [updateRelaySettings], ); return updater; diff --git a/gui/src/renderer/components/VpnSettings.tsx b/gui/src/renderer/components/VpnSettings.tsx index bd075c33f3df..2b41c7f8698f 100644 --- a/gui/src/renderer/components/VpnSettings.tsx +++ b/gui/src/renderer/components/VpnSettings.tsx @@ -7,7 +7,6 @@ import { IDnsOptions, TunnelProtocol, wrapConstraint } from '../../shared/daemon import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; import { useAppContext } from '../context'; -import { toRawNormalRelaySettings } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { RoutePath } from '../lib/routes'; @@ -31,6 +30,7 @@ import { TitleBarItem, } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; +import { useRelaySettingsUpdater } from '../lib/constraint-updater'; const StyledContent = styled.div({ display: 'flex', @@ -663,19 +663,16 @@ function TunnelProtocolSetting() { const tunnelProtocol = useSelector((state) => mapRelaySettingsToProtocol(state.settings.relaySettings), ); - const relaySettings = useSelector((state) => state.settings.relaySettings); - const { updateRelaySettings } = useAppContext(); + const relaySettingsUpdater = useRelaySettingsUpdater(); const setTunnelProtocol = useCallback(async (tunnelProtocol: TunnelProtocol | null) => { - const settings = toRawNormalRelaySettings(relaySettings); - settings.tunnelProtocol = wrapConstraint(tunnelProtocol); try { - await updateRelaySettings({ normal: settings }); + await relaySettingsUpdater((settings) => ({ ...settings, tunnelProtocol: wrapConstraint(tunnelProtocol) })); } catch (e) { const error = e as Error; log.error('Failed to update tunnel protocol constraints', error.message); } - }, [relaySettings]); + }, []); const tunnelProtocolItems: Array> = useMemo( () => [ diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 35baf512dbba..82d71a5bef93 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -14,7 +14,6 @@ import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; import { removeNonNumericCharacters } from '../../shared/string-helpers'; import { useAppContext } from '../context'; -import { toRawNormalRelaySettings } from '../lib/constraint-updater'; import { useHistory } from '../lib/history'; import { useBoolean } from '../lib/utilityHooks'; import { useSelector } from '../redux/store'; @@ -34,6 +33,7 @@ import { TitleBarItem, } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; +import { useRelaySettingsUpdater } from '../lib/constraint-updater'; const MIN_WIREGUARD_MTU_VALUE = 1280; const MAX_WIREGUARD_MTU_VALUE = 1420; @@ -133,7 +133,7 @@ export default function WireguardSettings() { function PortSelector() { const relaySettings = useSelector((state) => state.settings.relaySettings); - const { updateRelaySettings } = useAppContext(); + const relaySettingsUpdater = useRelaySettingsUpdater(); const allowedPortRanges = useSelector((state) => state.settings.wireguardEndpointData.portRanges); const wireguardPortItems = useMemo>>( @@ -148,16 +148,17 @@ function PortSelector() { const setWireguardPort = useCallback( async (port: number | null) => { - const settings = toRawNormalRelaySettings(relaySettings); - settings.wireguardConstraints.port = wrapConstraint(port); try { - await updateRelaySettings({ normal: settings }); + await relaySettingsUpdater((settings) => { + settings.wireguardConstraints.port = wrapConstraint(port); + return settings; + }); } catch (e) { const error = e as Error; log.error('Failed to update relay settings', error.message); } }, - [relaySettings], + [relaySettingsUpdater], ); const parseValue = useCallback((port: string) => parseInt(port), []); @@ -318,6 +319,7 @@ function Udp2tcpPortSetting() { function MultihopSetting() { const relaySettings = useSelector((state) => state.settings.relaySettings); + const relaySettingsUpdater = useRelaySettingsUpdater(); const { updateRelaySettings } = useAppContext(); const multihop = 'normal' in relaySettings ? relaySettings.normal.wireguard.useMultihop : false; @@ -326,16 +328,17 @@ function MultihopSetting() { const setMultihopImpl = useCallback( async (enabled: boolean) => { - const settings = toRawNormalRelaySettings(relaySettings); - settings.wireguardConstraints.useMultihop = enabled; try { - await updateRelaySettings({ normal: settings }); + await relaySettingsUpdater((settings) => { + settings.wireguardConstraints.useMultihop = enabled; + return settings; + }); } catch (e) { const error = e as Error; log.error('Failed to update WireGuard multihop settings', error.message); } }, - [relaySettings, updateRelaySettings], + [updateRelaySettings], ); const setMultihop = useCallback( @@ -409,7 +412,7 @@ function MultihopSetting() { } function IpVersionSetting() { - const { updateRelaySettings } = useAppContext(); + const relaySettingsUpdater = useRelaySettingsUpdater(); const relaySettings = useSelector((state) => state.settings.relaySettings); const ipVersion = useMemo(() => { const ipVersion = 'normal' in relaySettings ? relaySettings.normal.wireguard.ipVersion : 'any'; @@ -432,16 +435,17 @@ function IpVersionSetting() { const setIpVersion = useCallback( async (ipVersion: IpVersion | null) => { - const settings = toRawNormalRelaySettings(relaySettings); - settings.wireguardConstraints.ipVersion = wrapConstraint(ipVersion); try { - await updateRelaySettings({ normal: settings }); + await relaySettingsUpdater((settings) => { + settings.wireguardConstraints.ipVersion = wrapConstraint(ipVersion); + return settings; + }); } catch (e) { const error = e as Error; log.error('Failed to update relay settings', error.message); } }, - [relaySettings, updateRelaySettings], + [relaySettingsUpdater], ); return ( diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx index 7372d8df288a..2ebfd1a730c2 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -4,8 +4,6 @@ import { sprintf } from 'sprintf-js'; import { colors } from '../../../config.json'; import { Ownership } from '../../../shared/daemon-rpc-types'; import { messages } from '../../../shared/gettext'; -import { useAppContext } from '../../context'; -import { toRawNormalRelaySettings } from '../../lib/constraint-updater'; import { filterSpecialLocations } from '../../lib/filter-locations'; import { useHistory } from '../../lib/history'; import { formatHtml } from '../../lib/html-formatter'; @@ -55,10 +53,11 @@ import { StyledSearchBar, } from './SelectLocationStyles'; import { SpacePreAllocationView } from './SpacePreAllocationView'; +import { useRelaySettingsUpdater } from '../../lib/constraint-updater'; export default function SelectLocation() { const history = useHistory(); - const { updateRelaySettings } = useAppContext(); + const relaySettingsUpdater = useRelaySettingsUpdater(); const { saveScrollPosition, resetScrollPositions, @@ -87,20 +86,16 @@ export default function SelectLocation() { const onClearProviders = useCallback(async () => { resetScrollPositions(); if (relaySettings) { - const newSettings = toRawNormalRelaySettings({ normal: relaySettings }); - newSettings.providers = []; - await updateRelaySettings({ normal: newSettings }); + await relaySettingsUpdater((settings) => ({ ...settings, providers: [] })); } - }, [updateRelaySettings, resetScrollPositions, relaySettings]); + }, [relaySettingsUpdater, resetScrollPositions, relaySettings]); const onClearOwnership = useCallback(async () => { resetScrollPositions(); if (relaySettings) { - const newSettings = toRawNormalRelaySettings({ normal: relaySettings }); - newSettings.ownership = Ownership.any; - await updateRelaySettings({ normal: newSettings }); + await relaySettingsUpdater((settings) => ({ ...settings, ownership: Ownership.any })); } - }, [updateRelaySettings, resetScrollPositions, relaySettings]); + }, [relaySettingsUpdater, resetScrollPositions, relaySettings]); const changeLocationType = useCallback( (locationType: LocationType) => { diff --git a/gui/src/renderer/components/select-location/select-location-hooks.ts b/gui/src/renderer/components/select-location/select-location-hooks.ts index 51bd5d6e641d..78298352b21e 100644 --- a/gui/src/renderer/components/select-location/select-location-hooks.ts +++ b/gui/src/renderer/components/select-location/select-location-hooks.ts @@ -5,30 +5,29 @@ import { BridgeSettings, RelayLocation, RelaySettings, + wrapConstraint, } from '../../../shared/daemon-rpc-types'; import log from '../../../shared/logging'; import { useAppContext } from '../../context'; -import { toRawNormalRelaySettings } from '../../lib/constraint-updater'; import { useHistory } from '../../lib/history'; -import { useSelector } from '../../redux/store'; import { LocationType, SpecialBridgeLocationType } from './select-location-types'; import { useSelectLocationContext } from './SelectLocationContainer'; +import { useRelaySettingsModifier } from '../../lib/constraint-updater'; export function useOnSelectExitLocation() { const onSelectLocation = useOnSelectLocation(); const history = useHistory(); - const relaySettings = useSelector((state) => state.settings.relaySettings); + const relaySettingsModifier = useRelaySettingsModifier(); const { connectTunnel } = useAppContext(); const onSelectRelay = useCallback( async (relayLocation: RelayLocation) => { + const settings = await relaySettingsModifier((settings) => ({ ...settings, location: wrapConstraint(relayLocation) })); history.pop(); - const settings = toRawNormalRelaySettings(relaySettings); - settings.location = { only: relayLocation }; await onSelectLocation({ normal: settings }); await connectTunnel(); }, - [history, relaySettings], + [history, relaySettingsModifier], ); const onSelectSpecial = useCallback((_location: undefined) => { @@ -41,21 +40,25 @@ export function useOnSelectExitLocation() { export function useOnSelectEntryLocation() { const onSelectLocation = useOnSelectLocation(); const { setLocationType } = useSelectLocationContext(); - const baseRelaySettings = useSelector((state) => state.settings.relaySettings); + const relaySettingsModifier = useRelaySettingsModifier(); const onSelectRelay = useCallback(async (entryLocation: RelayLocation) => { setLocationType(LocationType.exit); - const settings = toRawNormalRelaySettings(baseRelaySettings); - settings.wireguardConstraints.entryLocation = { only: entryLocation }; + const settings = await relaySettingsModifier((settings) => { + settings.wireguardConstraints.entryLocation = wrapConstraint(entryLocation); + return settings; + }); await onSelectLocation({ normal: settings }); - }, []); + }, [relaySettingsModifier]); const onSelectSpecial = useCallback(async (_location: 'any') => { setLocationType(LocationType.exit); - const settings = toRawNormalRelaySettings(baseRelaySettings); - settings.wireguardConstraints.entryLocation = 'any'; + const settings = await relaySettingsModifier((settings) => { + settings.wireguardConstraints.entryLocation = 'any'; + return settings; + }); await onSelectLocation({ normal: settings }); - }, []); + }, [relaySettingsModifier]); return [onSelectRelay, onSelectSpecial] as const; } @@ -68,7 +71,7 @@ function useOnSelectLocation() { await updateRelaySettings(relaySettings); } catch (e) { const error = e as Error; - log.error(`Failed to select the exit location: ${error.message}`); + log.error(`Failed to select the location: ${error.message}`); } }, []); } diff --git a/gui/src/renderer/lib/constraint-updater.ts b/gui/src/renderer/lib/constraint-updater.ts index 3e93bf1771b8..6e894703aee6 100644 --- a/gui/src/renderer/lib/constraint-updater.ts +++ b/gui/src/renderer/lib/constraint-updater.ts @@ -1,19 +1,22 @@ +import { useCallback } from 'react'; import { IOpenVpnConstraints, IRelaySettingsNormal, IWireguardConstraints, Ownership, wrapConstraint } from '../../shared/daemon-rpc-types'; -import { RelaySettingsRedux } from '../redux/settings/reducers'; +import { NormalRelaySettingsRedux } from '../redux/settings/reducers'; +import { useNormalRelaySettings } from './utilityHooks'; +import { useAppContext } from '../context'; -export function toRawNormalRelaySettings(relaySettings: RelaySettingsRedux): IRelaySettingsNormal { - if ('normal' in relaySettings) { - const openvpnPort = wrapConstraint(relaySettings.normal.openvpn.port); - const openvpnProtocol = wrapConstraint(relaySettings.normal.openvpn.protocol); - const wgPort = wrapConstraint(relaySettings.normal.wireguard.port); - const wgIpVersion = wrapConstraint(relaySettings.normal.wireguard.ipVersion); - const wgEntryLocation = wrapConstraint(relaySettings.normal.wireguard.entryLocation); - const location = wrapConstraint(relaySettings.normal.location); - const tunnelProtocol = wrapConstraint(relaySettings.normal.tunnelProtocol); +export function wrapRelaySettingsOrDefault(relaySettings?: NormalRelaySettingsRedux): IRelaySettingsNormal { + if (relaySettings) { + const openvpnPort = wrapConstraint(relaySettings.openvpn.port); + const openvpnProtocol = wrapConstraint(relaySettings.openvpn.protocol); + const wgPort = wrapConstraint(relaySettings.wireguard.port); + const wgIpVersion = wrapConstraint(relaySettings.wireguard.ipVersion); + const wgEntryLocation = wrapConstraint(relaySettings.wireguard.entryLocation); + const location = wrapConstraint(relaySettings.location); + const tunnelProtocol = wrapConstraint(relaySettings.tunnelProtocol); return { - providers: [...relaySettings.normal.providers], - ownership: relaySettings.normal.ownership, + providers: [...relaySettings.providers], + ownership: relaySettings.ownership, tunnelProtocol, openvpnConstraints: { port: openvpnPort, @@ -22,7 +25,7 @@ export function toRawNormalRelaySettings(relaySettings: RelaySettingsRedux): IRe wireguardConstraints: { port: wgPort, ipVersion: wgIpVersion, - useMultihop: relaySettings.normal.wireguard.useMultihop, + useMultihop: relaySettings.wireguard.useMultihop, entryLocation: wgEntryLocation, }, location, @@ -46,3 +49,22 @@ export function toRawNormalRelaySettings(relaySettings: RelaySettingsRedux): IRe }, }; } + +export function useRelaySettingsModifier() { + const relaySettings = useNormalRelaySettings(); + + return useCallback((fn: (settings: IRelaySettingsNormal) => IRelaySettingsNormal) => { + const settings = wrapRelaySettingsOrDefault(relaySettings); + return fn(settings); + }, [relaySettings]); +} + +export function useRelaySettingsUpdater() { + const { updateRelaySettings } = useAppContext(); + const modifyRelaySettings = useRelaySettingsModifier(); + + return useCallback((fn: (settings: IRelaySettingsNormal) => IRelaySettingsNormal) => { + const modifiedSettings = modifyRelaySettings(fn); + updateRelaySettings({ normal: modifiedSettings }); + }, [updateRelaySettings, modifyRelaySettings]); +}