diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-02-10 16:37:45 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-02-11 09:00:53 +0100 |
| commit | 83a1b073c6616330fa48468deaa13ade13ed711c (patch) | |
| tree | 2bf5396b4cd93baa1b551e74b60d8f9a4a5ce493 /gui/src | |
| parent | c9b3e7f007b0dd340ea810bca05abef36a325b7f (diff) | |
| download | mullvadvpn-83a1b073c6616330fa48468deaa13ade13ed711c.tar.xz mullvadvpn-83a1b073c6616330fa48468deaa13ade13ed711c.zip | |
Add account expiry warning notification
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/index.ts | 32 | ||||
| -rw-r--r-- | gui/src/main/notification-controller.ts | 18 | ||||
| -rw-r--r-- | gui/src/renderer/components/Account.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/Connect.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/NotificationArea.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/containers/ConnectPage.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/containers/NotificationAreaContainer.tsx | 2 | ||||
| -rw-r--r-- | gui/src/shared/account-expiry.ts (renamed from gui/src/renderer/lib/account-expiry.ts) | 8 |
9 files changed, 62 insertions, 8 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 4df3b4c7a2..32620c3574 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -2,8 +2,10 @@ import { execFile } from 'child_process'; import { app, BrowserWindow, ipcMain, Menu, nativeImage, screen, Tray } from 'electron'; import log from 'electron-log'; import mkdirp from 'mkdirp'; +import moment from 'moment'; import * as path from 'path'; import * as uuid from 'uuid'; +import AccountExpiry from '../shared/account-expiry'; import BridgeSettingsBuilder from '../shared/bridge-settings-builder'; import { AccountToken, @@ -145,6 +147,8 @@ class ApplicationMain { private wireguardPublicKey?: IWireguardPublicKey; + private accountExpiryNotificationTimeout?: NodeJS.Timeout; + private accountDataCache = new AccountDataCache( (accountToken) => { return this.daemonRpc.getAccountData(accountToken); @@ -155,6 +159,8 @@ class ApplicationMain { if (this.windowController) { IpcMainEventChannel.account.notify(this.windowController.webContents, accountData); } + + this.notifyOfAccountExpiry(); }, ); @@ -1081,6 +1087,11 @@ class ApplicationMain { private async logout(): Promise<void> { try { await this.daemonRpc.setAccount(); + + if (this.accountExpiryNotificationTimeout) { + global.clearTimeout(this.accountExpiryNotificationTimeout); + this.accountExpiryNotificationTimeout = undefined; + } } catch (error) { log.info(`Failed to logout: ${error.message}`); @@ -1132,6 +1143,27 @@ class ApplicationMain { } } + private notifyOfAccountExpiry() { + if (this.accountData) { + const accountExpiry = new AccountExpiry(this.accountData.expiry, this.locale); + if ( + accountExpiry && + !this.accountExpiryNotificationTimeout && + accountExpiry.willHaveExpiredAt( + moment() + .add(3, 'days') + .toDate(), + ) + ) { + this.notificationController.closeToExpiryNotification(accountExpiry); + this.accountExpiryNotificationTimeout = global.setTimeout(() => { + this.accountExpiryNotificationTimeout = undefined; + this.notifyOfAccountExpiry(); + }, 12 * 60 * 60 * 1000); // Every 12 hours + } + } + } + private async updateAccountHistory(): Promise<void> { try { this.setAccountHistory(await this.daemonRpc.getAccountHistory()); diff --git a/gui/src/main/notification-controller.ts b/gui/src/main/notification-controller.ts index 98852029f3..69c0f9ec30 100644 --- a/gui/src/main/notification-controller.ts +++ b/gui/src/main/notification-controller.ts @@ -3,6 +3,7 @@ import os from 'os'; import path from 'path'; import { sprintf } from 'sprintf-js'; import config from '../config.json'; +import AccountExpiry from '../shared/account-expiry'; import { TunnelState } from '../shared/daemon-rpc-types'; import { messages } from '../shared/gettext'; @@ -156,6 +157,23 @@ export default class NotificationController { this.scheduleNotification(notification); } + public closeToExpiryNotification(accountExpiry: AccountExpiry) { + const duration = accountExpiry.durationUntilExpiry(); + const notification = new Notification({ + title: this.notificationTitle, + body: sprintf( + // TRANSLATORS: The system notification displayed to the user when the account credit is close to expiry. + // TRANSLATORS: Available placeholder: + // TRANSLATORS: %(duration)s - remaining time, e.g. "2 days" + messages.pgettext('notifications', 'Account credit expires in %(duration)s'), + { duration }, + ), + silent: true, + icon: this.notificationIcon, + }); + this.scheduleNotification(notification); + } + public cancelPendingNotifications() { for (const notification of this.pendingNotifications) { notification.close(); diff --git a/gui/src/renderer/components/Account.tsx b/gui/src/renderer/components/Account.tsx index 1390f78370..5b5a393f62 100644 --- a/gui/src/renderer/components/Account.tsx +++ b/gui/src/renderer/components/Account.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Component, Text, View } from 'reactxp'; +import AccountExpiry from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; -import AccountExpiry from '../lib/account-expiry'; import styles from './AccountStyles'; import * as AppButton from './AppButton'; import ClipboardLabel from './ClipboardLabel'; diff --git a/gui/src/renderer/components/Connect.tsx b/gui/src/renderer/components/Connect.tsx index 4cb63c6a5b..2308a91dff 100644 --- a/gui/src/renderer/components/Connect.tsx +++ b/gui/src/renderer/components/Connect.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Component, Styles, View } from 'reactxp'; import { links } from '../../config.json'; +import AccountExpiry from '../../shared/account-expiry'; import NotificationAreaContainer from '../containers/NotificationAreaContainer'; -import AccountExpiry from '../lib/account-expiry'; import { AuthFailureKind, parseAuthFailure } from '../lib/auth-failure'; import { IConnectionReduxState } from '../redux/connection/reducers'; import { IVersionReduxState } from '../redux/version/reducers'; diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx index c459695a30..243b406518 100644 --- a/gui/src/renderer/components/NotificationArea.tsx +++ b/gui/src/renderer/components/NotificationArea.tsx @@ -13,8 +13,8 @@ import { NotificationTitle, } from './NotificationBanner'; +import AccountExpiry from '../../shared/account-expiry'; import { ErrorStateCause, TunnelParameterError, TunnelState } from '../../shared/daemon-rpc-types'; -import AccountExpiry from '../lib/account-expiry'; import { parseAuthFailure } from '../lib/auth-failure'; import { IVersionReduxState } from '../redux/version/reducers'; diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index dc7f875529..ee5cc8c340 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Component, Text, View } from 'reactxp'; import { colors, links } from '../../config.json'; +import AccountExpiry from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; -import AccountExpiry from '../lib/account-expiry'; import * as AppButton from './AppButton'; import * as Cell from './Cell'; import { Container, Layout } from './Layout'; diff --git a/gui/src/renderer/containers/ConnectPage.tsx b/gui/src/renderer/containers/ConnectPage.tsx index e4c37f19cc..de691b0db9 100644 --- a/gui/src/renderer/containers/ConnectPage.tsx +++ b/gui/src/renderer/containers/ConnectPage.tsx @@ -3,10 +3,10 @@ import log from 'electron-log'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { sprintf } from 'sprintf-js'; +import AccountExpiry from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; import Connect from '../components/Connect'; import withAppContext, { IAppContext } from '../context'; -import AccountExpiry from '../lib/account-expiry'; import { IRelayLocationRedux, RelaySettingsRedux } from '../redux/settings/reducers'; import { IReduxState, ReduxDispatch } from '../redux/store'; diff --git a/gui/src/renderer/containers/NotificationAreaContainer.tsx b/gui/src/renderer/containers/NotificationAreaContainer.tsx index 814b055d97..f282c48fab 100644 --- a/gui/src/renderer/containers/NotificationAreaContainer.tsx +++ b/gui/src/renderer/containers/NotificationAreaContainer.tsx @@ -2,9 +2,9 @@ import { connect } from 'react-redux'; import { shell } from 'electron'; import { links } from '../../config.json'; +import AccountExpiry from '../../shared/account-expiry'; import NotificationArea from '../components/NotificationArea'; import withAppContext, { IAppContext } from '../context'; -import AccountExpiry from '../lib/account-expiry'; import { IReduxState, ReduxDispatch } from '../redux/store'; const mapStateToProps = (state: IReduxState, _props: IAppContext) => ({ diff --git a/gui/src/renderer/lib/account-expiry.ts b/gui/src/shared/account-expiry.ts index 5393238dcd..8e4c9b2ece 100644 --- a/gui/src/renderer/lib/account-expiry.ts +++ b/gui/src/shared/account-expiry.ts @@ -1,6 +1,6 @@ import moment from 'moment'; import { sprintf } from 'sprintf-js'; -import { messages } from '../../shared/gettext'; +import { messages } from './gettext'; export default class AccountExpiry { private expiry: moment.Moment; @@ -21,8 +21,12 @@ export default class AccountExpiry { return this.expiry.format('L LTS'); } + public durationUntilExpiry(): string { + return this.expiry.fromNow(true); + } + public remainingTime(): string { - const duration = this.expiry.fromNow(true); + const duration = this.durationUntilExpiry(); return sprintf( // TRANSLATORS: The remaining time left on the account displayed across the app. |
