diff options
Diffstat (limited to 'gui/src/renderer')
| -rw-r--r-- | gui/src/renderer/app.tsx | 171 | ||||
| -rw-r--r-- | gui/src/renderer/components/Connect.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/components/SecuredLabel.tsx | 7 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 2 |
4 files changed, 129 insertions, 56 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 28d2c8b480..dd8a6543ca 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Provider } from 'react-redux'; +import { batch, Provider } from 'react-redux'; import { Router } from 'react-router'; import { bindActionCreators } from 'redux'; @@ -116,14 +116,17 @@ export default class AppRenderer { }; private locale = 'en'; - private location?: ILocation; + private location?: Partial<ILocation>; + private lastDisconnectedLocation?: Partial<ILocation>; private relayListPair!: IRelayListPair; private tunnelState!: TunnelState; + private optimisticTunnelState?: TunnelState['state']; private settings!: ISettings; private guiSettings!: IGuiSettingsState; private doingLogin = false; private loginScheduler = new Scheduler(); private connectedToDaemon = false; + private getLocationPromise?: Promise<ILocation>; constructor() { log.addOutput(new ConsoleOutput(LogLevel.debug)); @@ -164,10 +167,6 @@ export default class AppRenderer { this.updateBlockedState(this.tunnelState, newSettings.blockWhenDisconnected); }); - IpcRendererEventChannel.location.listen((newLocation: ILocation) => { - this.setLocation(newLocation); - }); - IpcRendererEventChannel.relays.listen((relayListPair: IRelayListPair) => { this.setRelayListPair(relayListPair); }); @@ -231,10 +230,6 @@ export default class AppRenderer { this.setTunnelState(initialState.tunnelState); this.updateBlockedState(initialState.tunnelState, initialState.settings.blockWhenDisconnected); - if (initialState.location) { - this.setLocation(initialState.location); - } - this.setRelayListPair(initialState.relayListPair); this.setCurrentVersion(initialState.currentVersion); this.setUpgradeVersion(initialState.upgradeVersion); @@ -254,6 +249,8 @@ export default class AppRenderer { ); } + void this.updateLocation(); + const navigationBase = this.getNavigationBase( initialState.isConnected, initialState.settings.accountToken, @@ -327,38 +324,48 @@ export default class AppRenderer { } public async connectTunnel(): Promise<void> { - const state = this.tunnelState.state; + const state = this.optimisticTunnelState ?? this.tunnelState.state; // connect only if tunnel is disconnected or blocked. if (state === 'disconnecting' || state === 'disconnected' || state === 'error') { // switch to the connecting state ahead of time to make the app look more responsive - this.resetLocationToConstraints(); - this.reduxActions.connection.connecting(); + this.optimisticTunnelState = 'connecting'; + batch(() => { + void this.updateLocation({ state: 'connecting' }); + this.reduxActions.connection.connecting(); + }); return IpcRendererEventChannel.tunnel.connect(); } } public async disconnectTunnel(): Promise<void> { - const state = this.tunnelState.state; + const state = this.optimisticTunnelState ?? this.tunnelState.state; // disconnect only if tunnel is connected, connecting or blocked. if (state === 'connecting' || state === 'connected' || state === 'error') { // switch to the disconnecting state ahead of time to make the app look more responsive - this.reduxActions.connection.disconnecting('nothing'); + this.optimisticTunnelState = 'disconnecting'; + batch(() => { + void this.updateLocation({ state: 'disconnecting', details: 'nothing' }); + this.reduxActions.connection.disconnecting('nothing'); + }); return IpcRendererEventChannel.tunnel.disconnect(); } } public async reconnectTunnel(): Promise<void> { - const state = this.tunnelState.state; + const state = this.optimisticTunnelState ?? this.tunnelState.state; // reconnect only if tunnel is connected or connecting. if (state === 'connecting' || state === 'connected') { // switch to the connecting state ahead of time to make the app look more responsive - this.resetLocationToConstraints(); - this.reduxActions.connection.connecting(); + this.optimisticTunnelState = 'connecting'; + batch(() => { + void this.updateLocation({ state: 'connecting' }); + this.reduxActions.connection.connecting(); + }); return IpcRendererEventChannel.tunnel.reconnect(); } @@ -605,37 +612,6 @@ export default class AppRenderer { this.reduxActions.userInterface.updateLocale(locale); } - private resetLocationToConstraints() { - const relaySettings = this.settings.relaySettings; - if ('normal' in relaySettings) { - const location = relaySettings.normal.location; - if (location !== 'any' && 'only' in location) { - const constraint = location.only; - - const relayLocations = this.reduxStore.getState().settings.relayLocations; - if ('country' in constraint) { - const country = relayLocations.find(({ code }) => constraint.country === code); - - this.reduxActions.connection.newLocation({ country: country?.name }); - } else if ('city' in constraint) { - const country = relayLocations.find(({ code }) => constraint.city[0] === code); - const city = country?.cities.find(({ code }) => constraint.city[1] === code); - - this.reduxActions.connection.newLocation({ country: country?.name, city: city?.name }); - } else if ('hostname' in constraint) { - const country = relayLocations.find(({ code }) => constraint.hostname[0] === code); - const city = country?.cities.find((location) => location.code === constraint.hostname[1]); - - this.reduxActions.connection.newLocation({ - country: country?.name, - city: city?.name, - hostname: constraint.hostname[2], - }); - } - } - } - } - private setRelaySettings(relaySettings: RelaySettings) { const actions = this.reduxActions; @@ -777,6 +753,9 @@ export default class AppRenderer { actions.connection.blocked(tunnelState.details); break; } + + // Update the location when entering a new tunnel state since it's likely changed. + void this.updateLocation(); } private setSettings(newSettings: ISettings) { @@ -841,7 +820,7 @@ export default class AppRenderer { this.doingLogin = false; } - private setLocation(location: ILocation) { + private setLocation(location: Partial<ILocation>) { this.location = location; this.propagateLocationToRedux(); } @@ -852,7 +831,7 @@ export default class AppRenderer { } } - private translateLocation(inputLocation: ILocation): ILocation { + private translateLocation(inputLocation: Partial<ILocation>): Partial<ILocation> { const location = { ...inputLocation }; if (location.city) { @@ -933,4 +912,94 @@ export default class AppRenderer { private setWireguardPublicKey(publicKey?: IWireguardPublicKey) { this.reduxActions.settings.setWireguardKey(publicKey); } + + private async updateLocation(tunnelState = this.tunnelState) { + if ( + (tunnelState.state === 'disconnected' || tunnelState.state === 'disconnecting') && + this.lastDisconnectedLocation + ) { + // If we have a previous location for the disconnected state we use that when disconnecting and + // during the location fetch while disconnected. + this.setLocation(this.lastDisconnectedLocation); + } else if (tunnelState.state === 'disconnecting') { + // If there's no previous location while disconnecting we remove the location. We keep the + // coordinates to prevent the map from jumping around. + const { longitude, latitude } = this.reduxStore.getState().connection; + this.setLocation({ longitude, latitude }); + } + + if ( + (tunnelState.state === 'connected' || tunnelState.state === 'connecting') && + tunnelState.details?.location + ) { + this.setLocation(tunnelState.details.location); + } else if (tunnelState.state === 'connecting') { + this.setLocation(this.getLocationFromConstraints()); + } else if (tunnelState.state === 'connected' || tunnelState.state === 'disconnected') { + const location = await this.fetchLocation(); + if (location) { + this.setLocation(location); + // Cache the user location + if (tunnelState.state === 'disconnected') { + this.lastDisconnectedLocation = location; + } + } + } + } + + private async fetchLocation(): Promise<ILocation | void> { + try { + // Fetch the new user location + const getLocationPromise = IpcRendererEventChannel.location.get(); + this.getLocationPromise = getLocationPromise; + const location = await getLocationPromise; + // If the location is currently unavailable, do nothing! This only ever happens when a + // custom relay is set or we are in a blocked state. + if (location && getLocationPromise === this.getLocationPromise) { + return location; + } + } catch (error) { + log.error(`Failed to update the location: ${error.message}`); + } + } + + private getLocationFromConstraints(): Partial<ILocation> { + const state = this.reduxStore.getState(); + const coordinates = { + longitude: state.connection.longitude, + latitude: state.connection.latitude, + }; + + const relaySettings = this.settings.relaySettings; + if ('normal' in relaySettings) { + const location = relaySettings.normal.location; + if (location !== 'any' && 'only' in location) { + const constraint = location.only; + + const relayLocations = state.settings.relayLocations; + if ('country' in constraint) { + const country = relayLocations.find(({ code }) => constraint.country === code); + + return { country: country?.name, ...coordinates }; + } else if ('city' in constraint) { + const country = relayLocations.find(({ code }) => constraint.city[0] === code); + const city = country?.cities.find(({ code }) => constraint.city[1] === code); + + return { country: country?.name, city: city?.name, ...coordinates }; + } else if ('hostname' in constraint) { + const country = relayLocations.find(({ code }) => constraint.hostname[0] === code); + const city = country?.cities.find((location) => location.code === constraint.hostname[1]); + + return { + country: country?.name, + city: city?.name, + hostname: constraint.hostname[2], + ...coordinates, + }; + } + } + } + + return coordinates; + } } diff --git a/gui/src/renderer/components/Connect.tsx b/gui/src/renderer/components/Connect.tsx index e48abf8d96..a150da90f1 100644 --- a/gui/src/renderer/components/Connect.tsx +++ b/gui/src/renderer/components/Connect.tsx @@ -213,9 +213,6 @@ export default class Connect extends React.Component<IProps, IState> { private showMarkerOrSpinner(): MarkerOrSpinner { const status = this.props.connection.status; - return status.state === 'connecting' || - (status.state === 'disconnecting' && status.details === 'reconnect') - ? 'spinner' - : 'marker'; + return status.state === 'connecting' || status.state === 'disconnecting' ? 'spinner' : 'marker'; } } diff --git a/gui/src/renderer/components/SecuredLabel.tsx b/gui/src/renderer/components/SecuredLabel.tsx index 2d80991ed3..c00edc73de 100644 --- a/gui/src/renderer/components/SecuredLabel.tsx +++ b/gui/src/renderer/components/SecuredLabel.tsx @@ -8,11 +8,13 @@ export enum SecuredDisplayStyle { blocked, securing, unsecured, + unsecuring, failedToSecure, } const securedDisplayStyleColorMap = { [SecuredDisplayStyle.securing]: colors.white, + [SecuredDisplayStyle.unsecuring]: colors.white, [SecuredDisplayStyle.secured]: colors.green, [SecuredDisplayStyle.blocked]: colors.green, [SecuredDisplayStyle.unsecured]: colors.red, @@ -20,6 +22,8 @@ const securedDisplayStyleColorMap = { }; const StyledSecuredLabel = styled.span((props: { displayStyle: SecuredDisplayStyle }) => ({ + display: 'inline-block', + minHeight: '22px', color: securedDisplayStyleColorMap[props.displayStyle], })); @@ -50,6 +54,9 @@ function getLabelText(displayStyle: SecuredDisplayStyle) { case SecuredDisplayStyle.unsecured: return messages.gettext('UNSECURE CONNECTION'); + case SecuredDisplayStyle.unsecuring: + return ''; + case SecuredDisplayStyle.failedToSecure: return messages.gettext('FAILED TO SECURE CONNECTION'); } diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx index 016c360e4a..de3906c012 100644 --- a/gui/src/renderer/components/TunnelControl.tsx +++ b/gui/src/renderer/components/TunnelControl.tsx @@ -163,7 +163,7 @@ export default class TunnelControl extends React.Component<ITunnelControlProps> return ( <Wrapper> <Body> - <Secured displayStyle={SecuredDisplayStyle.secured} /> + <Secured displayStyle={SecuredDisplayStyle.unsecuring} /> <Location>{this.renderCountry()}</Location> </Body> <Footer> |
