diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-10-11 16:33:03 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-10-15 13:19:02 +0200 |
| commit | 2b6aa2d738727966f0c4f7cd67db6a9181ccd805 (patch) | |
| tree | 074cab278426c9c998b62e22158af3fa820558ae /gui | |
| parent | 1a7ebcf1f54ae56e5bd1c7748ea26db8a2bb68e7 (diff) | |
| download | mullvadvpn-2b6aa2d738727966f0c4f7cd67db6a9181ccd805.tar.xz mullvadvpn-2b6aa2d738727966f0c4f7cd67db6a9181ccd805.zip | |
Add system notifications for inconsistent state and unsupported version
Diffstat (limited to 'gui')
7 files changed, 119 insertions, 49 deletions
diff --git a/gui/flow-libs/electron.js.flow b/gui/flow-libs/electron.js.flow index b0facb6c7e..bc37e5a524 100644 --- a/gui/flow-libs/electron.js.flow +++ b/gui/flow-libs/electron.js.flow @@ -57,6 +57,7 @@ declare module 'electron' { declare class Remote { app: App; + shell: Shell; getCurrentWindow(): BrowserWindow; getCurrentWebContents(): WebContents; getGlobal(name: string): ?mixed; diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js index d4f5f76830..c465e74b8d 100644 --- a/gui/packages/desktop/src/renderer/app.js +++ b/gui/packages/desktop/src/renderer/app.js @@ -360,18 +360,78 @@ export default class AppRenderer { actions.settings.updateAutoConnect(autoConnect); } + async _getAppComponentsVersions() { + const daemonVersion = await this._daemonRpc.getCurrentVersion(); + const guiVersion = remote.app.getVersion().replace('.0', ''); + return { + daemon: daemonVersion, + gui: guiVersion, + isConsistent: daemonVersion === guiVersion, + }; + } + async _fetchCurrentVersion() { const actions = this._reduxActions; - const versionFromDaemon = await this._daemonRpc.getCurrentVersion(); - const versionFromGui = remote.app.getVersion().replace('.0', ''); + const versions = await this._getAppComponentsVersions(); + + // notify user about inconsistent version + if (process.env.NODE_ENV !== 'development' && !versions.isConsistent) { + this._notificationController.notifyInconsistentVersion(); + } - actions.version.updateVersion(versionFromDaemon, versionFromDaemon === versionFromGui); + actions.version.updateVersion(versions.gui, versions.isConsistent); } async _fetchLatestVersionInfo() { + function isBeta(version: string) { + return version.includes('-'); + } + + function nextUpgrade(current: string, latest: string, latestStable: string): ?string { + if (isBeta(current)) { + return current === latest ? null : latest; + } else { + return current === latestStable ? null : latestStable; + } + } + + function checkIfLatest(current: string, latest: string, latestStable: string): boolean { + // perhaps -beta? + if (isBeta(current)) { + return current === latest; + } else { + // must be stable + return current === latestStable; + } + } + + const versions = await this._getAppComponentsVersions(); + const versionInfo = await this._daemonRpc.getVersionInfo(); + const latestVersion = versionInfo.latest.latest; + const latestStableVersion = versionInfo.latest.latestStable; + + // the reason why we rely on daemon version here is because daemon obtains the version info + // based on its built-in version information + const isUpToDate = checkIfLatest(versions.daemon, latestVersion, latestStableVersion); + const upgradeVersion = nextUpgrade(versions.daemon, latestVersion, latestStableVersion); + + // notify user to update the app if it became unsupported + if ( + process.env.NODE_ENV !== 'development' && + versions.isConsistent && + !versionInfo.currentIsSupported && + upgradeVersion + ) { + this._notificationController.notifyUnsupportedVersion(upgradeVersion); + } + // fetching the latest version info has a higher latency because the daemon communicates with // the API server - this._reduxActions.version.updateLatest(await this._daemonRpc.getVersionInfo()); + this._reduxActions.version.updateLatest({ + ...versionInfo, + nextUpgrade: upgradeVersion, + upToDate: isUpToDate, + }); } async _onOpenConnection() { @@ -535,7 +595,7 @@ export default class AppRenderer { this._updateConnectionStatus(tunnelState); this._updateUserLocation(tunnelState.state); this._updateTrayIcon(tunnelState.state); - this._notificationController.notify(tunnelState); + this._notificationController.notifyTunnelState(tunnelState); } _setSettings(newSettings: Settings) { diff --git a/gui/packages/desktop/src/renderer/components/NotificationArea.js b/gui/packages/desktop/src/renderer/components/NotificationArea.js index e469d657a4..8fff84f375 100644 --- a/gui/packages/desktop/src/renderer/components/NotificationArea.js +++ b/gui/packages/desktop/src/renderer/components/NotificationArea.js @@ -138,9 +138,9 @@ export default class NotificationArea extends Component<Props, State> { <NotificationIndicator type={'error'} /> <NotificationContent> <NotificationTitle>{'UNSUPPORTED VERSION'}</NotificationTitle> - <NotificationSubtitle>{`This app version might have security issues. Please upgrade to ${ + <NotificationSubtitle>{`You are running an unsupported app version. Please upgrade to ${ this.state.upgradeVersion - }`}</NotificationSubtitle> + } now to ensure your security`}</NotificationSubtitle> </NotificationContent> <NotificationActions> <NotificationOpenLinkAction diff --git a/gui/packages/desktop/src/renderer/components/NotificationBanner.js b/gui/packages/desktop/src/renderer/components/NotificationBanner.js index 03f0247e88..a168ee0bf7 100644 --- a/gui/packages/desktop/src/renderer/components/NotificationBanner.js +++ b/gui/packages/desktop/src/renderer/components/NotificationBanner.js @@ -46,6 +46,7 @@ const styles = { flex: 0, flexDirection: 'column', justifyContent: 'center', + marginLeft: 5, }), actionButton: Styles.createButtonStyle({ flex: 1, diff --git a/gui/packages/desktop/src/renderer/lib/notification-controller.js b/gui/packages/desktop/src/renderer/lib/notification-controller.js index 9f53524295..9cb2f925dc 100644 --- a/gui/packages/desktop/src/renderer/lib/notification-controller.js +++ b/gui/packages/desktop/src/renderer/lib/notification-controller.js @@ -2,28 +2,30 @@ import { remote } from 'electron'; import log from 'electron-log'; +import config from '../../config'; import type { TunnelStateTransition } from './daemon-rpc'; export default class NotificationController { _activeNotification: ?Notification; _reconnecting = false; + _presentedNotifications = {}; - notify(tunnelState: TunnelStateTransition) { + notifyTunnelState(tunnelState: TunnelStateTransition) { switch (tunnelState.state) { case 'connecting': if (!this._reconnecting) { - this._show('Connecting'); + this._showTunnelStateNotification('Connecting'); } break; case 'connected': - this._show('Secured'); + this._showTunnelStateNotification('Secured'); break; case 'disconnected': - this._show('Unsecured'); + this._showTunnelStateNotification('Unsecured'); break; case 'blocked': - this._show('Blocked all connections'); + this._showTunnelStateNotification('Blocked all connections'); break; case 'disconnecting': switch (tunnelState.details) { @@ -32,7 +34,7 @@ export default class NotificationController { // no-op break; case 'reconnect': - this._show('Reconnecting'); + this._showTunnelStateNotification('Reconnecting'); this._reconnecting = true; return; } @@ -44,7 +46,29 @@ export default class NotificationController { this._reconnecting = false; } - _show(message: string) { + notifyInconsistentVersion() { + this._presentNotificationOnce('inconsistent-version', () => { + new Notification(remote.app.getName(), { + body: 'Inconsistent internal version information, please restart the app', + silent: true, + }); + }); + } + + notifyUnsupportedVersion(upgradeVersion: string) { + this._presentNotificationOnce('unsupported-version', () => { + const notification = new Notification(remote.app.getName(), { + body: `You are running an unsupported app version. Please upgrade to ${upgradeVersion} now to ensure your security`, + silent: true, + }); + + notification.addEventListener('click', () => { + remote.shell.openExternal(config.links.download); + }); + }); + } + + _showTunnelStateNotification(message: string) { const lastNotification = this._activeNotification; const sameAsLastNotification = lastNotification && lastNotification.body === message; @@ -59,6 +83,7 @@ export default class NotificationController { newNotification.addEventListener('show', () => { // If the notification is closed too soon, it might still get shown. If that happens, close() // should be called again so that it is closed immediately. + // Tracking issue: https://github.com/electron/electron/issues/12887 if (this._activeNotification !== newNotification) { newNotification.close(); } @@ -68,4 +93,12 @@ export default class NotificationController { lastNotification.close(); } } + + _presentNotificationOnce(notificationName: string, presentNotification: () => void) { + const presented = this._presentedNotifications; + if (!presented[notificationName]) { + presented[notificationName] = true; + presentNotification(); + } + } } diff --git a/gui/packages/desktop/src/renderer/redux/version/actions.js b/gui/packages/desktop/src/renderer/redux/version/actions.js index 0277a7f069..4eee3fcc61 100644 --- a/gui/packages/desktop/src/renderer/redux/version/actions.js +++ b/gui/packages/desktop/src/renderer/redux/version/actions.js @@ -2,9 +2,14 @@ import type { AppVersionInfo } from '../../lib/daemon-rpc'; +type UpdateLatestActionPayload = { + upToDate: boolean, + nextUpgrade: ?string, +} & AppVersionInfo; + export type UpdateLatestAction = { type: 'UPDATE_LATEST', - latestInfo: AppVersionInfo, + latestInfo: UpdateLatestActionPayload, }; export type UpdateVersionAction = { @@ -15,7 +20,7 @@ export type UpdateVersionAction = { export type VersionAction = UpdateLatestAction | UpdateVersionAction; -function updateLatest(latestInfo: AppVersionInfo): UpdateLatestAction { +function updateLatest(latestInfo: UpdateLatestActionPayload): UpdateLatestAction { return { type: 'UPDATE_LATEST', latestInfo, diff --git a/gui/packages/desktop/src/renderer/redux/version/reducers.js b/gui/packages/desktop/src/renderer/redux/version/reducers.js index c3d6a07382..a2bc07c3e7 100644 --- a/gui/packages/desktop/src/renderer/redux/version/reducers.js +++ b/gui/packages/desktop/src/renderer/redux/version/reducers.js @@ -22,45 +22,17 @@ const initialState: VersionReduxState = { consistent: true, }; -function isBeta(version: string) { - return version.includes('-'); -} - -function nextUpgrade(current: string, latest: ?string, latestStable: ?string): ?string { - if (isBeta(current)) { - return current === latest ? null : latest; - } else { - return current === latestStable ? null : latestStable; - } -} - -function checkIfLatest(current: string, latest: ?string, latestStable: ?string): boolean { - // perhaps -beta? - if (isBeta(current)) { - return current === latest || latest === null; - } else { - // must be stable - return current === latestStable || latestStable === null; - } -} - export default function( state: VersionReduxState = initialState, action: ReduxAction, ): VersionReduxState { switch (action.type) { case 'UPDATE_LATEST': { - const currentIsSupported = action.latestInfo.currentIsSupported; - const latest = action.latestInfo.latest.latest; - const latestStable = action.latestInfo.latest.latestStable; - + const { latest, ...other } = action.latestInfo; return { ...state, - currentIsSupported, - latest, - latestStable, - nextUpgrade: nextUpgrade(state.current, latest, latestStable), - upToDate: checkIfLatest(state.current, latest, latestStable), + ...other, + ...latest, }; } @@ -69,8 +41,6 @@ export default function( ...state, current: action.version, consistent: action.consistent, - nextUpgrade: nextUpgrade(action.version, state.latest, state.latestStable), - upToDate: checkIfLatest(action.version, state.latest, state.latestStable), }; default: |
