diff options
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/index.ts | 5 | ||||
| -rw-r--r-- | gui/src/main/notification-controller.ts | 135 | ||||
| -rw-r--r-- | gui/src/main/user-interface.ts | 8 | ||||
| -rw-r--r-- | gui/src/shared/notifications/account-expired.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/notifications/block-when-disconnected.ts | 1 | ||||
| -rw-r--r-- | gui/src/shared/notifications/close-to-account-expiry.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/notifications/connected.ts | 3 | ||||
| -rw-r--r-- | gui/src/shared/notifications/connecting.ts | 4 | ||||
| -rw-r--r-- | gui/src/shared/notifications/disconnected.ts | 1 | ||||
| -rw-r--r-- | gui/src/shared/notifications/error.ts | 1 | ||||
| -rw-r--r-- | gui/src/shared/notifications/inconsistent-version.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/notifications/notification.ts | 6 | ||||
| -rw-r--r-- | gui/src/shared/notifications/reconnecting.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/notifications/unsupported-version.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/notifications/update-available.ts | 2 |
15 files changed, 91 insertions, 85 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 40f9054958..e6dc2901af 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -947,10 +947,7 @@ class ApplicationMain }; // UserInterfaceDelegate - public cancelPendingNotifications = () => - this.notificationController.cancelPendingNotifications(); - public resetTunnelStateAnnouncements = () => - this.notificationController.resetTunnelStateAnnouncements(); + public closeActiveNotifications = () => this.notificationController.closeActiveNotifications(); public isUnpinnedWindow = () => this.settings.gui.unpinnedWindow; public updateAccountData = () => this.account.updateAccountData(); public getAccountData = () => this.account.accountData; diff --git a/gui/src/main/notification-controller.ts b/gui/src/main/notification-controller.ts index cf703e431c..7682c60d15 100644 --- a/gui/src/main/notification-controller.ts +++ b/gui/src/main/notification-controller.ts @@ -1,4 +1,4 @@ -import { app, NativeImage, nativeImage, Notification } from 'electron'; +import { app, NativeImage, nativeImage, Notification as ElectronNotification } from 'electron'; import os from 'os'; import path from 'path'; @@ -15,6 +15,14 @@ import { SystemNotificationProvider, SystemNotificationSeverityType, } from '../shared/notifications/notification'; +import { Scheduler } from '../shared/scheduler'; + +const THROTTLE_DELAY = 500; + +export interface Notification { + specification: SystemNotification; + notification: ElectronNotification; +} export interface NotificationSender { notify(notification: SystemNotification): void; @@ -26,10 +34,11 @@ export interface NotificationControllerDelegate { } export default class NotificationController { - private lastTunnelStateAnnouncement?: { body: string; notification: Notification }; private reconnecting = false; - private previousNotifications: { [key: string]: boolean } = {}; - private activeNotifications: Notification[] = []; + private presentedNotifications: { [key: string]: boolean } = {}; + private activeNotifications: Set<Notification> = new Set(); + private closedNotifications: Set<Notification> = new Set(); + private throttledNotifications: Map<SystemNotification, Scheduler> = new Map(); private notificationTitle = process.platform === 'linux' ? app.name : ''; private notificationIcon?: NativeImage; @@ -74,11 +83,7 @@ export default class NotificationController { const notification = notificationProvider.getSystemNotification(); if (notification) { - this.showTunnelStateNotification( - notification, - isWindowVisible, - areSystemNotificationsEnabled, - ); + this.notify(notification, isWindowVisible, areSystemNotificationsEnabled); } else { log.error( `Notification providers mayDisplay() returned true but getSystemNotification() returned undefined for ${notificationProvider.constructor.name}`, @@ -90,36 +95,66 @@ export default class NotificationController { tunnelState.state === 'disconnecting' && tunnelState.details === 'reconnect'; } - public cancelPendingNotifications() { - for (const notification of this.activeNotifications) { - notification.close(); - } - } - - public resetTunnelStateAnnouncements() { - this.lastTunnelStateAnnouncement = undefined; + public closeActiveNotifications() { + this.activeNotifications.forEach((notification) => notification.notification.close()); } public notify( systemNotification: SystemNotification, - isWindowVisible: boolean, - areSystemNotificationsEnabled: boolean, + windowVisible: boolean, + infoNotificationsEnabled: boolean, ) { - if ( - this.evaluateNotification(systemNotification, isWindowVisible, areSystemNotificationsEnabled) - ) { - const notification = this.createNotification(systemNotification); - this.addActiveNotification(notification); - notification.show(); + if (!this.evaluateNotification(systemNotification, windowVisible, infoNotificationsEnabled)) { + return; + } + + // Cancel throttled notifications within the same category + if (systemNotification.category !== undefined) { + this.throttledNotifications.forEach((scheduler, specification) => { + if (specification.category === systemNotification.category) { + scheduler.cancel(); + this.throttledNotifications.delete(specification); + } + }); + } + + if (systemNotification.throttle) { + const scheduler = new Scheduler(); + scheduler.schedule(() => { + this.throttledNotifications.delete(systemNotification); + this.notifyImpl(systemNotification); + }, THROTTLE_DELAY); - return notification; + this.throttledNotifications.set(systemNotification, scheduler); } else { - return; + this.notifyImpl(systemNotification); + } + } + + private notifyImpl(systemNotification: SystemNotification): Notification { + // Remove notifications in the same category if specified + if (systemNotification.category !== undefined) { + this.activeNotifications.forEach((notification) => { + if (notification.specification.category === systemNotification.category) { + notification.notification.close(); + } + }); + } + + const notification = this.createNotification(systemNotification); + this.addActiveNotification(notification); + notification.notification.show(); + + // Close notification of low severity automatically + if (systemNotification.severity === SystemNotificationSeverityType.info) { + setTimeout(() => notification.notification.close(), 4000); } + + return notification; } - private createNotification(systemNotification: SystemNotification) { - const notification = new Notification({ + private createNotification(systemNotification: SystemNotification): Notification { + const notification = new ElectronNotification({ title: this.notificationTitle, body: systemNotification.message, silent: true, @@ -148,7 +183,7 @@ export default class NotificationController { } } - return notification; + return { specification: systemNotification, notification }; } private performAction(action?: NotificationAction) { @@ -157,44 +192,14 @@ export default class NotificationController { } } - private showTunnelStateNotification( - systemNotification: SystemNotification, - isWindowVisible: boolean, - areSystemNotificationsEnabled: boolean, - ) { - const message = systemNotification.message; - const lastAnnouncement = this.lastTunnelStateAnnouncement; - const sameAsLastNotification = lastAnnouncement && lastAnnouncement.body === message; - - if (sameAsLastNotification) { - return; - } - - if (lastAnnouncement) { - lastAnnouncement.notification.close(); - } - - const newNotification = this.notify( - systemNotification, - isWindowVisible, - areSystemNotificationsEnabled, - ); - - if (newNotification) { - this.lastTunnelStateAnnouncement = { - body: message, - notification: newNotification, - }; - } - } - private addActiveNotification(notification: Notification) { - notification.on('close', () => this.removeActiveNotification(notification)); - this.activeNotifications.push(notification); + notification.notification.on('close', () => this.removeActiveNotification(notification)); + this.activeNotifications.add(notification); } private removeActiveNotification(notification: Notification) { - this.activeNotifications = this.activeNotifications.filter((value) => value !== notification); + this.activeNotifications.delete(notification); + this.closedNotifications.add(notification); } private evaluateNotification( @@ -217,7 +222,7 @@ export default class NotificationController { } private suppressDueToAlreadyPresented(notification: SystemNotification) { - const presented = this.previousNotifications; + const presented = this.presentedNotifications; if (notification.presentOnce?.value) { if (presented[notification.presentOnce.name]) { return true; diff --git a/gui/src/main/user-interface.ts b/gui/src/main/user-interface.ts index 78af002c58..7013397baa 100644 --- a/gui/src/main/user-interface.ts +++ b/gui/src/main/user-interface.ts @@ -25,8 +25,7 @@ import WindowController, { WindowControllerDelegate } from './window-controller' const execAsync = promisify(exec); export interface UserInterfaceDelegate { - cancelPendingNotifications(): void; - resetTunnelStateAnnouncements(): void; + closeActiveNotifications(): void; updateAccountData(): void; connectTunnel(): void; reconnectTunnel(): void; @@ -319,7 +318,7 @@ export default class UserInterface implements WindowControllerDelegate { this.blurNavigationResetScheduler.cancel(); // cancel notifications when window appears - this.delegate.cancelPendingNotifications(); + this.delegate.closeActiveNotifications(); const accountData = this.delegate.getAccountData(); if (!accountData || closeToExpiry(accountData.expiry, 4) || hasExpired(accountData.expiry)) { @@ -329,9 +328,6 @@ export default class UserInterface implements WindowControllerDelegate { this.windowController.window?.on('blur', () => { IpcMainEventChannel.window.notifyFocus?.(false); - - // ensure notification guard is reset - this.delegate.resetTunnelStateAnnouncements(); }); // Use hide instead of blur to prevent the navigation reset from happening when bluring an diff --git a/gui/src/shared/notifications/account-expired.ts b/gui/src/shared/notifications/account-expired.ts index 42e40aa491..a7af4f2c8b 100644 --- a/gui/src/shared/notifications/account-expired.ts +++ b/gui/src/shared/notifications/account-expired.ts @@ -4,6 +4,7 @@ import { TunnelState } from '../daemon-rpc-types'; import { messages } from '../gettext'; import { SystemNotification, + SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, } from './notification'; @@ -27,6 +28,7 @@ export class AccountExpiredNotificationProvider implements SystemNotificationPro public getSystemNotification(): SystemNotification { return { message: messages.pgettext('notifications', 'Account is out of time'), + category: SystemNotificationCategory.expiry, severity: SystemNotificationSeverityType.high, presentOnce: { value: true, name: this.constructor.name }, action: { diff --git a/gui/src/shared/notifications/block-when-disconnected.ts b/gui/src/shared/notifications/block-when-disconnected.ts index 3d7ec1e509..c0b2f4e0f1 100644 --- a/gui/src/shared/notifications/block-when-disconnected.ts +++ b/gui/src/shared/notifications/block-when-disconnected.ts @@ -37,7 +37,6 @@ export class BlockWhenDisconnectedNotificationProvider message, severity: SystemNotificationSeverityType.info, category: SystemNotificationCategory.tunnelState, - replaceByCategory: true, }; } diff --git a/gui/src/shared/notifications/close-to-account-expiry.ts b/gui/src/shared/notifications/close-to-account-expiry.ts index 2a9fc1a384..a3f5e749ad 100644 --- a/gui/src/shared/notifications/close-to-account-expiry.ts +++ b/gui/src/shared/notifications/close-to-account-expiry.ts @@ -8,6 +8,7 @@ import { InAppNotification, InAppNotificationProvider, SystemNotification, + SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, } from './notification'; @@ -39,6 +40,7 @@ export class CloseToAccountExpiryNotificationProvider return { message, + category: SystemNotificationCategory.expiry, severity: SystemNotificationSeverityType.medium, action: { type: 'open-url', diff --git a/gui/src/shared/notifications/connected.ts b/gui/src/shared/notifications/connected.ts index d695df1c95..c66339fe9e 100644 --- a/gui/src/shared/notifications/connected.ts +++ b/gui/src/shared/notifications/connected.ts @@ -32,9 +32,8 @@ export class ConnectedNotificationProvider implements SystemNotificationProvider return { message, - severity: SystemNotificationSeverityType.low, + severity: SystemNotificationSeverityType.info, category: SystemNotificationCategory.tunnelState, - replaceByCategory: true, }; } else { return undefined; diff --git a/gui/src/shared/notifications/connecting.ts b/gui/src/shared/notifications/connecting.ts index e722e7fb95..214e015e10 100644 --- a/gui/src/shared/notifications/connecting.ts +++ b/gui/src/shared/notifications/connecting.ts @@ -42,9 +42,9 @@ export class ConnectingNotificationProvider return { message, - severity: SystemNotificationSeverityType.low, + severity: SystemNotificationSeverityType.info, category: SystemNotificationCategory.tunnelState, - replaceByCategory: true, + throttle: true, }; } else { return undefined; diff --git a/gui/src/shared/notifications/disconnected.ts b/gui/src/shared/notifications/disconnected.ts index 531c95d015..874cb11b3e 100644 --- a/gui/src/shared/notifications/disconnected.ts +++ b/gui/src/shared/notifications/disconnected.ts @@ -23,7 +23,6 @@ export class DisconnectedNotificationProvider implements SystemNotificationProvi message: messages.pgettext('notifications', 'Disconnected and unsecure'), severity: SystemNotificationSeverityType.info, category: SystemNotificationCategory.tunnelState, - replaceByCategory: true, }; } } diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts index e878f5ef19..67267a494e 100644 --- a/gui/src/shared/notifications/error.ts +++ b/gui/src/shared/notifications/error.ts @@ -49,7 +49,6 @@ export class ErrorNotificationProvider ? SystemNotificationSeverityType.low : SystemNotificationSeverityType.high, category: SystemNotificationCategory.tunnelState, - replaceByCategory: false, }; } else { return undefined; diff --git a/gui/src/shared/notifications/inconsistent-version.ts b/gui/src/shared/notifications/inconsistent-version.ts index 3334f9e99c..f6f3cf4166 100644 --- a/gui/src/shared/notifications/inconsistent-version.ts +++ b/gui/src/shared/notifications/inconsistent-version.ts @@ -3,6 +3,7 @@ import { InAppNotification, InAppNotificationProvider, SystemNotification, + SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, } from './notification'; @@ -20,6 +21,7 @@ export class InconsistentVersionNotificationProvider public getSystemNotification(): SystemNotification { return { message: messages.pgettext('notifications', 'App is out of sync. Please quit and restart.'), + category: SystemNotificationCategory.version, severity: SystemNotificationSeverityType.high, presentOnce: { value: true, name: this.constructor.name }, suppressInDevelopment: true, diff --git a/gui/src/shared/notifications/notification.ts b/gui/src/shared/notifications/notification.ts index 873f7b2f4a..2fd0a47a37 100644 --- a/gui/src/shared/notifications/notification.ts +++ b/gui/src/shared/notifications/notification.ts @@ -16,6 +16,8 @@ export enum SystemNotificationSeverityType { export enum SystemNotificationCategory { tunnelState, + expiry, + version, } interface NotificationProvider { @@ -25,8 +27,8 @@ interface NotificationProvider { export interface SystemNotification { message: string; severity: SystemNotificationSeverityType; - category?: SystemNotificationCategory; - replaceByCategory?: boolean; + category: SystemNotificationCategory; + throttle?: boolean; presentOnce?: { value: boolean; name: string }; suppressInDevelopment?: boolean; action?: NotificationAction; diff --git a/gui/src/shared/notifications/reconnecting.ts b/gui/src/shared/notifications/reconnecting.ts index 08eb36b18d..43491322f0 100644 --- a/gui/src/shared/notifications/reconnecting.ts +++ b/gui/src/shared/notifications/reconnecting.ts @@ -22,7 +22,7 @@ export class ReconnectingNotificationProvider message: messages.pgettext('notifications', 'Reconnecting'), severity: SystemNotificationSeverityType.info, category: SystemNotificationCategory.tunnelState, - replaceByCategory: true, + throttle: true, }; } diff --git a/gui/src/shared/notifications/unsupported-version.ts b/gui/src/shared/notifications/unsupported-version.ts index 3a012e1aad..6c9c380595 100644 --- a/gui/src/shared/notifications/unsupported-version.ts +++ b/gui/src/shared/notifications/unsupported-version.ts @@ -4,6 +4,7 @@ import { InAppNotification, InAppNotificationProvider, SystemNotification, + SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, } from './notification'; @@ -26,6 +27,7 @@ export class UnsupportedVersionNotificationProvider public getSystemNotification(): SystemNotification { return { message: this.getMessage(), + category: SystemNotificationCategory.version, severity: SystemNotificationSeverityType.high, action: { type: 'open-url', diff --git a/gui/src/shared/notifications/update-available.ts b/gui/src/shared/notifications/update-available.ts index 2205274cc6..8e6bd7e76e 100644 --- a/gui/src/shared/notifications/update-available.ts +++ b/gui/src/shared/notifications/update-available.ts @@ -6,6 +6,7 @@ import { InAppNotification, InAppNotificationProvider, SystemNotification, + SystemNotificationCategory, SystemNotificationProvider, SystemNotificationSeverityType, } from './notification'; @@ -40,6 +41,7 @@ export class UpdateAvailableNotificationProvider public getSystemNotification(): SystemNotification { return { message: this.systemMessage(), + category: SystemNotificationCategory.version, severity: SystemNotificationSeverityType.medium, action: { type: 'open-url', |
