diff options
| author | Oliver <oliver@mohlin.dev> | 2025-08-28 11:42:51 +0200 |
|---|---|---|
| committer | Tobias Järvelöv <tobias.jarvelov@mullvad.net> | 2025-09-22 12:35:43 +0200 |
| commit | 0ada6b7e47dfcd008ada1d2fb4d178c95501612a (patch) | |
| tree | 5b0bfd910d339fb8276d97b4912e1370e9adc9ed | |
| parent | e4d719bfaf940880684ac8e9ab61ad909c2d8798 (diff) | |
| download | mullvadvpn-0ada6b7e47dfcd008ada1d2fb4d178c95501612a.tar.xz mullvadvpn-0ada6b7e47dfcd008ada1d2fb4d178c95501612a.zip | |
Move wireguard settings component to separate folders
13 files changed, 485 insertions, 415 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx index 283bc5e83d..0750310ab2 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx @@ -1,27 +1,9 @@ -import { useCallback, useMemo } from 'react'; import { sprintf } from 'sprintf-js'; -import styled from 'styled-components'; import { strings } from '../../../../shared/constants'; -import { - Constraint, - IpVersion, - ObfuscationType, - wrapConstraint, -} from '../../../../shared/daemon-rpc-types'; import { messages } from '../../../../shared/gettext'; -import log from '../../../../shared/logging'; -import { RoutePath } from '../../../../shared/routes'; -import { removeNonNumericCharacters } from '../../../../shared/string-helpers'; -import { isInRanges } from '../../../../shared/utils'; -import { useAppContext } from '../../../context'; -import { useRelaySettingsUpdater } from '../../../lib/constraint-updater'; import { useHistory } from '../../../lib/history'; -import { useSelector } from '../../../redux/store'; import { AppNavigationHeader } from '../..'; -import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from '../../AriaGroup'; -import * as Cell from '../../cell'; -import Selector, { SelectorItem, SelectorWithCustomItem } from '../../cell/Selector'; import { BackAction } from '../../KeyboardNavigation'; import { Layout, @@ -30,22 +12,16 @@ import { SettingsGroup, SettingsStack, } from '../../Layout'; -import { ModalMessage } from '../../Modal'; import { NavigationContainer } from '../../NavigationContainer'; import { NavigationScrollbars } from '../../NavigationScrollbars'; import SettingsHeader, { HeaderTitle } from '../../SettingsHeader'; - -const MIN_WIREGUARD_MTU_VALUE = 1280; -const MAX_WIREGUARD_MTU_VALUE = 1420; -const WIREUGARD_UDP_PORTS = [51820, 53]; - -function mapPortToSelectorItem(value: number): SelectorItem<number> { - return { label: value.toString(), value }; -} - -const StyledSelectorContainer = styled.div({ - flex: 0, -}); +import { + IpVersionSetting, + MtuSetting, + ObfuscationSettings, + PortSelector, + QuantumResistantSetting, +} from './components'; export function WireguardSettingsView() { const { pop } = useHistory(); @@ -106,386 +82,3 @@ export function WireguardSettingsView() { </BackAction> ); } - -function PortSelector() { - const relaySettings = useSelector((state) => state.settings.relaySettings); - const relaySettingsUpdater = useRelaySettingsUpdater(); - const allowedPortRanges = useSelector((state) => state.settings.wireguardEndpointData.portRanges); - - const wireguardPortItems = useMemo<Array<SelectorItem<number>>>( - () => WIREUGARD_UDP_PORTS.map(mapPortToSelectorItem), - [], - ); - - const port = useMemo(() => { - const port = 'normal' in relaySettings ? relaySettings.normal.wireguard.port : 'any'; - return port === 'any' ? null : port; - }, [relaySettings]); - - const setWireguardPort = useCallback( - async (port: number | null) => { - try { - 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); - } - }, - [relaySettingsUpdater], - ); - - const parseValue = useCallback((port: string) => parseInt(port), []); - - const validateValue = useCallback( - (value: number) => isInRanges(value, allowedPortRanges), - [allowedPortRanges], - ); - - const portRangesText = allowedPortRanges - .map(([start, end]) => (start === end ? start : `${start}-${end}`)) - .join(', '); - - return ( - <AriaInputGroup> - <StyledSelectorContainer> - <SelectorWithCustomItem - // TRANSLATORS: The title for the WireGuard port selector. - title={messages.pgettext('wireguard-settings-view', 'Port')} - items={wireguardPortItems} - value={port} - onSelect={setWireguardPort} - inputPlaceholder={messages.pgettext('wireguard-settings-view', 'Port')} - automaticValue={null} - parseValue={parseValue} - modifyValue={removeNonNumericCharacters} - validateValue={validateValue} - maxLength={5} - details={ - <> - <ModalMessage> - {messages.pgettext( - 'wireguard-settings-view', - 'The automatic setting will randomly choose from the valid port ranges shown below.', - )} - </ModalMessage> - <ModalMessage> - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - 'The custom port can be any value inside the valid ranges: %(portRanges)s.', - ), - { portRanges: portRangesText }, - )} - </ModalMessage> - </> - } - /> - </StyledSelectorContainer> - </AriaInputGroup> - ); -} - -function ObfuscationSettings() { - const { setObfuscationSettings } = useAppContext(); - const obfuscationSettings = useSelector((state) => state.settings.obfuscationSettings); - - // TRANSLATORS: Text showing currently selected port. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(port)s - Can be either a number between 1 and 65535 or the text "Automatic". - const subLabelTemplate = messages.pgettext('wireguard-settings-view', 'Port: %(port)s'); - - const obfuscationType = obfuscationSettings.selectedObfuscation; - const obfuscationTypeItems: SelectorItem<ObfuscationType>[] = useMemo( - () => [ - { - label: messages.pgettext('wireguard-settings-view', 'Shadowsocks'), - subLabel: sprintf(subLabelTemplate, { - port: formatPortForSubLabel(obfuscationSettings.shadowsocksSettings.port), - }), - value: ObfuscationType.shadowsocks, - details: { - path: RoutePath.shadowsocks, - ariaLabel: messages.pgettext('accessibility', 'Shadowsocks settings'), - }, - }, - { - label: messages.pgettext('wireguard-settings-view', 'UDP-over-TCP'), - subLabel: sprintf(subLabelTemplate, { - port: formatPortForSubLabel(obfuscationSettings.udp2tcpSettings.port), - }), - value: ObfuscationType.udp2tcp, - details: { - path: RoutePath.udpOverTcp, - ariaLabel: messages.pgettext('accessibility', 'UDP-over-TCP settings'), - }, - }, - { - label: messages.pgettext('wireguard-settings-view', 'QUIC'), - value: ObfuscationType.quic, - }, - { - label: messages.gettext('Off'), - value: ObfuscationType.off, - }, - ], - [ - obfuscationSettings.shadowsocksSettings.port, - obfuscationSettings.udp2tcpSettings.port, - subLabelTemplate, - ], - ); - - const selectObfuscationType = useCallback( - async (value: ObfuscationType) => { - await setObfuscationSettings({ - ...obfuscationSettings, - selectedObfuscation: value, - }); - }, - [setObfuscationSettings, obfuscationSettings], - ); - - return ( - <AriaInputGroup> - <StyledSelectorContainer> - <Selector - // TRANSLATORS: The title for the WireGuard obfuscation selector. - title={messages.pgettext('wireguard-settings-view', 'Obfuscation')} - details={ - <ModalMessage> - { - // TRANSLATORS: Describes what WireGuard obfuscation does, how it works and when - // TRANSLATORS: it would be useful to enable it. - messages.pgettext( - 'wireguard-settings-view', - 'Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connection would be blocked.', - ) - } - </ModalMessage> - } - items={obfuscationTypeItems} - value={obfuscationType} - onSelect={selectObfuscationType} - automaticValue={ObfuscationType.auto} - automaticTestId="automatic-obfuscation" - /> - </StyledSelectorContainer> - </AriaInputGroup> - ); -} - -function formatPortForSubLabel(port: Constraint<number>): string { - return port === 'any' ? messages.gettext('Automatic') : `${port.only}`; -} - -function IpVersionSetting() { - const relaySettingsUpdater = useRelaySettingsUpdater(); - const relaySettings = useSelector((state) => state.settings.relaySettings); - const ipVersion = useMemo(() => { - const ipVersion = 'normal' in relaySettings ? relaySettings.normal.wireguard.ipVersion : 'any'; - return ipVersion === 'any' ? null : ipVersion; - }, [relaySettings]); - - const ipVersionItems: SelectorItem<IpVersion>[] = useMemo( - () => [ - { - label: messages.gettext('IPv4'), - value: 'ipv4', - }, - { - label: messages.gettext('IPv6'), - value: 'ipv6', - }, - ], - [], - ); - - const setIpVersion = useCallback( - async (ipVersion: IpVersion | null) => { - try { - 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); - } - }, - [relaySettingsUpdater], - ); - - return ( - <AriaInputGroup> - <StyledSelectorContainer> - <Selector - // TRANSLATORS: The title for the WireGuard IP version selector. - title={messages.pgettext('wireguard-settings-view', 'IP version')} - items={ipVersionItems} - value={ipVersion} - onSelect={setIpVersion} - automaticValue={null} - /> - </StyledSelectorContainer> - <Cell.CellFooter> - <AriaDescription> - <Cell.CellFooterText> - {sprintf( - // TRANSLATORS: The hint displayed below the WireGuard IP version selector. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard" - messages.pgettext( - 'wireguard-settings-view', - 'This allows access to %(wireguard)s for devices that only support IPv6.', - ), - { wireguard: strings.wireguard }, - )} - </Cell.CellFooterText> - </AriaDescription> - </Cell.CellFooter> - </AriaInputGroup> - ); -} - -function mtuIsValid(mtu: string): boolean { - const parsedMtu = mtu ? parseInt(mtu) : undefined; - return ( - parsedMtu === undefined || - (parsedMtu >= MIN_WIREGUARD_MTU_VALUE && parsedMtu <= MAX_WIREGUARD_MTU_VALUE) - ); -} - -function MtuSetting() { - const { setWireguardMtu: setWireguardMtuImpl } = useAppContext(); - const mtu = useSelector((state) => state.settings.wireguard.mtu); - - const setMtu = useCallback( - async (mtu?: number) => { - try { - await setWireguardMtuImpl(mtu); - } catch (e) { - const error = e as Error; - log.error('Failed to update mtu value', error.message); - } - }, - [setWireguardMtuImpl], - ); - - const onSubmit = useCallback( - async (value: string) => { - const parsedValue = value === '' ? undefined : parseInt(value, 10); - if (mtuIsValid(value)) { - await setMtu(parsedValue); - } - }, - [setMtu], - ); - - return ( - <AriaInputGroup> - <Cell.Container> - <AriaLabel> - <Cell.InputLabel>{messages.pgettext('wireguard-settings-view', 'MTU')}</Cell.InputLabel> - </AriaLabel> - <AriaInput> - <Cell.AutoSizingTextInput - initialValue={mtu ? mtu.toString() : ''} - inputMode={'numeric'} - maxLength={4} - placeholder={messages.gettext('Default')} - onSubmitValue={onSubmit} - validateValue={mtuIsValid} - submitOnBlur={true} - modifyValue={removeNonNumericCharacters} - /> - </AriaInput> - </Cell.Container> - <Cell.CellFooter> - <AriaDescription> - <Cell.CellFooterText> - {sprintf( - // TRANSLATORS: The hint displayed below the WireGuard MTU input field. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard" - // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value - // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value - messages.pgettext( - 'wireguard-settings-view', - 'Set %(wireguard)s MTU value. Valid range: %(min)d - %(max)d.', - ), - { - wireguard: strings.wireguard, - min: MIN_WIREGUARD_MTU_VALUE, - max: MAX_WIREGUARD_MTU_VALUE, - }, - )} - </Cell.CellFooterText> - </AriaDescription> - </Cell.CellFooter> - </AriaInputGroup> - ); -} - -function QuantumResistantSetting() { - const { setWireguardQuantumResistant } = useAppContext(); - const quantumResistant = useSelector((state) => state.settings.wireguard.quantumResistant); - - const items: SelectorItem<boolean>[] = useMemo( - () => [ - { - label: messages.gettext('On'), - value: true, - }, - { - label: messages.gettext('Off'), - value: false, - }, - ], - [], - ); - - const selectQuantumResistant = useCallback( - async (quantumResistant: boolean | null) => { - await setWireguardQuantumResistant(quantumResistant ?? undefined); - }, - [setWireguardQuantumResistant], - ); - - return ( - <AriaInputGroup> - <StyledSelectorContainer> - <Selector - title={ - // TRANSLATORS: The title for the WireGuard quantum resistance selector. This setting - // TRANSLATORS: makes the cryptography resistant to the future abilities of quantum - // TRANSLATORS: computers. - messages.pgettext('wireguard-settings-view', 'Quantum-resistant tunnel') - } - details={ - <> - <ModalMessage> - {messages.pgettext( - 'wireguard-settings-view', - 'This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.', - )} - </ModalMessage> - <ModalMessage> - {messages.pgettext( - 'wireguard-settings-view', - 'It does this by performing an extra key exchange using a quantum safe algorithm and mixing the result into WireGuard’s regular encryption. This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.', - )} - </ModalMessage> - </> - } - items={items} - value={quantumResistant ?? null} - onSelect={selectQuantumResistant} - automaticValue={null} - /> - </StyledSelectorContainer> - </AriaInputGroup> - ); -} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts new file mode 100644 index 0000000000..42aaeef491 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts @@ -0,0 +1,5 @@ +export * from './ip-version-setting'; +export * from './mtu-setting'; +export * from './obfuscation-settings'; +export * from './port-setting'; +export * from './quantum-resistant-setting'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx new file mode 100644 index 0000000000..4d72450bd3 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx @@ -0,0 +1,86 @@ +import { useCallback, useMemo } from 'react'; +import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; + +import { strings } from '../../../../../../shared/constants'; +import { IpVersion, wrapConstraint } from '../../../../../../shared/daemon-rpc-types'; +import { messages } from '../../../../../../shared/gettext'; +import log from '../../../../../../shared/logging'; +import { useRelaySettingsUpdater } from '../../../../../lib/constraint-updater'; +import { useSelector } from '../../../../../redux/store'; +import { AriaDescription, AriaInputGroup } from '../../../../AriaGroup'; +import * as Cell from '../../../../cell'; +import Selector, { SelectorItem } from '../../../../cell/Selector'; + +const StyledSelectorContainer = styled.div({ + flex: 0, +}); + +export function IpVersionSetting() { + const relaySettingsUpdater = useRelaySettingsUpdater(); + const relaySettings = useSelector((state) => state.settings.relaySettings); + const ipVersion = useMemo(() => { + const ipVersion = 'normal' in relaySettings ? relaySettings.normal.wireguard.ipVersion : 'any'; + return ipVersion === 'any' ? null : ipVersion; + }, [relaySettings]); + + const ipVersionItems: SelectorItem<IpVersion>[] = useMemo( + () => [ + { + label: messages.gettext('IPv4'), + value: 'ipv4', + }, + { + label: messages.gettext('IPv6'), + value: 'ipv6', + }, + ], + [], + ); + + const setIpVersion = useCallback( + async (ipVersion: IpVersion | null) => { + try { + 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); + } + }, + [relaySettingsUpdater], + ); + + return ( + <AriaInputGroup> + <StyledSelectorContainer> + <Selector + // TRANSLATORS: The title for the WireGuard IP version selector. + title={messages.pgettext('wireguard-settings-view', 'IP version')} + items={ipVersionItems} + value={ipVersion} + onSelect={setIpVersion} + automaticValue={null} + /> + </StyledSelectorContainer> + <Cell.CellFooter> + <AriaDescription> + <Cell.CellFooterText> + {sprintf( + // TRANSLATORS: The hint displayed below the WireGuard IP version selector. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard" + messages.pgettext( + 'wireguard-settings-view', + 'This allows access to %(wireguard)s for devices that only support IPv6.', + ), + { wireguard: strings.wireguard }, + )} + </Cell.CellFooterText> + </AriaDescription> + </Cell.CellFooter> + </AriaInputGroup> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts new file mode 100644 index 0000000000..947b3403ce --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts @@ -0,0 +1 @@ +export * from './IpVersionSetting'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx new file mode 100644 index 0000000000..3b8ed56c81 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx @@ -0,0 +1,93 @@ +import { useCallback } from 'react'; +import { sprintf } from 'sprintf-js'; + +import { strings } from '../../../../../../shared/constants'; +import { messages } from '../../../../../../shared/gettext'; +import log from '../../../../../../shared/logging'; +import { removeNonNumericCharacters } from '../../../../../../shared/string-helpers'; +import { useAppContext } from '../../../../../context'; +import { useSelector } from '../../../../../redux/store'; +import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from '../../../../AriaGroup'; +import * as Cell from '../../../../cell'; + +const MIN_WIREGUARD_MTU_VALUE = 1280; +const MAX_WIREGUARD_MTU_VALUE = 1420; + +function mtuIsValid(mtu: string): boolean { + const parsedMtu = mtu ? parseInt(mtu) : undefined; + return ( + parsedMtu === undefined || + (parsedMtu >= MIN_WIREGUARD_MTU_VALUE && parsedMtu <= MAX_WIREGUARD_MTU_VALUE) + ); +} + +export function MtuSetting() { + const { setWireguardMtu: setWireguardMtuImpl } = useAppContext(); + const mtu = useSelector((state) => state.settings.wireguard.mtu); + + const setMtu = useCallback( + async (mtu?: number) => { + try { + await setWireguardMtuImpl(mtu); + } catch (e) { + const error = e as Error; + log.error('Failed to update mtu value', error.message); + } + }, + [setWireguardMtuImpl], + ); + + const onSubmit = useCallback( + async (value: string) => { + const parsedValue = value === '' ? undefined : parseInt(value, 10); + if (mtuIsValid(value)) { + await setMtu(parsedValue); + } + }, + [setMtu], + ); + + return ( + <AriaInputGroup> + <Cell.Container> + <AriaLabel> + <Cell.InputLabel>{messages.pgettext('wireguard-settings-view', 'MTU')}</Cell.InputLabel> + </AriaLabel> + <AriaInput> + <Cell.AutoSizingTextInput + initialValue={mtu ? mtu.toString() : ''} + inputMode={'numeric'} + maxLength={4} + placeholder={messages.gettext('Default')} + onSubmitValue={onSubmit} + validateValue={mtuIsValid} + submitOnBlur={true} + modifyValue={removeNonNumericCharacters} + /> + </AriaInput> + </Cell.Container> + <Cell.CellFooter> + <AriaDescription> + <Cell.CellFooterText> + {sprintf( + // TRANSLATORS: The hint displayed below the WireGuard MTU input field. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard" + // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value + // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value + messages.pgettext( + 'wireguard-settings-view', + 'Set %(wireguard)s MTU value. Valid range: %(min)d - %(max)d.', + ), + { + wireguard: strings.wireguard, + min: MIN_WIREGUARD_MTU_VALUE, + max: MAX_WIREGUARD_MTU_VALUE, + }, + )} + </Cell.CellFooterText> + </AriaDescription> + </Cell.CellFooter> + </AriaInputGroup> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts new file mode 100644 index 0000000000..8925e802c8 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts @@ -0,0 +1 @@ +export * from './MtuSetting'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx new file mode 100644 index 0000000000..8ad91f387e --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx @@ -0,0 +1,109 @@ +import { useCallback, useMemo } from 'react'; +import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; + +import { Constraint, ObfuscationType } from '../../../../../../shared/daemon-rpc-types'; +import { messages } from '../../../../../../shared/gettext'; +import { RoutePath } from '../../../../../../shared/routes'; +import { useAppContext } from '../../../../../context'; +import { useSelector } from '../../../../../redux/store'; +import { AriaInputGroup } from '../../../../AriaGroup'; +import Selector, { SelectorItem } from '../../../../cell/Selector'; +import { ModalMessage } from '../../../../Modal'; + +const StyledSelectorContainer = styled.div({ + flex: 0, +}); + +export function formatPortForSubLabel(port: Constraint<number>): string { + return port === 'any' ? messages.gettext('Automatic') : `${port.only}`; +} + +export function ObfuscationSettings() { + const { setObfuscationSettings } = useAppContext(); + const obfuscationSettings = useSelector((state) => state.settings.obfuscationSettings); + + // TRANSLATORS: Text showing currently selected port. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(port)s - Can be either a number between 1 and 65535 or the text "Automatic". + const subLabelTemplate = messages.pgettext('wireguard-settings-view', 'Port: %(port)s'); + + const obfuscationType = obfuscationSettings.selectedObfuscation; + const obfuscationTypeItems: SelectorItem<ObfuscationType>[] = useMemo( + () => [ + { + label: messages.pgettext('wireguard-settings-view', 'Shadowsocks'), + subLabel: sprintf(subLabelTemplate, { + port: formatPortForSubLabel(obfuscationSettings.shadowsocksSettings.port), + }), + value: ObfuscationType.shadowsocks, + details: { + path: RoutePath.shadowsocks, + ariaLabel: messages.pgettext('accessibility', 'Shadowsocks settings'), + }, + }, + { + label: messages.pgettext('wireguard-settings-view', 'UDP-over-TCP'), + subLabel: sprintf(subLabelTemplate, { + port: formatPortForSubLabel(obfuscationSettings.udp2tcpSettings.port), + }), + value: ObfuscationType.udp2tcp, + details: { + path: RoutePath.udpOverTcp, + ariaLabel: messages.pgettext('accessibility', 'UDP-over-TCP settings'), + }, + }, + { + label: messages.pgettext('wireguard-settings-view', 'QUIC'), + value: ObfuscationType.quic, + }, + { + label: messages.gettext('Off'), + value: ObfuscationType.off, + }, + ], + [ + obfuscationSettings.shadowsocksSettings.port, + obfuscationSettings.udp2tcpSettings.port, + subLabelTemplate, + ], + ); + + const selectObfuscationType = useCallback( + async (value: ObfuscationType) => { + await setObfuscationSettings({ + ...obfuscationSettings, + selectedObfuscation: value, + }); + }, + [setObfuscationSettings, obfuscationSettings], + ); + + return ( + <AriaInputGroup> + <StyledSelectorContainer> + <Selector + // TRANSLATORS: The title for the WireGuard obfuscation selector. + title={messages.pgettext('wireguard-settings-view', 'Obfuscation')} + details={ + <ModalMessage> + { + // TRANSLATORS: Describes what WireGuard obfuscation does, how it works and when + // TRANSLATORS: it would be useful to enable it. + messages.pgettext( + 'wireguard-settings-view', + 'Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connection would be blocked.', + ) + } + </ModalMessage> + } + items={obfuscationTypeItems} + value={obfuscationType} + onSelect={selectObfuscationType} + automaticValue={ObfuscationType.auto} + automaticTestId="automatic-obfuscation" + /> + </StyledSelectorContainer> + </AriaInputGroup> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts new file mode 100644 index 0000000000..16dbafd65d --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts @@ -0,0 +1 @@ +export * from './ObfuscationSettings'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx new file mode 100644 index 0000000000..f11abde22b --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx @@ -0,0 +1,105 @@ +import { useCallback, useMemo } from 'react'; +import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; + +import { wrapConstraint } from '../../../../../../shared/daemon-rpc-types'; +import { messages } from '../../../../../../shared/gettext'; +import log from '../../../../../../shared/logging'; +import { removeNonNumericCharacters } from '../../../../../../shared/string-helpers'; +import { isInRanges } from '../../../../../../shared/utils'; +import { useRelaySettingsUpdater } from '../../../../../lib/constraint-updater'; +import { useSelector } from '../../../../../redux/store'; +import { AriaInputGroup } from '../../../../AriaGroup'; +import { SelectorItem, SelectorWithCustomItem } from '../../../../cell/Selector'; +import { ModalMessage } from '../../../../Modal'; + +const WIREUGARD_UDP_PORTS = [51820, 53]; + +function mapPortToSelectorItem(value: number): SelectorItem<number> { + return { label: value.toString(), value }; +} + +const StyledSelectorContainer = styled.div({ + flex: 0, +}); + +export function PortSelector() { + const relaySettings = useSelector((state) => state.settings.relaySettings); + const relaySettingsUpdater = useRelaySettingsUpdater(); + const allowedPortRanges = useSelector((state) => state.settings.wireguardEndpointData.portRanges); + + const wireguardPortItems = useMemo<Array<SelectorItem<number>>>( + () => WIREUGARD_UDP_PORTS.map(mapPortToSelectorItem), + [], + ); + + const port = useMemo(() => { + const port = 'normal' in relaySettings ? relaySettings.normal.wireguard.port : 'any'; + return port === 'any' ? null : port; + }, [relaySettings]); + + const setWireguardPort = useCallback( + async (port: number | null) => { + try { + 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); + } + }, + [relaySettingsUpdater], + ); + + const parseValue = useCallback((port: string) => parseInt(port), []); + + const validateValue = useCallback( + (value: number) => isInRanges(value, allowedPortRanges), + [allowedPortRanges], + ); + + const portRangesText = allowedPortRanges + .map(([start, end]) => (start === end ? start : `${start}-${end}`)) + .join(', '); + + return ( + <AriaInputGroup> + <StyledSelectorContainer> + <SelectorWithCustomItem + // TRANSLATORS: The title for the WireGuard port selector. + title={messages.pgettext('wireguard-settings-view', 'Port')} + items={wireguardPortItems} + value={port} + onSelect={setWireguardPort} + inputPlaceholder={messages.pgettext('wireguard-settings-view', 'Port')} + automaticValue={null} + parseValue={parseValue} + modifyValue={removeNonNumericCharacters} + validateValue={validateValue} + maxLength={5} + details={ + <> + <ModalMessage> + {messages.pgettext( + 'wireguard-settings-view', + 'The automatic setting will randomly choose from the valid port ranges shown below.', + )} + </ModalMessage> + <ModalMessage> + {sprintf( + messages.pgettext( + 'wireguard-settings-view', + 'The custom port can be any value inside the valid ranges: %(portRanges)s.', + ), + { portRanges: portRangesText }, + )} + </ModalMessage> + </> + } + /> + </StyledSelectorContainer> + </AriaInputGroup> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts new file mode 100644 index 0000000000..b936bda065 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts @@ -0,0 +1 @@ +export * from './PortSelector'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx new file mode 100644 index 0000000000..f853d913a4 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx @@ -0,0 +1,74 @@ +import { useCallback, useMemo } from 'react'; +import styled from 'styled-components'; + +import { messages } from '../../../../../../shared/gettext'; +import { useAppContext } from '../../../../../context'; +import { useSelector } from '../../../../../redux/store'; +import { AriaInputGroup } from '../../../../AriaGroup'; +import Selector, { SelectorItem } from '../../../../cell/Selector'; +import { ModalMessage } from '../../../../Modal'; + +const StyledSelectorContainer = styled.div({ + flex: 0, +}); + +export function QuantumResistantSetting() { + const { setWireguardQuantumResistant } = useAppContext(); + const quantumResistant = useSelector((state) => state.settings.wireguard.quantumResistant); + + const items: SelectorItem<boolean>[] = useMemo( + () => [ + { + label: messages.gettext('On'), + value: true, + }, + { + label: messages.gettext('Off'), + value: false, + }, + ], + [], + ); + + const selectQuantumResistant = useCallback( + async (quantumResistant: boolean | null) => { + await setWireguardQuantumResistant(quantumResistant ?? undefined); + }, + [setWireguardQuantumResistant], + ); + + return ( + <AriaInputGroup> + <StyledSelectorContainer> + <Selector + title={ + // TRANSLATORS: The title for the WireGuard quantum resistance selector. This setting + // TRANSLATORS: makes the cryptography resistant to the future abilities of quantum + // TRANSLATORS: computers. + messages.pgettext('wireguard-settings-view', 'Quantum-resistant tunnel') + } + details={ + <> + <ModalMessage> + {messages.pgettext( + 'wireguard-settings-view', + 'This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.', + )} + </ModalMessage> + <ModalMessage> + {messages.pgettext( + 'wireguard-settings-view', + 'It does this by performing an extra key exchange using a quantum safe algorithm and mixing the result into WireGuard’s regular encryption. This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.', + )} + </ModalMessage> + </> + } + items={items} + value={quantumResistant ?? null} + onSelect={selectQuantumResistant} + automaticValue={null} + /> + </StyledSelectorContainer> + </AriaInputGroup> + ); +} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts new file mode 100644 index 0000000000..9f4ee97b0b --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts @@ -0,0 +1 @@ +export * from './QuantumResistantSetting'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts index 8b6d14ab5a..6f59813edc 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts @@ -1 +1 @@ -export * from './WireguardSettings'; +export * from './WireguardSettingsView'; |
