summaryrefslogtreecommitdiffhomepage
path: root/app
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-01-03 12:43:36 +0100
committerAndrej Mihajlov <and@mullvad.net>2018-01-03 12:43:36 +0100
commitd19f790931beca0dcc62ae84f16d45b74d69a9c8 (patch)
tree42f2ddc38820c72517d1598835da718b583d07d9 /app
parentd9f182e02742f9266941e4263e3e8f9cd92f6d32 (diff)
parent9c1589392d08c31bd4cc95f1e727c705bfb6900a (diff)
downloadmullvadvpn-d19f790931beca0dcc62ae84f16d45b74d69a9c8.tar.xz
mullvadvpn-d19f790931beca0dcc62ae84f16d45b74d69a9c8.zip
Merge branch 'add-lan-sharing'
Diffstat (limited to 'app')
-rw-r--r--app/components/Preferences.js57
-rw-r--r--app/components/PreferencesStyles.js103
-rw-r--r--app/components/Settings.js18
-rw-r--r--app/containers/PreferencesPage.js26
-rw-r--r--app/containers/SettingsPage.js1
-rw-r--r--app/lib/backend.js30
-rw-r--r--app/lib/ipc-facade.js32
-rw-r--r--app/redux/settings/actions.js20
-rw-r--r--app/redux/settings/reducers.js7
-rw-r--r--app/routes.js4
-rw-r--r--app/transitions.js3
11 files changed, 283 insertions, 18 deletions
diff --git a/app/components/Preferences.js b/app/components/Preferences.js
new file mode 100644
index 0000000000..f0b65d4a06
--- /dev/null
+++ b/app/components/Preferences.js
@@ -0,0 +1,57 @@
+// @flow
+import React from 'react';
+import { Component, Text, Button, View } from 'reactxp';
+import { Layout, Container, Header } from './Layout';
+import Img from './Img';
+import Switch from './Switch';
+import styles from './PreferencesStyles';
+
+export type PreferencesProps = {
+ allowLan: boolean;
+ onChangeAllowLan: (boolean) => void;
+ onClose: () => void;
+};
+
+export default class Preferences extends Component {
+ props: PreferencesProps;
+
+ render() {
+ return (
+ <Layout>
+ <Header hidden={ true } style={ 'defaultDark' } />
+ <Container>
+ <View style={ styles.preferences }>
+ <Button style={ styles.preferences__close } cursor='default' onPress={ this.props.onClose } testName='closeButton'>
+ <View style={ styles.preferences__close_content }>
+ <Img style={ styles.preferences__close_icon } source="icon-back" />
+ <Text style={ styles.preferences__close_title }>Settings</Text>
+ </View>
+ </Button>
+ <View style={ styles.preferences__container }>
+
+ <View style={ styles.preferences__header }>
+ <Text style={ styles.preferences__title }>Preferences</Text>
+ </View>
+
+ <View style={ styles.preferences__content }>
+ <View style={ styles.preferences__cell }>
+ <View style={ styles.preferences__cell_label_container }>
+ <Text style={ styles.preferences__cell_label }>Local network sharing</Text>
+ </View>
+ <View style={ styles.preferences__cell_accessory }>
+ <Switch isOn={ this.props.allowLan } onChange={ this.props.onChangeAllowLan } />
+ </View>
+ </View>
+ <View style={ styles.preferences__cell_footer }>
+ <Text style={ styles.preferences__cell_footer_label }>
+ { 'Allows access to other devices on the same network for sharing, printing etc.' }
+ </Text>
+ </View>
+ </View>
+ </View>
+ </View>
+ </Container>
+ </Layout>
+ );
+ }
+}
diff --git a/app/components/PreferencesStyles.js b/app/components/PreferencesStyles.js
new file mode 100644
index 0000000000..64d900b09a
--- /dev/null
+++ b/app/components/PreferencesStyles.js
@@ -0,0 +1,103 @@
+// @flow
+
+import { createViewStyles, createTextStyles } from '../lib/styles';
+
+export default {
+ ...createViewStyles({
+ preferences: {
+ background: '#192E45',
+ height: '100%',
+ },
+ preferences__container: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ },
+ preferences__header: {
+ flexGrow: 0,
+ flexShrink: 0,
+ flexBasis: 'auto',
+ paddingTop: 40,
+ paddingRight: 24,
+ paddingLeft: 24,
+ paddingBottom: 24,
+ position: 'relative' /* anchor for close button */
+ },
+ preferences__close: {
+ position: 'absolute',
+ top: 0,
+ left: 12,
+ borderWidth: 0,
+ padding: 0,
+ margin: 0,
+ zIndex: 1, /* part of .preferences__container covers the button */
+ cursor: 'default',
+ },
+ preferences__close_content: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ preferences__close_icon: {
+ opacity: 0.6,
+ marginRight: 8,
+ },
+ preferences__content: {
+ flexDirection: 'column',
+ flexGrow: 1,
+ flexShrink: 1,
+ flexBasis: 'auto',
+ },
+ preferences__cell: {
+ backgroundColor: 'rgba(41,71,115,1)',
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ preferences__cell_accessory: {
+ marginRight: 12,
+ },
+ preferences__cell_footer: {
+ paddingTop: 8,
+ paddingRight: 24,
+ paddingBottom: 24,
+ paddingLeft: 24,
+ },
+ preferences__cell_label_container: {
+ paddingTop: 15,
+ paddingRight: 12,
+ paddingBottom: 15,
+ paddingLeft: 24,
+ flexGrow: 1,
+ },
+ }),
+ ...createTextStyles({
+ preferences__close_title: {
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: 600,
+ color: 'rgba(255, 255, 255, 0.6)',
+ },
+ preferences__title: {
+ fontFamily: 'DINPro',
+ fontSize: 32,
+ fontWeight: 900,
+ lineHeight: 40,
+ color: '#fff',
+ },
+ preferences__cell_label: {
+ fontFamily: 'DINPro',
+ fontSize: 20,
+ fontWeight: 900,
+ lineHeight: 26,
+ letterSpacing: -0.2,
+ color: '#fff',
+ },
+ preferences__cell_footer_label: {
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: 600,
+ lineHeight: 20,
+ letterSpacing: -0.2,
+ color: 'rgba(255,255,255,0.8)'
+ }
+ })
+}; \ No newline at end of file
diff --git a/app/components/Settings.js b/app/components/Settings.js
index fe73ffd0af..b0b758b58c 100644
--- a/app/components/Settings.js
+++ b/app/components/Settings.js
@@ -18,6 +18,7 @@ export type SettingsProps = {
onClose: () => void,
onViewAccount: () => void,
onViewSupport: () => void,
+ onViewPreferences: () => void,
onViewAdvancedSettings: () => void,
onExternalLink: (type: string) => void
};
@@ -58,6 +59,7 @@ export default class Settings extends Component {
{/* show account options when logged in */}
{isLoggedIn ? (
<View style={styles.settings_account} testName='settings__account'>
+
<Button onPress={ this.props.onViewAccount } testName='settings__view_account'>
<View style={styles.settings__cell}>
<Text style={styles.settings__cell_label}>Account</Text>
@@ -75,15 +77,27 @@ export default class Settings extends Component {
) : null}
{isLoggedIn ? (
- <Button onPress={ this.props.onViewAdvancedSettings }>
+ <Button onPress={ this.props.onViewPreferences } testName='settings__preferences'>
+ <View style={styles.settings__cell}>
+ <Text style={styles.settings__cell_label}>Preferences</Text>
+ <Img style={styles.settings__cell_disclosure} source='icon-chevron' tintColor='currentColor' />
+ </View>
+ </Button>
+ ) : null}
+
+ {isLoggedIn ? (
+ <Button onPress={ this.props.onViewAdvancedSettings } testName="settings__advanced">
<View style={styles.settings__cell}>
<Text style={styles.settings__cell_label}>Advanced</Text>
<Img style={styles.settings__cell_disclosure} source='icon-chevron' tintColor='currentColor'/>
</View>
- <View style={styles.settings__cell_spacer}></View>
</Button>
) : null}
+ {isLoggedIn ? (
+ <View style={styles.settings__cell_spacer}></View>
+ ) : null}
+
<Button onPress={ this.props.onExternalLink.bind(this, 'faq') } testName='settings__external_link'>
<View style={styles.settings__cell}>
<Text style={styles.settings__cell_label}>FAQs</Text>
diff --git a/app/containers/PreferencesPage.js b/app/containers/PreferencesPage.js
new file mode 100644
index 0000000000..793848d35b
--- /dev/null
+++ b/app/containers/PreferencesPage.js
@@ -0,0 +1,26 @@
+// @flow
+
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { push } from 'react-router-redux';
+import Preferences from '../components/Preferences';
+
+import type { ReduxState, ReduxDispatch } from '../redux/store';
+import type { SharedRouteProps } from '../routes';
+
+const mapStateToProps = (state: ReduxState) => ({
+ allowLan: state.settings.allowLan
+});
+
+const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
+ const { backend } = props;
+ const { push: pushHistory } = bindActionCreators({ push }, dispatch);
+ return {
+ onClose: () => pushHistory('/settings'),
+ onChangeAllowLan: (allowLan) => {
+ backend.setAllowLan(allowLan);
+ },
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(Preferences);
diff --git a/app/containers/SettingsPage.js b/app/containers/SettingsPage.js
index 938d306fee..bfe30ee566 100644
--- a/app/containers/SettingsPage.js
+++ b/app/containers/SettingsPage.js
@@ -18,6 +18,7 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, _props: SharedRouteProps) =
onClose: () => pushHistory('/connect'),
onViewAccount: () => pushHistory('/settings/account'),
onViewSupport: () => pushHistory('/settings/support'),
+ onViewPreferences: () => pushHistory('/settings/preferences'),
onViewAdvancedSettings: () => pushHistory('/settings/advanced'),
onExternalLink: (type) => open(links[type]),
};
diff --git a/app/lib/backend.js b/app/lib/backend.js
index f78d359926..da87deced4 100644
--- a/app/lib/backend.js
+++ b/app/lib/backend.js
@@ -139,6 +139,12 @@ export class Backend {
log.error('Failed to fetch the location: ', e.message);
}
+ try {
+ await this._fetchAllowLan();
+ } catch(e) {
+ log.error('Failed to fetch the LAN sharing policy: ', e.message);
+ }
+
await this._fetchAccountHistory();
}
@@ -339,8 +345,6 @@ export class Backend {
}
}
-
-
async _fetchRelayLocations() {
await this._ensureAuthenticated();
@@ -395,6 +399,28 @@ export class Backend {
);
}
+ async setAllowLan(allowLan: boolean) {
+ try {
+ await this._ensureAuthenticated();
+ await this._ipc.setAllowLan(allowLan);
+
+ this._store.dispatch(
+ settingsActions.updateAllowLan(allowLan)
+ );
+ } catch(e) {
+ log.error('Failed to change the LAN sharing policy: ', e.message);
+ }
+ }
+
+ async _fetchAllowLan() {
+ await this._ensureAuthenticated();
+
+ const allowLan = await this._ipc.getAllowLan();
+ this._store.dispatch(
+ settingsActions.updateAllowLan(allowLan)
+ );
+ }
+
/**
* Start reachability monitoring for online/offline detection
* This is currently done via HTML5 APIs but will be replaced later
diff --git a/app/lib/ipc-facade.js b/app/lib/ipc-facade.js
index 567a6ab8f1..b2676193ee 100644
--- a/app/lib/ipc-facade.js
+++ b/app/lib/ipc-facade.js
@@ -142,11 +142,13 @@ const RelayListSchema = object({
export interface IpcFacade {
setConnectionString(string): void,
getAccountData(AccountToken): Promise<AccountData>,
+ getRelayLocations(): Promise<RelayList>,
getAccount(): Promise<?AccountToken>,
setAccount(accountToken: ?AccountToken): Promise<void>,
updateRelaySettings(RelaySettingsUpdate): Promise<void>,
getRelaySettings(): Promise<RelaySettings>,
- getRelayLocations(): Promise<RelayList>,
+ setAllowLan(boolean): Promise<void>,
+ getAllowLan(): Promise<boolean>,
connect(): Promise<void>,
disconnect(): Promise<void>,
shutdown(): Promise<void>,
@@ -186,6 +188,16 @@ export class RealIpc implements IpcFacade {
});
}
+ async getRelayLocations(): Promise<RelayList> {
+ const raw = await this._ipc.send('get_relay_locations');
+ try {
+ const validated: any = validate(RelayListSchema, raw);
+ return (validated: RelayList);
+ } catch (e) {
+ throw new InvalidReply(raw, e);
+ }
+ }
+
getAccount(): Promise<?AccountToken> {
return this._ipc.send('get_account')
.then( raw => {
@@ -223,13 +235,17 @@ export class RealIpc implements IpcFacade {
});
}
- async getRelayLocations(): Promise<RelayList> {
- const raw = await this._ipc.send('get_relay_locations');
- try {
- const validated: any = validate(RelayListSchema, raw);
- return (validated: RelayList);
- } catch (e) {
- throw new InvalidReply(raw, e);
+ setAllowLan(allowLan: boolean): Promise<void> {
+ return this._ipc.send('set_allow_lan', [allowLan])
+ .then(this._ignoreResponse);
+ }
+
+ async getAllowLan(): Promise<boolean> {
+ const raw = await this._ipc.send('get_allow_lan');
+ if(typeof(raw) === 'boolean') {
+ return raw;
+ } else {
+ throw new InvalidReply(raw, 'Expected a boolean');
}
}
diff --git a/app/redux/settings/actions.js b/app/redux/settings/actions.js
index 783eb042d2..02debd5b77 100644
--- a/app/redux/settings/actions.js
+++ b/app/redux/settings/actions.js
@@ -9,10 +9,15 @@ export type UpdateRelayAction = {
export type UpdateRelayLocationsAction = {
type: 'UPDATE_RELAY_LOCATIONS',
- relayLocations: Array<RelayLocationRedux>
-}
+ relayLocations: Array<RelayLocationRedux>,
+};
+
+export type UpdateAllowLanAction = {
+ type: 'UPDATE_ALLOW_LAN',
+ allowLan: boolean,
+};
-export type SettingsAction = UpdateRelayAction | UpdateRelayLocationsAction;
+export type SettingsAction = UpdateRelayAction | UpdateRelayLocationsAction | UpdateAllowLanAction;
function updateRelay(relay: RelaySettingsRedux): UpdateRelayAction {
return {
@@ -28,4 +33,11 @@ function updateRelayLocations(relayLocations: Array<RelayLocationRedux>): Update
};
}
-export default { updateRelay, updateRelayLocations };
+function updateAllowLan(allowLan: boolean): UpdateAllowLanAction {
+ return {
+ type: 'UPDATE_ALLOW_LAN',
+ allowLan: allowLan,
+ };
+}
+
+export default { updateRelay, updateRelayLocations, updateAllowLan };
diff --git a/app/redux/settings/reducers.js b/app/redux/settings/reducers.js
index 21258a80a7..59f51c2a66 100644
--- a/app/redux/settings/reducers.js
+++ b/app/redux/settings/reducers.js
@@ -34,6 +34,7 @@ export type RelayLocationRedux = {
export type SettingsReduxState = {
relaySettings: RelaySettingsRedux,
relayLocations: Array<RelayLocationRedux>,
+ allowLan: boolean,
};
const initialState: SettingsReduxState = {
@@ -45,6 +46,7 @@ const initialState: SettingsReduxState = {
}
},
relayLocations: [],
+ allowLan: false,
};
export default function(state: SettingsReduxState = initialState, action: ReduxAction): SettingsReduxState {
@@ -60,6 +62,11 @@ export default function(state: SettingsReduxState = initialState, action: ReduxA
relayLocations: action.relayLocations,
};
+ case 'UPDATE_ALLOW_LAN':
+ return { ...state,
+ allowLan: action.allowLan,
+ };
+
default:
return state;
}
diff --git a/app/routes.js b/app/routes.js
index f428c8093b..9caea27884 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -9,6 +9,7 @@ import ConnectPage from './containers/ConnectPage';
import SettingsPage from './containers/SettingsPage';
import AdvancedSettingsPage from './containers/AdvancedSettingsPage';
import AccountPage from './containers/AccountPage';
+import PreferencesPage from './containers/PreferencesPage';
import SupportPage from './containers/SupportPage';
import SelectLocationPage from './containers/SelectLocationPage';
import { getTransitionProps } from './transitions';
@@ -97,8 +98,9 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar
<PrivateRoute exact path="/connect" component={ ConnectPage } />
<PublicRoute exact path="/settings" component={ SettingsPage } />
<PrivateRoute exact path="/settings/account" component={ AccountPage } />
- <PublicRoute exact path="/settings/support" component={ SupportPage } />
+ <PublicRoute exact path="/settings/preferences" component={ PreferencesPage } />
<PublicRoute exact path="/settings/advanced" component={ AdvancedSettingsPage } />
+ <PublicRoute exact path="/settings/support" component={ SupportPage } />
<PrivateRoute exact path="/select-location" component={ SelectLocationPage } />
</Switch>
</CSSTransitionGroup>
diff --git a/app/transitions.js b/app/transitions.js
index fe6da53563..58e692f99c 100644
--- a/app/transitions.js
+++ b/app/transitions.js
@@ -49,8 +49,9 @@ const transitions: TransitionMap = {
*/
const transitionRules = [
r('/settings', '/settings/account', transitions.push),
- r('/settings', '/settings/support', transitions.push),
+ r('/settings', '/settings/preferences', transitions.push),
r('/settings', '/settings/advanced', transitions.push),
+ r('/settings', '/settings/support', transitions.push),
r(null, '/settings', transitions.slide),
r(null, '/select-location', transitions.slide)
];