diff options
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/AdvancedSettings.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/OpenVPNSettings.tsx | 83 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardSettings.tsx | 344 | ||||
| -rw-r--r-- | gui/src/renderer/containers/OpenVPNSettingsPage.tsx | 14 | ||||
| -rw-r--r-- | gui/src/renderer/containers/WireguardSettingsPage.tsx | 82 |
5 files changed, 337 insertions, 188 deletions
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx index f511265464..369f5169a2 100644 --- a/gui/src/renderer/components/AdvancedSettings.tsx +++ b/gui/src/renderer/components/AdvancedSettings.tsx @@ -236,7 +236,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> { type={ModalAlertType.caution} buttons={[ <AppButton.RedButton key="confirm" onClick={this.confirmEnableBlockWhenDisconnected}> - {messages.pgettext('advanced-settings-view', 'Enable anyway')} + {messages.gettext('Enable anyway')} </AppButton.RedButton>, <AppButton.BlueButton key="back" onClick={this.hideConfirmBlockWhenDisconnectedAlert}> {messages.gettext('Back')} diff --git a/gui/src/renderer/components/OpenVPNSettings.tsx b/gui/src/renderer/components/OpenVPNSettings.tsx index 72e672b681..6812cea9aa 100644 --- a/gui/src/renderer/components/OpenVPNSettings.tsx +++ b/gui/src/renderer/components/OpenVPNSettings.tsx @@ -17,6 +17,7 @@ import { } from './NavigationBar'; import Selector, { ISelectorItem } from './cell/Selector'; import SettingsHeader, { HeaderTitle } from './SettingsHeader'; +import { formatMarkdown } from '../markdown-formatter'; const MIN_MSSFIX_VALUE = 1000; const MAX_MSSFIX_VALUE = 1450; @@ -43,7 +44,12 @@ export const StyledInputFrame = styled(Cell.InputFrame)({ flex: 0, }); +export const StyledSelectorForFooter = (styled(Selector)({ + marginBottom: 0, +}) as unknown) as new <T>() => Selector<T>; + interface IProps { + tunnelProtocolIsOpenVpn: boolean; openvpn: { protocol?: RelayProtocol; port?: number; @@ -59,7 +65,6 @@ interface IProps { export default class OpenVpnSettings extends React.Component<IProps> { private portItems: { [key in RelayProtocol]: Array<ISelectorItem<OptionalPort>> }; private protocolItems: Array<ISelectorItem<OptionalRelayProtocol>>; - private bridgeStateItems: Array<ISelectorItem<BridgeState>>; constructor(props: IProps) { super(props); @@ -88,21 +93,6 @@ export default class OpenVpnSettings extends React.Component<IProps> { value: 'udp', }, ]; - - this.bridgeStateItems = [ - { - label: messages.gettext('Automatic'), - value: 'auto', - }, - { - label: messages.gettext('On'), - value: 'on', - }, - { - label: messages.gettext('Off'), - value: 'off', - }, - ]; } public render() { @@ -164,15 +154,40 @@ export default class OpenVpnSettings extends React.Component<IProps> { </AriaInputGroup> <AriaInputGroup> - <Selector - title={ - // TRANSLATORS: The title for the shadowsocks bridge selector section. - messages.pgettext('openvpn-settings-view', 'Bridge mode') - } - values={this.bridgeStateItems} - value={this.props.bridgeState} - onSelect={this.onSelectBridgeState} - /> + <StyledSelectorContainer> + <StyledSelectorForFooter + title={ + // TRANSLATORS: The title for the shadowsocks bridge selector section. + messages.pgettext('openvpn-settings-view', 'Bridge mode') + } + values={this.bridgeStateItems(this.props.tunnelProtocolIsOpenVpn)} + value={this.props.bridgeState} + onSelect={this.onSelectBridgeState} + /> + </StyledSelectorContainer> + <Cell.Footer> + <AriaDescription> + <Cell.FooterText> + {this.props.tunnelProtocolIsOpenVpn + ? // This line is here to prevent prettier from moving up the next line. + // TRANSLATORS: This is used as a description for the bridge mode + // TRANSLATORS: setting. + messages.pgettext( + 'openvpn-settings-view', + 'Helps circumvent censorship, by routing your traffic through a bridge server before reaching an OpenVPN server. Obfuscation is added to make fingerprinting harder.', + ) + : // This line is here to prevent prettier from moving up the next line. + // TRANSLATORS: This is used to instruct users how to make the bridge + // TRANSLATORS: mode setting available. + formatMarkdown( + messages.pgettext( + 'wireguard-settings-view', + 'To activate Bridge mode, go back and change **Tunnel protocol** to **OpenVPN**.', + ), + )} + </Cell.FooterText> + </AriaDescription> + </Cell.Footer> </AriaInputGroup> <AriaInputGroup> @@ -226,6 +241,24 @@ export default class OpenVpnSettings extends React.Component<IProps> { ); } + private bridgeStateItems(onAvailable: boolean): Array<ISelectorItem<BridgeState>> { + return [ + { + label: messages.gettext('Automatic'), + value: 'auto', + }, + { + label: messages.gettext('On'), + value: 'on', + disabled: !onAvailable, + }, + { + label: messages.gettext('Off'), + value: 'off', + }, + ]; + } + private onSelectOpenvpnProtocol = (protocol?: RelayProtocol) => { this.props.setOpenVpnRelayProtocolAndPort(protocol); }; diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 8bd379d7d4..80e916fe1b 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -3,9 +3,11 @@ 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, @@ -16,6 +18,7 @@ import { } 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; @@ -47,13 +50,24 @@ export const StyledInputFrame = styled(Cell.InputFrame)({ interface IProps { wireguard: { port?: number; ipVersion?: IpVersion }; wireguardMtu?: number; + wireguardMultihop: boolean; setWireguardMtu: (value: number | undefined) => void; - setWireguardRelayPortAndIpVersion: (port?: number, ipVersion?: IpVersion) => void; + setWireguardMultihop: (value: boolean) => void; + setWireguardPort: (port?: number) => void; + setWireguardIpVersion: (ipVersion?: IpVersion) => void; onViewWireguardKeys: () => void; onClose: () => void; } -export default class WireguardSettings extends React.Component<IProps> { +interface IState { + showMultihopConfirmationDialog: boolean; +} + +export default class WireguardSettings extends React.Component<IProps, IState> { + public state = { showMultihopConfirmationDialog: false }; + + private multihopRef = React.createRef<Switch>(); + private wireguardPortItems: Array<ISelectorItem<OptionalPort>>; private wireguardIpVersionItems: Array<ISelectorItem<OptionalIpVersion>>; @@ -87,149 +101,172 @@ export default class WireguardSettings extends React.Component<IProps> { public render() { return ( - <Layout> - <SettingsContainer> - <NavigationContainer> - <NavigationBar> - <NavigationItems> - <BackBarItem action={this.props.onClose}> - { - // TRANSLATORS: Back button in navigation bar - messages.pgettext('navigation-bar', 'Advanced') - } - </BackBarItem> - <TitleBarItem> - { - // TRANSLATORS: Title label in navigation bar - messages.pgettext('wireguard-settings-nav', 'WireGuard settings') - } - </TitleBarItem> - </NavigationItems> - </NavigationBar> + <ModalContainer> + <Layout> + <SettingsContainer> + <NavigationContainer> + <NavigationBar> + <NavigationItems> + <BackBarItem action={this.props.onClose}> + { + // TRANSLATORS: Back button in navigation bar + messages.pgettext('navigation-bar', 'Advanced') + } + </BackBarItem> + <TitleBarItem> + { + // TRANSLATORS: Title label in navigation bar + messages.pgettext('wireguard-settings-nav', 'WireGuard settings') + } + </TitleBarItem> + </NavigationItems> + </NavigationBar> - <StyledNavigationScrollbars> - <SettingsHeader> - <HeaderTitle> - {messages.pgettext('wireguard-settings-view', 'WireGuard settings')} - </HeaderTitle> - </SettingsHeader> + <StyledNavigationScrollbars> + <SettingsHeader> + <HeaderTitle> + {messages.pgettext('wireguard-settings-view', 'WireGuard settings')} + </HeaderTitle> + </SettingsHeader> - <AriaInputGroup> - <StyledSelectorContainer> - <StyledSelectorForFooter - // TRANSLATORS: The title for the WireGuard port selector. - title={messages.pgettext('wireguard-settings-view', 'Port')} - values={this.wireguardPortItems} - value={this.props.wireguard.port} - onSelect={this.onSelectWireguardPort} - /> - </StyledSelectorContainer> - <Cell.Footer> - <AriaDescription> - <Cell.FooterText> - { - // 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.', - ) - } - </Cell.FooterText> - </AriaDescription> - </Cell.Footer> - </AriaInputGroup> - - <AriaInputGroup> - <StyledSelectorContainer> - <StyledSelectorForFooter - // TRANSLATORS: The title for the WireGuard IP version selector. - title={messages.pgettext('wireguard-settings-view', 'IP version')} - values={this.wireguardIpVersionItems} - value={this.props.wireguard.ipVersion} - onSelect={this.onSelectWireguardIpVersion} - /> - </StyledSelectorContainer> - <Cell.Footer> - <AriaDescription> - <Cell.FooterText> - { - // 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.', - ) - } - </Cell.FooterText> - </AriaDescription> - </Cell.Footer> - </AriaInputGroup> - - <Cell.CellButtonGroup> - <Cell.CellButton onClick={this.props.onViewWireguardKeys}> - <Cell.Label> - {messages.pgettext('wireguard-settings-view', 'WireGuard key')} - </Cell.Label> - <Cell.Icon height={12} width={7} source="icon-chevron" /> - </Cell.CellButton> - </Cell.CellButtonGroup> + <AriaInputGroup> + <StyledSelectorContainer> + <StyledSelectorForFooter + // TRANSLATORS: The title for the WireGuard port selector. + title={messages.pgettext('wireguard-settings-view', 'Port')} + values={this.wireguardPortItems} + value={this.props.wireguard.port} + onSelect={this.props.setWireguardPort} + /> + </StyledSelectorContainer> + <Cell.Footer> + <AriaDescription> + <Cell.FooterText> + { + // 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.', + ) + } + </Cell.FooterText> + </AriaDescription> + </Cell.Footer> + </AriaInputGroup> - <AriaInputGroup> - <Cell.Container> - <AriaLabel> - <Cell.InputLabel> - {messages.pgettext('wireguard-settings-view', 'MTU')} - </Cell.InputLabel> - </AriaLabel> - <StyledInputFrame> + <AriaInputGroup> + <Cell.Container> + <AriaLabel> + <Cell.InputLabel> + {messages.pgettext('advanced-settings-view', 'Enable multihop')} + </Cell.InputLabel> + </AriaLabel> <AriaInput> - <Cell.AutoSizingTextInput - value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''} - inputMode={'numeric'} - maxLength={4} - placeholder={messages.gettext('Default')} - onSubmitValue={this.onWireguardMtuSubmit} - validateValue={WireguardSettings.wireguarMtuIsValid} - submitOnBlur={true} - modifyValue={WireguardSettings.removeNonNumericCharacters} + <Cell.Switch + ref={this.multihopRef} + isOn={this.props.wireguardMultihop} + onChange={this.setWireguardMultihop} /> </AriaInput> - </StyledInputFrame> - </Cell.Container> - <Cell.Footer> - <AriaDescription> - <Cell.FooterText> - {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.', - ), + </Cell.Container> + <Cell.Footer> + <AriaDescription> + <Cell.FooterText> + {messages.pgettext( + 'advanced-settings-view', + 'Increases anonymity by routing your traffic into one WireGuard server and out another, making it harder to trace.', + )} + </Cell.FooterText> + </AriaDescription> + </Cell.Footer> + </AriaInputGroup> + + <AriaInputGroup> + <StyledSelectorContainer> + <StyledSelectorForFooter + // TRANSLATORS: The title for the WireGuard IP version selector. + title={messages.pgettext('wireguard-settings-view', 'IP version')} + values={this.wireguardIpVersionItems} + value={this.props.wireguard.ipVersion} + onSelect={this.props.setWireguardIpVersion} + /> + </StyledSelectorContainer> + <Cell.Footer> + <AriaDescription> + <Cell.FooterText> { - min: MIN_WIREGUARD_MTU_VALUE, - max: MAX_WIREGUARD_MTU_VALUE, - }, - )} - </Cell.FooterText> - </AriaDescription> - </Cell.Footer> - </AriaInputGroup> - </StyledNavigationScrollbars> - </NavigationContainer> - </SettingsContainer> - </Layout> - ); - } + // 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.', + ) + } + </Cell.FooterText> + </AriaDescription> + </Cell.Footer> + </AriaInputGroup> - private onSelectWireguardPort = (port?: number) => { - this.props.setWireguardRelayPortAndIpVersion(port, this.props.wireguard.ipVersion); - }; + <Cell.CellButtonGroup> + <Cell.CellButton onClick={this.props.onViewWireguardKeys}> + <Cell.Label> + {messages.pgettext('wireguard-settings-view', 'WireGuard key')} + </Cell.Label> + <Cell.Icon height={12} width={7} source="icon-chevron" /> + </Cell.CellButton> + </Cell.CellButtonGroup> - private onSelectWireguardIpVersion = (ipVersion?: IpVersion) => { - this.props.setWireguardRelayPortAndIpVersion(this.props.wireguard.port, ipVersion); - }; + <AriaInputGroup> + <Cell.Container> + <AriaLabel> + <Cell.InputLabel> + {messages.pgettext('wireguard-settings-view', 'MTU')} + </Cell.InputLabel> + </AriaLabel> + <StyledInputFrame> + <AriaInput> + <Cell.AutoSizingTextInput + value={this.props.wireguardMtu ? this.props.wireguardMtu.toString() : ''} + inputMode={'numeric'} + maxLength={4} + placeholder={messages.gettext('Default')} + onSubmitValue={this.onWireguardMtuSubmit} + validateValue={WireguardSettings.wireguarMtuIsValid} + submitOnBlur={true} + modifyValue={WireguardSettings.removeNonNumericCharacters} + /> + </AriaInput> + </StyledInputFrame> + </Cell.Container> + <Cell.Footer> + <AriaDescription> + <Cell.FooterText> + {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, + }, + )} + </Cell.FooterText> + </AriaDescription> + </Cell.Footer> + </AriaInputGroup> + </StyledNavigationScrollbars> + </NavigationContainer> + </SettingsContainer> + </Layout> + + {this.state.showMultihopConfirmationDialog && this.renderMultihopConfirmation()} + </ModalContainer> + ); + } private static removeNonNumericCharacters(value: string) { return value.replace(/[^0-9]/g, ''); @@ -249,4 +286,39 @@ export default class WireguardSettings extends React.Component<IProps> { (parsedMtu >= MIN_WIREGUARD_MTU_VALUE && parsedMtu <= MAX_WIREGUARD_MTU_VALUE) ); } + + private renderMultihopConfirmation = () => { + return ( + <ModalAlert + type={ModalAlertType.info} + message={messages.gettext('This setting increases latency. Use only if needed.')} + buttons={[ + <AppButton.RedButton key="confirm" onClick={this.confirmWireguardMultihop}> + {messages.gettext('Enable anyway')} + </AppButton.RedButton>, + <AppButton.BlueButton key="back" onClick={this.hideWireguardMultihopConfirmationDialog}> + {messages.gettext('Back')} + </AppButton.BlueButton>, + ]} + close={this.hideWireguardMultihopConfirmationDialog}></ModalAlert> + ); + }; + + 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); + }; } diff --git a/gui/src/renderer/containers/OpenVPNSettingsPage.tsx b/gui/src/renderer/containers/OpenVPNSettingsPage.tsx index 9212ca68ac..9885afe971 100644 --- a/gui/src/renderer/containers/OpenVPNSettingsPage.tsx +++ b/gui/src/renderer/containers/OpenVPNSettingsPage.tsx @@ -13,12 +13,26 @@ const mapStateToProps = (state: IReduxState) => { const protocolAndPort = mapRelaySettingsToProtocolAndPort(state.settings.relaySettings); return { + tunnelProtocolIsOpenVpn: mapRelaySettingsToProtocol(state.settings.relaySettings) === 'openvpn', mssfix: state.settings.openVpn.mssfix, bridgeState: state.settings.bridgeState, ...protocolAndPort, }; }; +const mapRelaySettingsToProtocol = (relaySettings: RelaySettingsRedux) => { + if ('normal' in relaySettings) { + const { tunnelProtocol } = relaySettings.normal; + return tunnelProtocol === 'any' ? undefined : tunnelProtocol; + // since the GUI doesn't display custom settings, just display the default ones. + // If the user sets any settings, then those will be applied. + } else if ('customTunnelEndpoint' in relaySettings) { + return undefined; + } else { + throw new Error('Unknown type of relay settings.'); + } +}; + const mapRelaySettingsToProtocolAndPort = (relaySettings: RelaySettingsRedux) => { if ('normal' in relaySettings) { const { openvpn } = relaySettings.normal; diff --git a/gui/src/renderer/containers/WireguardSettingsPage.tsx b/gui/src/renderer/containers/WireguardSettingsPage.tsx index 5e6c27105e..4b0af37510 100644 --- a/gui/src/renderer/containers/WireguardSettingsPage.tsx +++ b/gui/src/renderer/containers/WireguardSettingsPage.tsx @@ -1,21 +1,75 @@ import { connect } from 'react-redux'; import { IpVersion } from '../../shared/daemon-rpc-types'; import log from '../../shared/logging'; -import RelaySettingsBuilder from '../../shared/relay-settings-builder'; import WireguardSettings from '../components/WireguardSettings'; import withAppContext, { IAppContext } from '../context'; +import { createWireguardRelayUpdater } from '../lib/constraint-updater'; import { IHistoryProps, withHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { RelaySettingsRedux } from '../redux/settings/reducers'; import { IReduxState, ReduxDispatch } from '../redux/store'; -const mapStateToProps = (state: IReduxState) => { +const mapStateToProps = (state: IReduxState, props: IAppContext) => { const protocolAndPort = mapRelaySettingsToProtocolAndPort(state.settings.relaySettings); + let wireguardMultihop = false; + if ('normal' in state.settings.relaySettings) { + wireguardMultihop = state.settings.relaySettings.normal.wireguard.useMultihop; + } + return { wireguardMtu: state.settings.wireguard.mtu, + wireguardMultihop, ...protocolAndPort, + + setWireguardPort: async (port?: number) => { + const relayUpdate = createWireguardRelayUpdater(state.settings.relaySettings) + .tunnel.wireguard((wireguard) => { + if (port) { + wireguard.port.exact(port); + } else { + wireguard.port.any(); + } + }) + .build(); + try { + await props.app.updateRelaySettings(relayUpdate); + } catch (e) { + const error = e as Error; + log.error('Failed to update relay settings', error.message); + } + }, + + setWireguardIpVersion: async (ipVersion?: IpVersion) => { + const relayUpdate = createWireguardRelayUpdater(state.settings.relaySettings) + .tunnel.wireguard((wireguard) => { + if (ipVersion) { + wireguard.ipVersion.exact(ipVersion); + } else { + wireguard.ipVersion.any(); + } + }) + .build(); + try { + await props.app.updateRelaySettings(relayUpdate); + } catch (e) { + const error = e as Error; + log.error('Failed to update relay settings', error.message); + } + }, + + setWireguardMultihop: async (enabled: boolean) => { + const relayUpdate = createWireguardRelayUpdater(state.settings.relaySettings) + .tunnel.wireguard((wireguard) => wireguard.useMultihop(enabled)) + .build(); + try { + await props.app.updateRelaySettings(relayUpdate); + } catch (e) { + const error = e as Error; + log.error('Failed to update WireGuard multihop settings', error.message); + } + }, }; }; @@ -46,30 +100,6 @@ const mapDispatchToProps = (_dispatch: ReduxDispatch, props: IHistoryProps & IAp props.history.pop(); }, - setWireguardRelayPortAndIpVersion: async (port?: number, ipVersion?: IpVersion) => { - const relayUpdate = RelaySettingsBuilder.normal() - .tunnel.wireguard((wireguard) => { - if (port) { - wireguard.port.exact(port); - } else { - wireguard.port.any(); - } - - if (ipVersion) { - wireguard.ipVersion.exact(ipVersion); - } else { - wireguard.ipVersion.any(); - } - }) - .build(); - try { - await props.app.updateRelaySettings(relayUpdate); - } catch (e) { - const error = e as Error; - log.error('Failed to update relay settings', error.message); - } - }, - setWireguardMtu: async (mtu?: number) => { try { await props.app.setWireguardMtu(mtu); |
