summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-08-21 06:40:48 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-08-21 06:40:48 -0300
commit545ac2cf52ae7e5e44d9d97276a3cf375b10df92 (patch)
treea31f7052447ad58227dc9ef974dd2e41d35b76b3
parent6668dc4b6b7ff37136fb1f7abae11c9886adb7ff (diff)
parentd2b600f49ea66df5c3bd429b61519db4e0f9151f (diff)
downloadmullvadvpn-545ac2cf52ae7e5e44d9d97276a3cf375b10df92.tar.xz
mullvadvpn-545ac2cf52ae7e5e44d9d97276a3cf375b10df92.zip
Merge branch 'new-version-warning'
-rw-r--r--CHANGELOG.md1
-rw-r--r--gui/packages/desktop/src/assets/images/icon-alert.svg4
-rw-r--r--gui/packages/desktop/src/renderer/app.js18
-rw-r--r--gui/packages/desktop/src/renderer/components/Settings.js39
-rw-r--r--gui/packages/desktop/src/renderer/components/SettingsStyles.js17
-rw-r--r--gui/packages/desktop/src/renderer/containers/SettingsPage.js4
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc.js43
-rw-r--r--gui/packages/desktop/src/renderer/redux/store.js8
-rw-r--r--gui/packages/desktop/src/renderer/redux/version/actions.js33
-rw-r--r--gui/packages/desktop/src/renderer/redux/version/reducers.js53
10 files changed, 206 insertions, 14 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfd3f7dddf..8b1c42239a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ Line wrap the file at 100 chars. Th
### Added
- Add option to enable or disable IPv6 on the tunnel interface.
- Log panics in the daemon to the log file.
+- Warn in the Settings screen if a new version is available.
### Changed
- The "Buy more credit" button is changed to open a dedicated account login page instead of one
diff --git a/gui/packages/desktop/src/assets/images/icon-alert.svg b/gui/packages/desktop/src/assets/images/icon-alert.svg
new file mode 100644
index 0000000000..c11507a4a5
--- /dev/null
+++ b/gui/packages/desktop/src/assets/images/icon-alert.svg
@@ -0,0 +1,4 @@
+<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title>alert</title>
+ <path d="m12 24c-6.627417 0-12-5.372583-12-12s5.372583-12 12-12 12 5.372583 12 12-5.372583 12-12 12zm0-19.5c-.8284271 0-1.5.67157288-1.5 1.5v7.5c0 .8284271.6715729 1.5 1.5 1.5s1.5-.6715729 1.5-1.5v-7.5c0-.82842712-.6715729-1.5-1.5-1.5zm0 12c-.8284271 0-1.5.6715729-1.5 1.5s.6715729 1.5 1.5 1.5 1.5-.6715729 1.5-1.5-.6715729-1.5-1.5-1.5z" fill="currentColor" />
+</svg>
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js
index 82a717941d..310b1f84ce 100644
--- a/gui/packages/desktop/src/renderer/app.js
+++ b/gui/packages/desktop/src/renderer/app.js
@@ -10,7 +10,7 @@ import {
replace as replaceHistory,
} from 'connected-react-router';
import { createMemoryHistory } from 'history';
-import { webFrame, ipcRenderer } from 'electron';
+import { remote, webFrame, ipcRenderer } from 'electron';
import makeRoutes from './routes';
import ReconnectionBackoff from './lib/reconnection-backoff';
@@ -23,6 +23,7 @@ import configureStore from './redux/store';
import accountActions from './redux/account/actions';
import connectionActions from './redux/connection/actions';
import settingsActions from './redux/settings/actions';
+import versionActions from './redux/version/actions';
import daemonActions from './redux/daemon/actions';
import type { RpcCredentials } from '../common/types';
@@ -57,6 +58,7 @@ export default class AppRenderer {
account: bindActionCreators(accountActions, dispatch),
connection: bindActionCreators(connectionActions, dispatch),
settings: bindActionCreators(settingsActions, dispatch),
+ version: bindActionCreators(versionActions, dispatch),
daemon: bindActionCreators(daemonActions, dispatch),
history: bindActionCreators(
{
@@ -405,6 +407,19 @@ export default class AppRenderer {
actions.settings.updateEnableIpv6(tunnelOptions.openvpn.enableIpv6);
}
+ async _fetchVersionInfo() {
+ const actions = this._reduxActions;
+ const latestVersionInfo = await this._daemonRpc.getVersionInfo();
+ const versionFromDaemon = await this._daemonRpc.getCurrentVersion();
+ const versionFromGui = remote.app
+ .getVersion()
+ .replace('.0-', '-') // remove the .0 in yyyy.x.0-zzz
+ .replace(/\.0$/, ''); // remove the .0 in yyyy.x.0
+
+ actions.version.updateVersion(versionFromDaemon, versionFromDaemon === versionFromGui);
+ actions.version.updateLatest(latestVersionInfo);
+ }
+
async _connectToDaemon(): Promise<void> {
let credentials;
try {
@@ -536,6 +551,7 @@ export default class AppRenderer {
this._fetchLocation(),
this._fetchAccountHistory(),
this._fetchTunnelOptions(),
+ this._fetchVersionInfo(),
]);
}
diff --git a/gui/packages/desktop/src/renderer/components/Settings.js b/gui/packages/desktop/src/renderer/components/Settings.js
index 40c4c2e2de..cb8071a9ce 100644
--- a/gui/packages/desktop/src/renderer/components/Settings.js
+++ b/gui/packages/desktop/src/renderer/components/Settings.js
@@ -2,15 +2,17 @@
import moment from 'moment';
import * as React from 'react';
-import { Component, View } from 'reactxp';
+import { Component, Text, View } from 'reactxp';
import * as AppButton from './AppButton';
import * as Cell from './Cell';
+import Img from './Img';
import { Layout, Container } from './Layout';
import NavigationBar, { CloseBarItem } from './NavigationBar';
import SettingsHeader, { HeaderTitle } from './SettingsHeader';
import CustomScrollbars from './CustomScrollbars';
import styles from './SettingsStyles';
import WindowStateObserver from '../lib/window-state-observer';
+import { colors } from '../../config';
import type { LoginState } from '../redux/account/reducers';
@@ -18,6 +20,8 @@ type Props = {
loginState: LoginState,
accountExpiry: ?string,
appVersion: string,
+ consistentVersion: boolean,
+ upToDateVersion: boolean,
onQuit: () => void,
onClose: () => void,
onViewAccount: () => void,
@@ -133,29 +137,40 @@ export default class Settings extends Component<Props> {
}
_renderMiddleButtons() {
+ let icon;
+ let footer;
+ if (!this.props.consistentVersion || !this.props.upToDateVersion) {
+ const message = !this.props.consistentVersion
+ ? 'Inconsistent internal version information, please restart the app.'
+ : 'This is not the latest version, download the update to remain safe.';
+
+ icon = (
+ <Img source="icon-alert" tintColor={colors.red} style={styles.settings__version_warning} />
+ );
+ footer = (
+ <View style={styles.settings__cell_footer}>
+ <Text style={styles.settings__cell_footer_label}>{message}</Text>
+ </View>
+ );
+ } else {
+ footer = <View style={styles.settings__cell_spacer} />;
+ }
+
return (
<View>
<Cell.CellButton
onPress={this.props.onExternalLink.bind(this, 'download')}
testName="settings__version">
+ {icon}
<Cell.Label>App version</Cell.Label>
- <Cell.SubText>{this._formattedVersion()}</Cell.SubText>
+ <Cell.SubText>{this.props.appVersion}</Cell.SubText>
<Cell.Img height={16} width={16} source="icon-extLink" />
</Cell.CellButton>
- <View style={styles.settings__cell_spacer} />
+ {footer}
</View>
);
}
- _formattedVersion() {
- // the version in package.json has to be semver, but we use a YEAR.release-channel
- // version scheme. in package.json we thus have to write YEAR.release.X-channel and
- // this function is responsible for removing .X part.
- return this.props.appVersion
- .replace('.0-', '-') // remove the .0 in 2018.1.0-beta9
- .replace(/\.0$/, ''); // remove the .0 in 2018.1.0
- }
-
_renderBottomButtons() {
return (
<View>
diff --git a/gui/packages/desktop/src/renderer/components/SettingsStyles.js b/gui/packages/desktop/src/renderer/components/SettingsStyles.js
index 3f63d96b0f..56ec6b3752 100644
--- a/gui/packages/desktop/src/renderer/components/SettingsStyles.js
+++ b/gui/packages/desktop/src/renderer/components/SettingsStyles.js
@@ -27,14 +27,31 @@ export default {
height: 24,
flex: 0,
}),
+ settings__cell_footer: Styles.createViewStyle({
+ paddingTop: 8,
+ paddingRight: 24,
+ paddingBottom: 24,
+ paddingLeft: 24,
+ }),
settings__footer: Styles.createViewStyle({
paddingTop: 24,
paddingBottom: 24,
paddingLeft: 24,
paddingRight: 24,
}),
+ settings__version_warning: Styles.createViewStyle({
+ marginLeft: 8,
+ }),
settings__account_paid_until_label__error: Styles.createTextStyle({
color: colors.red,
}),
+ settings__cell_footer_label: Styles.createTextStyle({
+ fontFamily: 'Open Sans',
+ fontSize: 13,
+ fontWeight: '600',
+ lineHeight: 20,
+ letterSpacing: -0.2,
+ color: colors.white60,
+ }),
};
diff --git a/gui/packages/desktop/src/renderer/containers/SettingsPage.js b/gui/packages/desktop/src/renderer/containers/SettingsPage.js
index 68fc929342..98b0fcd061 100644
--- a/gui/packages/desktop/src/renderer/containers/SettingsPage.js
+++ b/gui/packages/desktop/src/renderer/containers/SettingsPage.js
@@ -13,7 +13,9 @@ import type { SharedRouteProps } from '../routes';
const mapStateToProps = (state: ReduxState) => ({
loginState: state.account.status,
accountExpiry: state.account.expiry,
- appVersion: remote.app.getVersion(),
+ appVersion: state.version.current,
+ consistentVersion: state.version.consistent,
+ upToDateVersion: state.version.upToDate,
});
const mapDispatchToProps = (dispatch: ReduxDispatch, props: SharedRouteProps) => {
const history = bindActionCreators({ push, goBack }, dispatch);
diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
index 8e2925f83e..3f3d3af9c1 100644
--- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
+++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js
@@ -205,6 +205,22 @@ const BackendStateSchema = object({
target_state: enumeration(...allSecurityStates),
});
+export type AppVersionInfo = {
+ currentIsSupported: boolean,
+ latest: {
+ latestStable: string,
+ latest: string,
+ },
+};
+
+const AppVersionInfoSchema = object({
+ current_is_supported: boolean,
+ latest: object({
+ latest_stable: string,
+ latest: string,
+ }),
+});
+
export interface DaemonRpcProtocol {
connect(string): void;
disconnect(): void;
@@ -230,6 +246,8 @@ export interface DaemonRpcProtocol {
authenticate(sharedSecret: string): Promise<void>;
getAccountHistory(): Promise<Array<AccountToken>>;
removeAccountFromHistory(accountToken: AccountToken): Promise<void>;
+ getCurrentVersion(): Promise<string>;
+ getVersionInfo(): Promise<AppVersionInfo>;
}
export class ResponseParseError extends Error {
@@ -455,4 +473,29 @@ export class DaemonRpc implements DaemonRpcProtocol {
async removeAccountFromHistory(accountToken: AccountToken): Promise<void> {
await this._transport.send('remove_account_from_history', accountToken);
}
+
+ async getCurrentVersion(): Promise<string> {
+ const response = await this._transport.send('get_current_version');
+ try {
+ return validate(string, response);
+ } catch (error) {
+ throw new ResponseParseError('Invalid response from get_current_version', null);
+ }
+ }
+
+ async getVersionInfo(): Promise<AppVersionInfo> {
+ const response = await this._transport.send('get_version_info');
+ try {
+ const versionInfo = validate(AppVersionInfoSchema, response);
+ return {
+ currentIsSupported: versionInfo.current_is_supported,
+ latest: {
+ latestStable: versionInfo.latest.latest_stable,
+ latest: versionInfo.latest.latest,
+ },
+ };
+ } catch (error) {
+ throw new ResponseParseError('Invalid response from get_version_info', null);
+ }
+ }
}
diff --git a/gui/packages/desktop/src/renderer/redux/store.js b/gui/packages/desktop/src/renderer/redux/store.js
index af2ebc1d5a..58c6645a45 100644
--- a/gui/packages/desktop/src/renderer/redux/store.js
+++ b/gui/packages/desktop/src/renderer/redux/store.js
@@ -11,6 +11,8 @@ import settings from './settings/reducers';
import settingsActions from './settings/actions';
import support from './support/reducers';
import supportActions from './support/actions';
+import version from './version/reducers';
+import versionActions from './version/actions';
import daemon from './daemon/reducers';
import daemonActions from './daemon/actions';
@@ -20,12 +22,14 @@ import type { AccountReduxState } from './account/reducers';
import type { ConnectionReduxState } from './connection/reducers';
import type { SettingsReduxState } from './settings/reducers';
import type { SupportReduxState } from './support/reducers';
+import type { VersionReduxState } from './version/reducers';
import type { DaemonReduxState } from './daemon/reducers';
import type { AccountAction } from './account/actions';
import type { ConnectionAction } from './connection/actions';
import type { SettingsAction } from './settings/actions';
import type { SupportAction } from './support/actions';
+import type { VersionAction } from './version/actions';
import type { DaemonAction } from './daemon/actions';
export type ReduxState = {
@@ -33,6 +37,7 @@ export type ReduxState = {
connection: ConnectionReduxState,
settings: SettingsReduxState,
support: SupportReduxState,
+ version: VersionReduxState,
daemon: DaemonReduxState,
};
@@ -41,6 +46,7 @@ export type ReduxAction =
| ConnectionAction
| SettingsAction
| SupportAction
+ | VersionAction
| DaemonAction;
export type ReduxStore = Store<ReduxState, ReduxAction, ReduxDispatch>;
export type ReduxGetState = () => ReduxState;
@@ -57,6 +63,7 @@ export default function configureStore(
...connectionActions,
...settingsActions,
...supportActions,
+ ...versionActions,
...daemonActions,
pushRoute: (route) => push(route),
replaceRoute: (route) => replace(route),
@@ -67,6 +74,7 @@ export default function configureStore(
connection,
settings,
support,
+ version,
daemon,
};
diff --git a/gui/packages/desktop/src/renderer/redux/version/actions.js b/gui/packages/desktop/src/renderer/redux/version/actions.js
new file mode 100644
index 0000000000..0277a7f069
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/redux/version/actions.js
@@ -0,0 +1,33 @@
+// @flow
+
+import type { AppVersionInfo } from '../../lib/daemon-rpc';
+
+export type UpdateLatestAction = {
+ type: 'UPDATE_LATEST',
+ latestInfo: AppVersionInfo,
+};
+
+export type UpdateVersionAction = {
+ type: 'UPDATE_VERSION',
+ version: string,
+ consistent: boolean,
+};
+
+export type VersionAction = UpdateLatestAction | UpdateVersionAction;
+
+function updateLatest(latestInfo: AppVersionInfo): UpdateLatestAction {
+ return {
+ type: 'UPDATE_LATEST',
+ latestInfo,
+ };
+}
+
+function updateVersion(version: string, consistent: boolean): UpdateVersionAction {
+ return {
+ type: 'UPDATE_VERSION',
+ version,
+ consistent,
+ };
+}
+
+export default { updateLatest, updateVersion };
diff --git a/gui/packages/desktop/src/renderer/redux/version/reducers.js b/gui/packages/desktop/src/renderer/redux/version/reducers.js
new file mode 100644
index 0000000000..97e99f0c3e
--- /dev/null
+++ b/gui/packages/desktop/src/renderer/redux/version/reducers.js
@@ -0,0 +1,53 @@
+// @flow
+
+import type { ReduxAction } from '../store';
+
+export type VersionReduxState = {
+ current: string,
+ latest: string,
+ latestStable: string,
+ upToDate: boolean,
+ consistent: boolean,
+};
+
+const initialState: VersionReduxState = {
+ current: '',
+ latest: '',
+ latestStable: '',
+ upToDate: false,
+ consistent: true,
+};
+
+const checkIfLatest = (current: string, latest: string, latestStable: string): boolean => {
+ return current === latest || current === latestStable;
+};
+
+export default function(
+ state: VersionReduxState = initialState,
+ action: ReduxAction,
+): VersionReduxState {
+ switch (action.type) {
+ case 'UPDATE_LATEST': {
+ const latest = action.latestInfo.latest.latest;
+ const latestStable = action.latestInfo.latest.latestStable;
+
+ return {
+ ...state,
+ latest,
+ latestStable,
+ upToDate: checkIfLatest(state.current, latest, latestStable),
+ };
+ }
+
+ case 'UPDATE_VERSION':
+ return {
+ ...state,
+ current: action.version,
+ consistent: action.consistent,
+ upToDate: checkIfLatest(action.version, state.latest, state.latestStable),
+ };
+
+ default:
+ return state;
+ }
+}