summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-09-06 12:42:26 +0300
committerAndrej Mihajlov <and@mullvad.net>2018-09-13 15:40:09 +0300
commitc2a547f0d07add5acd229954548a2f254801c605 (patch)
treee80670b9e610b5d87f7f6012f6d6955bba15ed49
parent7cb1310ef768141ed718f3ee0832b0303e4179c9 (diff)
downloadmullvadvpn-c2a547f0d07add5acd229954548a2f254801c605.tar.xz
mullvadvpn-c2a547f0d07add5acd229954548a2f254801c605.zip
Show banner when connection is blocked
-rw-r--r--gui/packages/desktop/src/renderer/components/BlockingInternetBanner.js70
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js152
-rw-r--r--gui/packages/desktop/src/renderer/components/ConnectStyles.js31
-rw-r--r--gui/packages/desktop/src/renderer/errors.js29
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.js6
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.js4
6 files changed, 182 insertions, 110 deletions
diff --git a/gui/packages/desktop/src/renderer/components/BlockingInternetBanner.js b/gui/packages/desktop/src/renderer/components/BlockingInternetBanner.js
new file mode 100644
index 0000000000..901d699fc5
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/components/BlockingInternetBanner.js
@@ -0,0 +1,70 @@
+// @flow
+
+import * as React from 'react';
+import { View, Text, Component, Styles } from 'reactxp';
+import { colors } from '../../config';
+
+const styles = {
+ container: Styles.createViewStyle({
+ flexDirection: 'row',
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ backdropFilter: 'blur(4px)',
+ paddingTop: 8,
+ paddingLeft: 20,
+ paddingRight: 20,
+ paddingBottom: 8,
+ }),
+ icon: Styles.createViewStyle({
+ width: 10,
+ height: 10,
+ flex: 0,
+ borderRadius: 5,
+ marginTop: 4,
+ marginRight: 8,
+ backgroundColor: colors.red,
+ }),
+ textContainer: Styles.createViewStyle({
+ flex: 1,
+ }),
+ title: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 12,
+ fontWeight: '800',
+ lineHeight: 17,
+ color: colors.white60,
+ }),
+ subtitle: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 12,
+ fontWeight: '800',
+ lineHeight: 17,
+ color: colors.white40,
+ }),
+};
+
+export class BannerTitle extends Component {
+ render() {
+ return <Text style={styles.title}>{this.props.children}</Text>;
+ }
+}
+
+export class BannerSubtitle extends Component {
+ render() {
+ return React.Children.count(this.props.children) > 0 ? (
+ <Text style={styles.subtitle}>{this.props.children}</Text>
+ ) : null;
+ }
+}
+
+export default class BlockingInternetBanner extends Component<{
+ children: Array<React.Element<typeof BannerTitle> | React.Element<typeof BannerSubtitle>>,
+}> {
+ render() {
+ return (
+ <View style={styles.container}>
+ <View style={styles.icon} />
+ <View style={styles.textContainer}>{this.props.children}</View>
+ </View>
+ );
+ }
+}
diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js
index 5849d71249..f04f33a492 100644
--- a/gui/packages/desktop/src/renderer/components/Connect.js
+++ b/gui/packages/desktop/src/renderer/components/Connect.js
@@ -6,12 +6,14 @@ import { Component, Clipboard, Text, View, Types } from 'reactxp';
import { Accordion } from '@mullvad/components';
import { Layout, Container, Header } from './Layout';
import { SettingsBarButton, Brand } from './HeaderBar';
+import BlockingInternetBanner, { BannerTitle, BannerSubtitle } from './BlockingInternetBanner';
import * as AppButton from './AppButton';
import Img from './Img';
import Map from './Map';
import styles from './ConnectStyles';
-import { BlockedError, NoCreditError, NoInternetError } from '../errors';
+import { NoCreditError, NoInternetError } from '../errors';
import WindowStateObserver from '../lib/window-state-observer';
+import type { BlockReason, TunnelState } from '../lib/daemon-rpc';
import type { HeaderBarStyle } from './HeaderBar';
import type { ConnectionReduxState } from '../redux/connection/reducers';
@@ -29,29 +31,50 @@ type Props = {
};
type State = {
+ banner: {
+ visible: boolean,
+ title: string,
+ subtitle: string,
+ },
showCopyIPMessage: boolean,
};
+function getBlockReasonMessage(reason: BlockReason): string {
+ switch (reason) {
+ case 'ipv6_unavailable':
+ return 'Could not configure IPv6, please enable it on your system or disable it in the app';
+ case 'set_security_policy_error':
+ return 'Failed to apply security policy';
+ case 'start_tunnel_error':
+ return 'Failed to start tunnel connection';
+ case 'no_matching_relay':
+ return 'No relay server matches the current settings';
+ default:
+ return `Unknown error: ${(reason: empty)}`;
+ }
+}
+
export default class Connect extends Component<Props, State> {
state = {
+ banner: {
+ visible: false,
+ title: '',
+ subtitle: '',
+ },
showCopyIPMessage: false,
};
_copyTimer: ?TimeoutID;
_windowStateObserver = new WindowStateObserver();
- shouldComponentUpdate(nextProps: Props, nextState: State) {
- const { connection: prevConnection, ...otherPrevProps } = this.props;
- const { connection: nextConnection, ...otherNextProps } = nextProps;
+ constructor(props: Props) {
+ super();
- const prevState = this.state;
-
- return (
- // shallow compare the connection
- !shallowCompare(prevConnection, nextConnection) ||
- !shallowCompare(otherPrevProps, otherNextProps) ||
- prevState.showCopyIPMessage !== nextState.showCopyIPMessage
- );
+ const connection = props.connection;
+ this.state = {
+ ...this.state,
+ banner: this.getBannerState(connection.status, connection.blockReason),
+ };
}
componentDidMount() {
@@ -70,6 +93,20 @@ export default class Connect extends Component<Props, State> {
this._windowStateObserver.dispose();
}
+ componentDidUpdate(oldProps: Props, _oldState: State) {
+ const oldConnection = oldProps.connection;
+ const newConnection = this.props.connection;
+
+ if (
+ oldConnection.status !== newConnection.status ||
+ oldConnection.blockReason !== newConnection.blockReason
+ ) {
+ this.setState({
+ banner: this.getBannerState(newConnection.status, newConnection.blockReason),
+ });
+ }
+ }
+
render() {
const error = this.checkForErrors();
const child = error ? this.renderError(error) : this.renderMap();
@@ -85,6 +122,33 @@ export default class Connect extends Component<Props, State> {
);
}
+ getBannerState(
+ tunnelState: TunnelState,
+ blockReason: ?BlockReason,
+ ): $PropertyType<State, 'banner'> {
+ switch (tunnelState) {
+ case 'connecting':
+ return {
+ visible: true,
+ title: 'BLOCKING INTERNET',
+ subtitle: '',
+ };
+
+ case 'blocked':
+ return {
+ visible: true,
+ title: 'BLOCKING INTERNET',
+ subtitle: blockReason ? getBlockReasonMessage(blockReason) : '',
+ };
+
+ default:
+ return {
+ ...this.state.banner,
+ visible: false,
+ };
+ }
+ }
+
renderError(error: Error) {
let title = '';
let message = '';
@@ -99,11 +163,6 @@ export default class Connect extends Component<Props, State> {
message = 'Your internet connection will be secured when you get back online';
}
- if (error instanceof BlockedError) {
- title = 'Blocked';
- message = error.message;
- }
-
return (
<View style={styles.connect}>
<View style={styles.status_icon}>
@@ -134,7 +193,7 @@ export default class Connect extends Component<Props, State> {
center: [longitude, latitude],
// do not show the marker when connecting
showMarker: status !== 'connecting',
- markerStyle: status === 'connected' ? 'secure' : 'unsecure',
+ markerStyle: status === 'connected' || status === 'blocked' ? 'secure' : 'unsecure',
// zoom in when connected
zoomLevel: status === 'connected' ? 'low' : 'medium',
// a magic offset to align marker with spinner
@@ -154,7 +213,13 @@ export default class Connect extends Component<Props, State> {
}
renderMap() {
- let [isConnecting, isConnected, isDisconnected, isDisconnecting] = [false, false, false, false];
+ let [isConnecting, isConnected, isDisconnected, isDisconnecting, isBlocked] = [
+ false,
+ false,
+ false,
+ false,
+ false,
+ ];
switch (this.props.connection.status) {
case 'connecting':
isConnecting = true;
@@ -168,6 +233,9 @@ export default class Connect extends Component<Props, State> {
case 'disconnecting':
isDisconnecting = true;
break;
+ case 'blocked':
+ isBlocked = true;
+ break;
}
return (
@@ -176,7 +244,15 @@ export default class Connect extends Component<Props, State> {
<Map style={{ width: '100%', height: '100%' }} {...this._getMapProps()} />
</View>
<View style={styles.container}>
- {this._renderIsBlockingInternetMessage()}
+ <Accordion
+ style={styles.blocking_container}
+ height={this.state.banner.visible ? 'auto' : 0}
+ testName={'blockingAccordion'}>
+ <BlockingInternetBanner>
+ <BannerTitle>{this.state.banner.title}</BannerTitle>
+ <BannerSubtitle>{this.state.banner.subtitle}</BannerSubtitle>
+ </BlockingInternetBanner>
+ </Accordion>
{/* show spinner when connecting */}
{isConnecting ? (
@@ -250,15 +326,15 @@ export default class Connect extends Component<Props, State> {
</View>
) : null}
- {/* footer when connecting */}
- {isConnecting ? (
+ {/* footer when connecting or blocked */}
+ {isConnecting || isBlocked ? (
<View style={styles.footer}>
<AppButton.TransparentButton
style={styles.switch_location_button}
onPress={this.props.onSelectLocation}>
{'Switch location'}
</AppButton.TransparentButton>
- <AppButton.RedTransparentButton onPress={this.props.onDisconnect}>
+ <AppButton.RedTransparentButton onPress={this.props.onDisconnect} testName="cancel">
{'Cancel'}
</AppButton.RedTransparentButton>
</View>
@@ -290,19 +366,6 @@ export default class Connect extends Component<Props, State> {
);
}
- _renderIsBlockingInternetMessage() {
- return (
- <Accordion
- style={styles.blocking_container}
- height={this.props.connection.status === 'connecting' ? 'auto' : 0}>
- <Text style={styles.blocking_message}>
- <Text style={styles.blocking_icon}>&nbsp;</Text>
- <Text>BLOCKING INTERNET</Text>
- </Text>
- </Accordion>
- );
- }
-
// Handlers
onExternalLink(type: string) {
@@ -341,9 +404,10 @@ export default class Connect extends Component<Props, State> {
networkSecurityStyle(): Types.Style {
const classes = [styles.status_security];
- if (this.props.connection.status === 'connected') {
+ const { status } = this.props.connection;
+ if (status === 'connected' || status === 'blocked') {
classes.push(styles.status_security__secure);
- } else if (this.props.connection.status === 'disconnected') {
+ } else if (status === 'disconnected' || status === 'disconnecting') {
classes.push(styles.status_security__unsecured);
}
return classes;
@@ -353,6 +417,8 @@ export default class Connect extends Component<Props, State> {
switch (this.props.connection.status) {
case 'connected':
return 'SECURE CONNECTION';
+ case 'blocked':
+ return 'BLOCKED CONNECTION';
case 'connecting':
return 'CREATING SECURE CONNECTION';
default:
@@ -380,16 +446,6 @@ export default class Connect extends Component<Props, State> {
return new NoCreditError();
}
- // Tunnel is blocked due to an error?
- if (this.props.connection.status === 'blocked') {
- return new BlockedError(this.props.connection.blockReason);
- }
-
return null;
}
}
-
-function shallowCompare(lhs: Object, rhs: Object) {
- const keys = Object.keys(lhs);
- return keys.length === Object.keys(rhs).length && keys.every((key) => lhs[key] === rhs[key]);
-}
diff --git a/gui/packages/desktop/src/renderer/components/ConnectStyles.js b/gui/packages/desktop/src/renderer/components/ConnectStyles.js
index 5417a64176..64633975fd 100644
--- a/gui/packages/desktop/src/renderer/components/ConnectStyles.js
+++ b/gui/packages/desktop/src/renderer/components/ConnectStyles.js
@@ -29,20 +29,6 @@ export default {
paddingLeft: 24,
paddingRight: 24,
}),
- blocking_container: Styles.createViewStyle({
- width: '100%',
- position: 'absolute',
- }),
- blocking_icon: Styles.createViewStyle({
- width: 10,
- height: 10,
- flex: 0,
- display: 'flex',
- borderRadius: 5,
- marginTop: 4,
- marginRight: 8,
- backgroundColor: colors.red,
- }),
status: Styles.createViewStyle({
paddingTop: 0,
paddingLeft: 24,
@@ -61,20 +47,9 @@ export default {
switch_location_button: Styles.createViewStyle({
marginBottom: 16,
}),
-
- blocking_message: Styles.createTextStyle({
- display: 'flex',
- flexDirection: 'row',
- fontFamily: 'Open Sans',
- fontSize: 12,
- fontWeight: '800',
- lineHeight: 17,
- paddingTop: 8,
- paddingLeft: 20,
- paddingRight: 20,
- paddingBottom: 8,
- color: colors.white60,
- backgroundColor: colors.blue,
+ blocking_container: Styles.createViewStyle({
+ width: '100%',
+ position: 'absolute',
}),
server_label: Styles.createTextStyle({
fontFamily: 'DINPro',
diff --git a/gui/packages/desktop/src/renderer/errors.js b/gui/packages/desktop/src/renderer/errors.js
index a6f103357a..67f87501dd 100644
--- a/gui/packages/desktop/src/renderer/errors.js
+++ b/gui/packages/desktop/src/renderer/errors.js
@@ -1,28 +1,5 @@
// @flow
-import type { BlockReason } from './lib/daemon-rpc';
-
-export class BlockedError extends Error {
- constructor(reason: BlockReason) {
- const message = (function() {
- switch (reason) {
- case 'ipv6_unavailable':
- return 'Could not configure IPv6, please enable it on your system or disable it in the app';
- case 'set_security_policy_error':
- return 'Failed to apply security policy';
- case 'start_tunnel_error':
- return 'Failed to start tunnel connection';
- case 'no_matching_relay':
- return 'No relay server matches the current settings';
- default:
- return `Unknown error: ${(reason: empty)}`;
- }
- })();
-
- super(message);
- }
-}
-
export class NoCreditError extends Error {
constructor() {
super("Account doesn't have enough credit available for connection");
@@ -47,12 +24,6 @@ export class InvalidAccountError extends Error {
}
}
-export class NoAccountError extends Error {
- constructor() {
- super('No account was set');
- }
-}
-
export class CommunicationError extends Error {
constructor() {
super('api.mullvad.net is blocked, please check your firewall');
diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.js b/gui/packages/desktop/src/renderer/redux/connection/actions.js
index fb920673e0..6a54a8926b 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/actions.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/actions.js
@@ -1,6 +1,6 @@
// @flow
-import type { Ip } from '../../lib/daemon-rpc';
+import type { BlockReason, Ip } from '../../lib/daemon-rpc';
type ConnectingAction = {
type: 'CONNECTING',
@@ -20,7 +20,7 @@ type DisconnectingAction = {
type BlockedAction = {
type: 'BLOCKED',
- reason: string,
+ reason: BlockReason,
};
type NewLocationAction = {
@@ -77,7 +77,7 @@ function disconnecting(): DisconnectingAction {
};
}
-function blocked(reason: string): BlockedAction {
+function blocked(reason: BlockReason): BlockedAction {
return {
type: 'BLOCKED',
reason,
diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.js b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
index 9381d5e037..82374dbb2c 100644
--- a/gui/packages/desktop/src/renderer/redux/connection/reducers.js
+++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.js
@@ -1,7 +1,7 @@
// @flow
import type { ReduxAction } from '../store';
-import type { TunnelState, Ip } from '../../lib/daemon-rpc';
+import type { BlockReason, TunnelState, Ip } from '../../lib/daemon-rpc';
export type ConnectionReduxState = {
status: TunnelState,
@@ -11,7 +11,7 @@ export type ConnectionReduxState = {
longitude: ?number,
country: ?string,
city: ?string,
- blockReason: ?string,
+ blockReason: ?BlockReason,
};
const initialState: ConnectionReduxState = {