import * as React from 'react'; import { sprintf } from 'sprintf-js'; import styled from 'styled-components'; import { IpVersion } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import * as AppButton from './AppButton'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import { Layout, SettingsContainer } from './Layout'; import { ModalAlert, ModalAlertType, ModalContainer } from './Modal'; import { BackBarItem, NavigationBar, NavigationContainer, NavigationItems, NavigationScrollbars, TitleBarItem, } from './NavigationBar'; import Selector, { ISelectorItem } from './cell/Selector'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; import Switch from './Switch'; const MIN_WIREGUARD_MTU_VALUE = 1280; const MAX_WIREGUARD_MTU_VALUE = 1420; const WIREUGARD_UDP_PORTS = [51820, 53]; type OptionalPort = number | undefined; type OptionalIpVersion = IpVersion | undefined; function mapPortToSelectorItem(value: number): ISelectorItem { return { label: value.toString(), value }; } export const StyledNavigationScrollbars = styled(NavigationScrollbars)({ flex: 1, }); export const StyledSelectorContainer = styled.div({ flex: 0, }); export const StyledSelectorForFooter = (styled(Selector)({ marginBottom: 0, }) as unknown) as new () => Selector; export const StyledInputFrame = styled(Cell.InputFrame)({ flex: 0, }); interface IProps { wireguard: { port?: number; ipVersion?: IpVersion }; wireguardMtu?: number; wireguardMultihop: boolean; setWireguardMtu: (value: number | undefined) => void; setWireguardMultihop: (value: boolean) => void; setWireguardPort: (port?: number) => void; setWireguardIpVersion: (ipVersion?: IpVersion) => void; onViewWireguardKeys: () => void; onClose: () => void; } interface IState { showMultihopConfirmationDialog: boolean; } export default class WireguardSettings extends React.Component { public state = { showMultihopConfirmationDialog: false }; private multihopRef = React.createRef(); private wireguardPortItems: Array>; private wireguardIpVersionItems: Array>; constructor(props: IProps) { super(props); const automaticPort: ISelectorItem = { label: messages.gettext('Automatic'), value: undefined, }; this.wireguardPortItems = [automaticPort].concat( WIREUGARD_UDP_PORTS.map(mapPortToSelectorItem), ); this.wireguardIpVersionItems = [ { label: messages.gettext('Automatic'), value: undefined, }, { label: messages.gettext('IPv4'), value: 'ipv4', }, { label: messages.gettext('IPv6'), value: 'ipv6', }, ]; } public render() { return ( { // TRANSLATORS: Back button in navigation bar messages.pgettext('navigation-bar', 'Advanced') } { // TRANSLATORS: Title label in navigation bar messages.pgettext('wireguard-settings-nav', 'WireGuard settings') } {messages.pgettext('wireguard-settings-view', 'WireGuard settings')} { // TRANSLATORS: The hint displayed below the WireGuard port selector. messages.pgettext( 'wireguard-settings-view', 'The automatic setting will randomly choose from a wide range of ports.', ) } { // TRANSLATORS: The label next to the multihop settings toggle. messages.pgettext('advanced-settings-view', 'Enable multihop') } { // TRANSLATORS: Description for multihop settings toggle. messages.pgettext( 'advanced-settings-view', 'Increases anonymity by routing your traffic into one WireGuard server and out another, making it harder to trace.', ) } { // TRANSLATORS: The hint displayed below the WireGuard IP version selector. messages.pgettext( 'wireguard-settings-view', 'This allows access to WireGuard for devices that only support IPv6.', ) } {messages.pgettext('wireguard-settings-view', 'WireGuard key')} {messages.pgettext('wireguard-settings-view', 'MTU')} {sprintf( // TRANSLATORS: The hint displayed below the WireGuard MTU input field. // TRANSLATORS: Available placeholders: // 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 MTU value. Valid range: %(min)d - %(max)d.', ), { min: MIN_WIREGUARD_MTU_VALUE, max: MAX_WIREGUARD_MTU_VALUE, }, )} {this.state.showMultihopConfirmationDialog && this.renderMultihopConfirmation()} ); } private static removeNonNumericCharacters(value: string) { return value.replace(/[^0-9]/g, ''); } private onWireguardMtuSubmit = (value: string) => { const parsedValue = value === '' ? undefined : parseInt(value, 10); if (WireguardSettings.wireguarMtuIsValid(value)) { this.props.setWireguardMtu(parsedValue); } }; private static wireguarMtuIsValid(mtu: string): boolean { const parsedMtu = mtu ? parseInt(mtu) : undefined; return ( parsedMtu === undefined || (parsedMtu >= MIN_WIREGUARD_MTU_VALUE && parsedMtu <= MAX_WIREGUARD_MTU_VALUE) ); } private renderMultihopConfirmation = () => { return ( {messages.gettext('Enable anyway')} , {messages.gettext('Back')} , ]} close={this.hideWireguardMultihopConfirmationDialog}> ); }; private setWireguardMultihop = (newValue: boolean) => { if (newValue) { this.setState({ showMultihopConfirmationDialog: true }); } else { this.props.setWireguardMultihop(false); } }; private hideWireguardMultihopConfirmationDialog = () => { this.setState({ showMultihopConfirmationDialog: false }); this.multihopRef.current?.setOn(this.props.wireguardMultihop); }; private confirmWireguardMultihop = () => { this.setState({ showMultihopConfirmationDialog: false }); this.props.setWireguardMultihop(true); }; }