import * as React from 'react'; import { Component, View } from 'reactxp'; import { sprintf } from 'sprintf-js'; import { colors } from '../../config.json'; import { RelayProtocol } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import styles from './AdvancedSettingsStyles'; import * as Cell from './Cell'; import { Container, Layout } from './Layout'; import { BackBarItem, NavigationBar, NavigationContainer, NavigationScrollbars, TitleBarItem, } from './NavigationBar'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; const MIN_MSSFIX_VALUE = 1000; const MAX_MSSFIX_VALUE = 1450; const PROTOCOLS: RelayProtocol[] = ['udp', 'tcp']; const UDP_PORTS = [1194, 1195, 1196, 1197, 1300, 1301, 1302]; const TCP_PORTS = [80, 443]; const PORT_ITEMS: { [key in RelayProtocol]: Array> } = { udp: UDP_PORTS.map(mapPortToSelectorItem), tcp: TCP_PORTS.map(mapPortToSelectorItem), }; const PROTOCOL_ITEMS: Array> = PROTOCOLS.map((value) => ({ label: value.toUpperCase(), value, })); function mapPortToSelectorItem(value: number): ISelectorItem { return { label: value.toString(), value }; } interface IProps { enableIpv6: boolean; blockWhenDisconnected: boolean; protocol?: RelayProtocol; mssfix?: number; port?: number; setEnableIpv6: (value: boolean) => void; setBlockWhenDisconnected: (value: boolean) => void; setOpenVpnMssfix: (value: number | undefined) => void; setRelayProtocolAndPort: (protocol?: RelayProtocol, port?: number) => void; onClose: () => void; } interface IState { persistedMssfix?: number; editedMssfix?: number; focusOnMssfix: boolean; } export default class AdvancedSettings extends Component { constructor(props: IProps) { super(props); this.state = { persistedMssfix: props.mssfix, editedMssfix: props.mssfix, focusOnMssfix: false, }; } public componentDidUpdate(_oldProps: IProps, _oldState: IState) { if (this.props.mssfix !== this.state.persistedMssfix) { this.setState((state, props) => ({ ...state, persistedMssfix: props.mssfix, editedMssfix: state.focusOnMssfix ? state.editedMssfix : props.mssfix, })); } } public render() { const mssfixStyle = this.mssfixIsValid() ? styles.advanced_settings__mssfix_valid_value : styles.advanced_settings__mssfix_invalid_value; const mssfixValue = this.state.editedMssfix; return ( {// TRANSLATORS: Back button in navigation bar messages.pgettext('advanced-settings-nav', 'Settings')} {// TRANSLATORS: Title label in navigation bar messages.pgettext('advanced-settings-nav', 'Advanced')} {messages.pgettext('advanced-settings-view', 'Advanced')} {messages.pgettext('advanced-settings-view', 'Enable IPv6')} {messages.pgettext( 'advanced-settings-view', 'Enable IPv6 communication through the tunnel.', )} {messages.pgettext('advanced-settings-view', 'Block when disconnected')} {messages.pgettext( 'advanced-settings-view', "Unless connected, always block all network traffic, even when you've disconnected or quit the app.", )} {this.props.protocol ? ( ) : ( undefined )} {messages.pgettext('advanced-settings-view', 'Mssfix')} {sprintf( // TRANSLATORS: The hint displayed below the Mssfix input field. // TRANSLATORS: Available placeholders: // TRANSLATORS: %(max)d - the maximum possible mssfix value // TRANSLATORS: %(min)d - the minimum possible mssfix value messages.pgettext( 'advanced-settings-view', 'Set OpenVPN MSS value. Valid range: %(min)d - %(max)d.', ), { min: MIN_MSSFIX_VALUE, max: MAX_MSSFIX_VALUE, }, )} ); } private onSelectProtocol = (protocol?: RelayProtocol) => { this.props.setRelayProtocolAndPort(protocol); }; private onSelectPort = (port?: number) => { this.props.setRelayProtocolAndPort(this.props.protocol, port); }; private onMssfixChange = (mssfixString: string) => { const mssfix = mssfixString.replace(/[^0-9]/g, ''); if (mssfix === '') { this.setState({ editedMssfix: undefined }); } else { this.setState({ editedMssfix: parseInt(mssfix, 10) }); } }; private onMssfixFocus = () => { this.setState({ focusOnMssfix: true }); }; private onMssfixBlur = () => { this.setState({ focusOnMssfix: false }); if (this.mssfixIsValid()) { this.props.setOpenVpnMssfix(this.state.editedMssfix); this.setState((state, _props) => ({ persistedMssfix: state.editedMssfix })); } }; private mssfixIsValid(): boolean { const mssfix = this.state.editedMssfix; return mssfix === undefined || (mssfix >= MIN_MSSFIX_VALUE && mssfix <= MAX_MSSFIX_VALUE); } } interface ISelectorItem { label: string; value: T; } interface ISelectorProps { title: string; values: Array>; value?: T; onSelect: (value?: T) => void; } class Selector extends Component> { public render() { return ( {this.props.title} {messages.pgettext('advanced-settings-view', 'Automatic')} {this.props.values.map((item, i) => ( {item.label} ))} ); } } interface ISelectorCell { value?: T; selected: boolean; onSelect: (value?: T) => void; children?: React.ReactText; } class SelectorCell extends Component> { public render() { return ( {this.props.children} ); } private onPress = () => { if (!this.props.selected) { this.props.onSelect(this.props.value); } }; }