summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/index.ts5
-rw-r--r--gui/src/main/notification-controller.ts135
-rw-r--r--gui/src/main/user-interface.ts8
-rw-r--r--gui/src/shared/notifications/account-expired.ts2
-rw-r--r--gui/src/shared/notifications/block-when-disconnected.ts1
-rw-r--r--gui/src/shared/notifications/close-to-account-expiry.ts2
-rw-r--r--gui/src/shared/notifications/connected.ts3
-rw-r--r--gui/src/shared/notifications/connecting.ts4
-rw-r--r--gui/src/shared/notifications/disconnected.ts1
-rw-r--r--gui/src/shared/notifications/error.ts1
-rw-r--r--gui/src/shared/notifications/inconsistent-version.ts2
-rw-r--r--gui/src/shared/notifications/notification.ts6
-rw-r--r--gui/src/shared/notifications/reconnecting.ts2
-rw-r--r--gui/src/shared/notifications/unsupported-version.ts2
-rw-r--r--gui/src/shared/notifications/update-available.ts2
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',