summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-08-01 17:32:52 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-08-08 08:12:47 -0300
commitb7fccae454943a113022fe4403ca9a6920ffec13 (patch)
tree8bcc6709f88652f47bfc9434acc1f6d1291c2af6
parent2c4d95cea476a0f038b731e13088d547465971c3 (diff)
downloadmullvadvpn-b7fccae454943a113022fe4403ca9a6920ffec13.tar.xz
mullvadvpn-b7fccae454943a113022fe4403ca9a6920ffec13.zip
Implement system notifications of connection state
-rw-r--r--app/app.js20
-rw-r--r--app/notification-controller.js25
-rw-r--r--flow-libs/notification.js.flow51
3 files changed, 96 insertions, 0 deletions
diff --git a/app/app.js b/app/app.js
index 501bfac780..3171d69b76 100644
--- a/app/app.js
+++ b/app/app.js
@@ -11,6 +11,7 @@ import makeRoutes from './routes';
import { log } from './lib/platform';
import ReconnectionBackoff from './lib/reconnection-backoff';
import { DaemonRpc } from './lib/daemon-rpc';
+import NotificationController from './notification-controller';
import { setShutdownHandler } from './shutdown-handler';
import { NoAccountError } from './errors';
@@ -30,6 +31,7 @@ import type { ConnectionState } from './redux/connection/reducers';
import type { TrayIconType } from './tray-icon-controller';
export default class AppRenderer {
+ _notificationController = new NotificationController();
_daemonRpc: DaemonRpcProtocol = new DaemonRpc();
_reconnectBackoff = new ReconnectionBackoff();
_credentials: ?RpcCredentials;
@@ -523,6 +525,24 @@ export default class AppRenderer {
}
this._updateTrayIcon(connectionState);
+ this._showNotification(connectionState);
+ }
+
+ _showNotification(connectionState: ConnectionState) {
+ switch (connectionState) {
+ case 'connecting':
+ this._notificationController.show('Connecting');
+ break;
+ case 'connected':
+ this._notificationController.show('Secured');
+ break;
+ case 'disconnected':
+ this._notificationController.show('Unsecured');
+ break;
+ default:
+ log.error(`Unexpected ConnectionState: ${(connectionState: empty)}`);
+ return;
+ }
}
async _authenticate(sharedSecret: string) {
diff --git a/app/notification-controller.js b/app/notification-controller.js
new file mode 100644
index 0000000000..62d6381e72
--- /dev/null
+++ b/app/notification-controller.js
@@ -0,0 +1,25 @@
+// @flow
+import { remote } from 'electron';
+
+export default class NotificationController {
+ _activeNotification: ?Notification;
+
+ show(message: string) {
+ const lastNotification = this._activeNotification;
+ const newNotification = new Notification(remote.app.getName(), { body: message, silent: true });
+
+ this._activeNotification = newNotification;
+
+ 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.
+ if (this._activeNotification !== newNotification) {
+ newNotification.close();
+ }
+ });
+
+ if (lastNotification) {
+ lastNotification.close();
+ }
+ }
+}
diff --git a/flow-libs/notification.js.flow b/flow-libs/notification.js.flow
new file mode 100644
index 0000000000..bf5dda3af2
--- /dev/null
+++ b/flow-libs/notification.js.flow
@@ -0,0 +1,51 @@
+/* Notification */
+type NotificationPermission = 'default' | 'denied' | 'granted';
+type NotificationDirection = 'auto' | 'ltr' | 'rtl';
+type VibratePattern = number | Array<number>;
+type NotificationAction = { action: string, title: string, icon?: string };
+type NotificationOptions = {
+ dir: NotificationDirection,
+ lang: string,
+ body: string,
+ tag: string,
+ image: string,
+ icon: string,
+ badge: string,
+ sound: string,
+ vibrate: VibratePattern,
+ timestamp: number,
+ renotify: boolean,
+ silent: boolean,
+ requireInteraction: boolean,
+ data: ?any,
+ actions: Array<NotificationAction>,
+};
+
+declare class Notification extends EventTarget {
+ constructor(title: string, options?: $Shape<NotificationOptions>): void;
+ static permission: NotificationPermission;
+ static requestPermission(
+ callback?: (perm: NotificationPermission) => mixed,
+ ): Promise<NotificationPermission>;
+ static maxActions: number;
+ onclick: (evt: Event) => any;
+ onerror: (evt: Event) => any;
+ title: string;
+ dir: NotificationDirection;
+ lang: string;
+ body: string;
+ tag: string;
+ image: string;
+ icon: string;
+ badge: string;
+ sound: string;
+ vibrate: Array<number>;
+ timestamp: number;
+ renotify: boolean;
+ silent: boolean;
+ requireInteraction: boolean;
+ data: any;
+ actions: Array<NotificationAction>;
+
+ close(): void;
+}