summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/renderer')
-rw-r--r--gui/src/renderer/app.tsx171
-rw-r--r--gui/src/renderer/components/Connect.tsx5
-rw-r--r--gui/src/renderer/components/SecuredLabel.tsx7
-rw-r--r--gui/src/renderer/components/TunnelControl.tsx2
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>