import { connect } from 'react-redux'; import BridgeSettingsBuilder from '../../shared/bridge-settings-builder'; import { LiftedConstraint, RelayLocation } from '../../shared/daemon-rpc-types'; import log from '../../shared/logging'; import RelaySettingsBuilder from '../../shared/relay-settings-builder'; import SelectLocation from '../components/SelectLocation'; import withAppContext, { IAppContext } from '../context'; import { createWireguardRelayUpdater } from '../lib/constraint-updater'; import { IHistoryProps, withHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; import { IRelayLocationRedux } from '../redux/settings/reducers'; import { IReduxState, ReduxDispatch } from '../redux/store'; const mapStateToProps = (state: IReduxState, props: IHistoryProps & IAppContext) => { let selectedExitLocation: RelayLocation | undefined; let selectedEntryLocation: RelayLocation | undefined; let selectedBridgeLocation: LiftedConstraint | undefined; let multihopEnabled = false; if ('normal' in state.settings.relaySettings) { const exitLocation = state.settings.relaySettings.normal.location; if (exitLocation !== 'any') { selectedExitLocation = exitLocation; } } const relaySettings = state.settings.relaySettings; const tunnelProtocol = 'normal' in relaySettings ? relaySettings.normal.tunnelProtocol : 'any'; if (tunnelProtocol === 'openvpn' && 'normal' in state.settings.bridgeSettings) { selectedBridgeLocation = state.settings.bridgeSettings.normal.location; } else if ('normal' in relaySettings) { multihopEnabled = relaySettings.normal.wireguard.useMultihop; const entryLocation = relaySettings.normal.wireguard.entryLocation; if (multihopEnabled && entryLocation !== 'any') { selectedEntryLocation = entryLocation; } } const allowEntrySelection = (tunnelProtocol === 'openvpn' && state.settings.bridgeState === 'on') || ((tunnelProtocol === 'any' || tunnelProtocol === 'wireguard') && multihopEnabled); const providers = 'normal' in relaySettings ? relaySettings.normal.providers : []; return { locale: state.userInterface.locale, selectedExitLocation, selectedEntryLocation, selectedBridgeLocation, relayLocations: filterLocationsByProvider(state.settings.relayLocations, providers), bridgeLocations: filterLocationsByProvider(state.settings.bridgeLocations, providers), allowEntrySelection, tunnelProtocol, providers, onSelectEntryLocation: async (entryLocation: RelayLocation) => { // dismiss the view first props.history.dismiss(); const relayUpdate = createWireguardRelayUpdater(state.settings.relaySettings) .tunnel.wireguard((wireguard) => wireguard.entryLocation.exact(entryLocation)) .build(); try { await props.app.updateRelaySettings(relayUpdate); } catch (e) { const error = e as Error; log.error('Failed to select the entry location', error.message); } }, }; }; const mapDispatchToProps = (_dispatch: ReduxDispatch, props: IHistoryProps & IAppContext) => { return { onClose: () => props.history.dismiss(), onViewFilterByProvider: () => props.history.push(RoutePath.filterByProvider), onSelectExitLocation: async (relayLocation: RelayLocation) => { // dismiss the view first props.history.dismiss(); try { const relayUpdate = RelaySettingsBuilder.normal().location.fromRaw(relayLocation).build(); await props.app.updateRelaySettings(relayUpdate); await props.app.connectTunnel(); } catch (e) { const error = e as Error; log.error(`Failed to select the exit location: ${error.message}`); } }, onSelectBridgeLocation: async (bridgeLocation: RelayLocation) => { // dismiss the view first props.history.dismiss(); try { await props.app.updateBridgeSettings( new BridgeSettingsBuilder().location.fromRaw(bridgeLocation).build(), ); } catch (e) { const error = e as Error; log.error(`Failed to select the bridge location: ${error.message}`); } }, onSelectClosestToExit: async () => { // dismiss the view first props.history.dismiss(); try { await props.app.updateBridgeSettings(new BridgeSettingsBuilder().location.any().build()); } catch (e) { const error = e as Error; log.error(`Failed to set the bridge location to closest to exit: ${error.message}`); } }, onClearProviders: async () => { await props.app.updateRelaySettings({ normal: { providers: [] } }); }, }; }; function filterLocationsByProvider( locations: IRelayLocationRedux[], providers: string[], ): IRelayLocationRedux[] { return providers.length === 0 ? locations : locations .map((country) => ({ ...country, cities: country.cities .map((city) => ({ ...city, relays: city.relays.filter((relay) => providers.includes(relay.provider)), })) .filter((city) => city.relays.length > 0), })) .filter((country) => country.cities.length > 0); } export default withAppContext( withHistory(connect(mapStateToProps, mapDispatchToProps)(SelectLocation)), );