diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-06-15 20:19:14 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-06-24 11:24:34 +0200 |
| commit | 60f8742caaf908c3ab1113bbe6903d12ca961aa3 (patch) | |
| tree | e4aef6a24979c54e9189d6c12f64a99ca8389243 /gui/src | |
| parent | 2341e2eea669ed29e1146d012fa7a4e5dc9bebf6 (diff) | |
| download | mullvadvpn-60f8742caaf908c3ab1113bbe6903d12ca961aa3.tar.xz mullvadvpn-60f8742caaf908c3ab1113bbe6903d12ca961aa3.zip | |
Add system notification for when account has expired
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/index.ts | 51 | ||||
| -rw-r--r-- | gui/src/renderer/components/NotificationArea.tsx | 6 | ||||
| -rw-r--r-- | gui/src/shared/notifications/account-expired.ts | 34 | ||||
| -rw-r--r-- | gui/src/shared/notifications/close-to-account-expiry.ts (renamed from gui/src/shared/notifications/account-expiry.ts) | 10 | ||||
| -rw-r--r-- | gui/src/shared/notifications/notification.ts | 3 |
5 files changed, 75 insertions, 29 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index a0e17fd9be..668b975b46 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -2,6 +2,7 @@ import { execFile } from 'child_process'; import { app, BrowserWindow, ipcMain, Menu, nativeImage, screen, shell, 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 { hasExpired } from '../shared/account-expiry'; @@ -27,17 +28,18 @@ import { loadTranslations, messages } from '../shared/gettext'; import { SYSTEM_PREFERRED_LOCALE_KEY } from '../shared/gui-settings-state'; import { IpcMainEventChannel } from '../shared/ipc-event-channel'; import { - AccountExpiryNotificationProvider, - InconsistentVersionNotificationProvider, - UnsupportedVersionNotificationProvider, -} from '../shared/notifications/notification'; -import { backupLogFile, getLogsDirectory, getMainLogFile, getRendererLogFile, setupLogging, } from '../shared/logging'; +import { + AccountExpiredNotificationProvider, + CloseToAccountExpiryNotificationProvider, + InconsistentVersionNotificationProvider, + UnsupportedVersionNotificationProvider, +} from '../shared/notifications/notification'; import consumePromise from '../shared/promise'; import { Scheduler } from '../shared/scheduler'; import AccountDataCache from './account-data-cache'; @@ -165,7 +167,7 @@ class ApplicationMain { private wireguardPublicKey?: IWireguardPublicKey; - private accountExpiryNotificationTimeout?: NodeJS.Timeout; + private accountExpiryNotificationScheduler = new Scheduler(); private accountDataCache = new AccountDataCache( (accountToken) => { @@ -178,7 +180,7 @@ class ApplicationMain { IpcMainEventChannel.account.notify(this.windowController.webContents, accountData); } - this.notifyOfAccountExpiry(); + this.handleAccountExpiry(); }, ); @@ -1165,12 +1167,8 @@ class ApplicationMain { try { await this.daemonRpc.setAccount(); - if (this.accountExpiryNotificationTimeout) { - global.clearTimeout(this.accountExpiryNotificationTimeout); - this.accountExpiryNotificationTimeout = undefined; - } - this.autoConnectFallbackScheduler.cancel(); + this.accountExpiryNotificationScheduler.cancel(); } catch (error) { log.info(`Failed to logout: ${error.message}`); @@ -1220,19 +1218,30 @@ class ApplicationMain { } } - private notifyOfAccountExpiry() { + private handleAccountExpiry() { if (this.accountData) { - const notificationProvider = new AccountExpiryNotificationProvider({ + const expiredNotification = new AccountExpiredNotificationProvider({ + accountExpiry: this.accountData.expiry, + tunnelState: this.tunnelState, + }); + const closeToExpiryNotification = new CloseToAccountExpiryNotificationProvider({ accountExpiry: this.accountData.expiry, locale: this.locale, - tooSoon: this.accountExpiryNotificationTimeout !== undefined, + tooSoon: this.accountExpiryNotificationScheduler.isRunning, }); - if (notificationProvider.mayDisplay()) { - this.notificationController.notify(notificationProvider.getSystemNotification()); - this.accountExpiryNotificationTimeout = global.setTimeout(() => { - this.accountExpiryNotificationTimeout = undefined; - this.notifyOfAccountExpiry(); - }, 12 * 60 * 60 * 1000); // Every 12 hours + + if (expiredNotification.mayDisplay()) { + this.accountExpiryNotificationScheduler.cancel(); + this.notificationController.notify(expiredNotification.getSystemNotification()); + } else if (closeToExpiryNotification.mayDisplay()) { + this.notificationController.notify(closeToExpiryNotification.getSystemNotification()); + + const twelveHours = 12 * 60 * 60 * 1000; + const remainingMilliseconds = moment(this.accountData.expiry).diff(new Date()); + const delay = Math.min(twelveHours, remainingMilliseconds); + this.accountExpiryNotificationScheduler.schedule(() => { + this.handleAccountExpiry(); + }, delay); } } } diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx index b5153a54aa..a6d4699e6a 100644 --- a/gui/src/renderer/components/NotificationArea.tsx +++ b/gui/src/renderer/components/NotificationArea.tsx @@ -4,7 +4,7 @@ import React, { useCallback } from 'react'; import { useSelector } from 'react-redux'; import { Types } from 'reactxp'; import { - AccountExpiryNotificationProvider, + CloseToAccountExpiryNotificationProvider, BlockWhenDisconnectedNotificationProvider, ConnectingNotificationProvider, ErrorNotificationProvider, @@ -51,7 +51,9 @@ export default function NotificationArea(props: IProps) { ]; if (accountExpiry) { - notificationProviders.push(new AccountExpiryNotificationProvider({ accountExpiry, locale })); + notificationProviders.push( + new CloseToAccountExpiryNotificationProvider({ accountExpiry, locale }), + ); } const notificationProvider = notificationProviders.find((notification) => diff --git a/gui/src/shared/notifications/account-expired.ts b/gui/src/shared/notifications/account-expired.ts new file mode 100644 index 0000000000..ea600aa577 --- /dev/null +++ b/gui/src/shared/notifications/account-expired.ts @@ -0,0 +1,34 @@ +import { links } from '../../config.json'; +import { hasExpired } from '../account-expiry'; +import { TunnelState } from '../daemon-rpc-types'; +import { messages } from '../gettext'; +import { SystemNotification, SystemNotificationProvider } from './notification'; + +interface AccountExpiredNotificaitonContext { + accountExpiry: string; + tunnelState: TunnelState; +} + +export class AccountExpiredNotificationProvider implements SystemNotificationProvider { + public constructor(private context: AccountExpiredNotificaitonContext) {} + + public mayDisplay() { + // Only show when disconnected since the error state handles this if the connection is closed + // due to account expiry. + return ( + this.context.tunnelState.state === 'disconnected' && hasExpired(this.context.accountExpiry) + ); + } + + public getSystemNotification(): SystemNotification { + return { + message: messages.pgettext( + 'notifications', + 'You have no more VPN time left on this account.', + ), + critical: true, + presentOnce: { value: true, name: this.constructor.name }, + action: { type: 'open-url', url: links.purchase, withAuth: true }, + }; + } +} diff --git a/gui/src/shared/notifications/account-expiry.ts b/gui/src/shared/notifications/close-to-account-expiry.ts index db536c7405..3cf3d82826 100644 --- a/gui/src/shared/notifications/account-expiry.ts +++ b/gui/src/shared/notifications/close-to-account-expiry.ts @@ -2,7 +2,7 @@ import moment from 'moment'; import { sprintf } from 'sprintf-js'; import { links } from '../../config.json'; import { messages } from '../../shared/gettext'; -import { hasExpired, formatRemainingTime } from '../account-expiry'; +import { formatDurationUntilExpiry, formatRemainingTime, hasExpired } from '../account-expiry'; import { InAppNotification, InAppNotificationProvider, @@ -10,15 +10,15 @@ import { SystemNotificationProvider, } from './notification'; -interface AccountExpiryContext { +interface CloseToAccountExpiryNotificationContext { accountExpiry: string; locale: string; tooSoon?: boolean; } -export class AccountExpiryNotificationProvider +export class CloseToAccountExpiryNotificationProvider implements InAppNotificationProvider, SystemNotificationProvider { - public constructor(private context: AccountExpiryContext) {} + public constructor(private context: CloseToAccountExpiryNotificationContext) {} public mayDisplay() { const willHaveExpiredInThreeDays = moment(this.context.accountExpiry).isSameOrBefore( @@ -37,7 +37,7 @@ export class AccountExpiryNotificationProvider // TRANSLATORS: %(duration)s - remaining time, e.g. "2 days" messages.pgettext('notifications', 'Account credit expires in %(duration)s'), { - duration: formatRemainingTime(this.context.accountExpiry, this.context.locale), + duration: formatDurationUntilExpiry(this.context.accountExpiry, this.context.locale), }, ); diff --git a/gui/src/shared/notifications/notification.ts b/gui/src/shared/notifications/notification.ts index ea067ea45b..98b50d4d24 100644 --- a/gui/src/shared/notifications/notification.ts +++ b/gui/src/shared/notifications/notification.ts @@ -29,7 +29,8 @@ export interface InAppNotificationProvider extends NotificationProvider { getInAppNotification(): InAppNotification | undefined; } -export * from './account-expiry'; +export * from './account-expired'; +export * from './close-to-account-expiry'; export * from './block-when-disconnected'; export * from './connected'; export * from './connecting'; |
