summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-08-12 13:17:20 +0200
committerOskar Nyberg <oskar@mullvad.net>2022-08-22 08:34:37 +0200
commit33547846ab4b7594d23b1772fea35e30e0b460fc (patch)
tree2ca36afbe949dcb6db3d0f66aceac4340141fb77 /gui/src
parent8feded87c6f13d4caaeca6114b1cd964a4b9a051 (diff)
downloadmullvadvpn-33547846ab4b7594d23b1772fea35e30e0b460fc.tar.xz
mullvadvpn-33547846ab4b7594d23b1772fea35e30e0b460fc.zip
Improve how information is passed between components in main process
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/account.ts25
-rw-r--r--gui/src/main/daemon-rpc.ts24
-rw-r--r--gui/src/main/index.ts132
-rw-r--r--gui/src/main/notification-controller.ts48
-rw-r--r--gui/src/main/tunnel-state.ts4
-rw-r--r--gui/src/main/user-interface.ts102
-rw-r--r--gui/src/main/version.ts13
7 files changed, 205 insertions, 143 deletions
diff --git a/gui/src/main/account.ts b/gui/src/main/account.ts
index e3509cd98d..00a3bf0dfd 100644
--- a/gui/src/main/account.ts
+++ b/gui/src/main/account.ts
@@ -11,21 +11,21 @@ import log from '../shared/logging';
import {
AccountExpiredNotificationProvider,
CloseToAccountExpiryNotificationProvider,
- SystemNotification,
} from '../shared/notifications/notification';
import { Scheduler } from '../shared/scheduler';
import AccountDataCache from './account-data-cache';
import { DaemonRpc } from './daemon-rpc';
import { InvalidAccountError } from './errors';
import { IpcMainEventChannel } from './ipc-event-channel';
+import { NotificationSender } from './notification-controller';
+import { TunnelStateProvider } from './tunnel-state';
-export interface AccountDelegate {
- notify(notification: SystemNotification): void;
- getTunnelState(): TunnelState;
+export interface LocaleProvider {
getLocale(): string;
- isPerformingPostUpgradeCheck(): boolean;
- performPostUpgradeCheck(): void;
- setTrayContextMenu(): void;
+}
+
+export interface AccountDelegate {
+ onDeviceEvent(): void;
}
export default class Account {
@@ -47,7 +47,10 @@ export default class Account {
private deviceStateValue?: DeviceState;
- public constructor(private delegate: AccountDelegate, private daemonRpc: DaemonRpc) {}
+ public constructor(
+ private delegate: AccountDelegate & TunnelStateProvider & LocaleProvider & NotificationSender,
+ private daemonRpc: DaemonRpc,
+ ) {}
public get accountData() {
return this.accountDataValue;
@@ -123,10 +126,6 @@ export default class Account {
public handleDeviceEvent(deviceEvent: DeviceEvent) {
this.deviceStateValue = deviceEvent.deviceState;
- if (this.delegate.isPerformingPostUpgradeCheck()) {
- void this.delegate.performPostUpgradeCheck();
- }
-
switch (deviceEvent.deviceState.type) {
case 'logged in':
this.accountDataCache.fetch(deviceEvent.deviceState.accountAndDevice.accountToken);
@@ -138,7 +137,7 @@ export default class Account {
}
void this.updateAccountHistory();
- this.delegate.setTrayContextMenu();
+ this.delegate.onDeviceEvent();
IpcMainEventChannel.account.notifyDevice?.(deviceEvent);
}
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 424c765d53..950cf1a4a3 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -80,7 +80,10 @@ const invalidErrorStateCause = new Error(
);
export class ConnectionObserver {
- constructor(private openHandler: () => void, private closeHandler: (error?: Error) => void) {}
+ constructor(
+ private openHandler: () => void,
+ private closeHandler: (wasConnected: boolean, error?: Error) => void,
+ ) {}
// Only meant to be called by DaemonRpc
// @internal
@@ -90,8 +93,8 @@ export class ConnectionObserver {
// Only meant to be called by DaemonRpc
// @internal
- public onClose = (error?: Error) => {
- this.closeHandler(error);
+ public onClose = (wasConnected: boolean, error?: Error) => {
+ this.closeHandler(wasConnected, error);
};
}
@@ -152,7 +155,7 @@ export class DaemonRpc {
return new Promise((resolve, reject) => {
this.client.waitForReady(this.deadlineFromNow(), (error) => {
if (error) {
- this.connectionObservers.forEach((observer) => observer.onClose(error));
+ this.onClose(error);
this.ensureConnectivity();
reject(error);
} else {
@@ -644,6 +647,13 @@ export class DaemonRpc {
}
}
+ private onClose(error?: Error) {
+ const wasConnected = this.isConnectedValue;
+ this.isConnectedValue = false;
+
+ this.connectionObservers.forEach((observer) => observer.onClose(wasConnected, error));
+ }
+
private removeSubscription(id: number) {
const subscription = this.subscriptions.get(id);
if (subscription !== undefined) {
@@ -686,8 +696,7 @@ export class DaemonRpc {
}
const wasConnected = this.isConnected;
if (this.channelDisconnected(currentState)) {
- this.connectionObservers.forEach((observer) => observer.onClose());
- this.isConnectedValue = false;
+ this.onClose();
// Try and reconnect in case
void this.connect().catch((error) => {
log.error(`Failed to reconnect - ${error}`);
@@ -730,8 +739,7 @@ export class DaemonRpc {
this.reconnectionTimeout = setTimeout(() => {
const lastState = this.client.getChannel().getConnectivityState(true);
if (this.channelDisconnected(lastState)) {
- this.connectionObservers.forEach((observer) => observer.onClose());
- this.isConnectedValue = false;
+ this.onClose();
}
if (!this.isConnected) {
void this.connect().catch((error) => {
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 3fca018236..99aaaf5a8b 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -1,5 +1,5 @@
import { exec } from 'child_process';
-import { app, dialog, nativeTheme, session, shell, systemPreferences } from 'electron';
+import { app, nativeTheme, session, shell, systemPreferences } from 'electron';
import fs from 'fs';
import * as path from 'path';
import util from 'util';
@@ -15,7 +15,7 @@ import { IChangelog, IHistoryObject, ScrollPositions } from '../shared/ipc-types
import log, { ConsoleOutput, Logger } from '../shared/logging';
import { LogLevel } from '../shared/logging-types';
import { SystemNotification } from '../shared/notifications/notification';
-import Account, { AccountDelegate } from './account';
+import Account, { AccountDelegate, LocaleProvider } from './account';
import { getOpenAtLogin } from './autostart';
import { readChangelog } from './changelog';
import { ConnectionObserver, DaemonRpc, SubscriptionListener } from './daemon-rpc';
@@ -33,14 +33,20 @@ import {
IpcInput,
OLD_LOG_FILES,
} from './logging';
-import NotificationController, { NotificationControllerDelegate } from './notification-controller';
+import NotificationController, {
+ NotificationControllerDelegate,
+ NotificationSender,
+} from './notification-controller';
import * as problemReport from './problem-report';
import ReconnectionBackoff from './reconnection-backoff';
import RelayList from './relay-list';
import Settings, { SettingsDelegate } from './settings';
-import TunnelStateHandler, { TunnelStateHandlerDelegate } from './tunnel-state';
+import TunnelStateHandler, {
+ TunnelStateHandlerDelegate,
+ TunnelStateProvider,
+} from './tunnel-state';
import UserInterface, { UserInterfaceDelegate } from './user-interface';
-import Version, { VersionDelegate } from './version';
+import Version from './version';
const execAsync = util.promisify(exec);
@@ -63,32 +69,28 @@ export enum AppQuitStage {
const ALLOWED_PERMISSIONS = ['clipboard-sanitized-write'];
const SANDBOX_DISABLED = app.commandLine.hasSwitch('no-sandbox');
-const QUIT_WITHOUT_DISCONNECT = process.argv.includes(CommandLineOptions.quitWithoutDisconnect);
-const FORCE_SHOW_CHANGES = process.argv.includes(CommandLineOptions.showChanges);
-const NAVIGATION_RESET_DISABLED = process.argv.includes(CommandLineOptions.disableResetNavigation);
const UPDATE_NOTIFICATION_DISABLED = process.env.MULLVAD_DISABLE_UPDATE_NOTIFICATION === '1';
class ApplicationMain
implements
+ NotificationSender,
+ TunnelStateProvider,
+ LocaleProvider,
NotificationControllerDelegate,
UserInterfaceDelegate,
- VersionDelegate,
TunnelStateHandlerDelegate,
SettingsDelegate,
AccountDelegate {
private daemonRpc = new DaemonRpc();
+
private notificationController = new NotificationController(this);
- private version = new Version(this, UPDATE_NOTIFICATION_DISABLED);
+ private version = new Version(this, this.daemonRpc, UPDATE_NOTIFICATION_DISABLED);
private settings = new Settings(this, this.daemonRpc, this.version.currentVersion);
private relayList = new RelayList();
private userInterface?: UserInterface;
private account: Account = new Account(this, this.daemonRpc);
private tunnelState = new TunnelStateHandler(this);
- // True while file pickers are displayed which is used to decide if the Browser window should be
- // hidden when losing focus.
- private browsingFiles = false;
-
private daemonEventListener?: SubscriptionListener<DaemonEvent>;
private reconnectBackoff = new ReconnectionBackoff();
private beforeFirstDaemonConnection = true;
@@ -128,7 +130,10 @@ class ApplicationMain
// This ensures that only a single instance is running at the same time, but also exits if
// there's no already running instance when the quit without disconnect flag is supplied.
- if (!app.requestSingleInstanceLock() || QUIT_WITHOUT_DISCONNECT) {
+ if (
+ !app.requestSingleInstanceLock() ||
+ process.argv.includes(CommandLineOptions.quitWithoutDisconnect)
+ ) {
this.quitWithoutDisconnect();
return;
}
@@ -209,6 +214,14 @@ class ApplicationMain
public isLoggedIn = () => this.account.isLoggedIn();
+ public notify = (notification: SystemNotification) => {
+ this.notificationController.notify(
+ notification,
+ this.userInterface?.isWindowVisible() ?? false,
+ this.settings.gui.enableSystemNotifications,
+ );
+ };
+
private addSecondInstanceEventHandler() {
app.on('second-instance', (_event, argv, _workingDirectory) => {
if (argv.includes(CommandLineOptions.quitWithoutDisconnect)) {
@@ -416,7 +429,7 @@ class ApplicationMain
this,
this.daemonRpc,
SANDBOX_DISABLED,
- NAVIGATION_RESET_DISABLED,
+ process.argv.includes(CommandLineOptions.disableResetNavigation),
);
this.tunnelStateExpectation = new Expectation(async () => {
@@ -427,8 +440,11 @@ class ApplicationMain
);
await this.userInterface?.updateTrayTheme();
- this.userInterface?.setTrayContextMenu();
- this.userInterface?.setTrayTooltip();
+ this.userInterface?.updateTray(
+ this.account.isLoggedIn(),
+ this.tunnelState.tunnelState,
+ this.settings.blockWhenDisconnected,
+ );
if (process.platform === 'win32') {
nativeTheme.on('updated', async () => {
@@ -452,7 +468,10 @@ class ApplicationMain
}
}
- await this.userInterface.initializeWindow();
+ await this.userInterface.initializeWindow(
+ this.account.isLoggedIn(),
+ this.tunnelState.tunnelState,
+ );
};
private onDaemonConnected = async () => {
@@ -581,14 +600,10 @@ class ApplicationMain
}
};
- private onDaemonDisconnected = (error?: Error) => {
+ private onDaemonDisconnected = (wasConnected: boolean, error?: Error) => {
if (this.daemonEventListener) {
this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener);
}
- // make sure we were connected before to distinguish between a failed attempt to reconnect and
- // connection loss.
- const wasConnected = this.daemonRpc.isConnected;
-
// Reset the daemon event listener since it's going to be invalidated on disconnect
this.daemonEventListener = undefined;
@@ -596,9 +611,7 @@ class ApplicationMain
if (wasConnected) {
// update the tray icon to indicate that the computer is not secure anymore
- this.userInterface?.updateTrayIcon({ state: 'disconnected' }, false);
- this.userInterface?.setTrayContextMenu();
- this.userInterface?.setTrayTooltip();
+ this.userInterface?.updateTray(false, { state: 'disconnected' }, false);
// notify renderer process
IpcMainEventChannel.daemon.notifyDisconnected?.();
@@ -664,7 +677,8 @@ class ApplicationMain
const oldSettings = this.settings;
this.settings.handleNewSettings(newSettings);
- this.userInterface?.updateTrayIcon(
+ this.userInterface?.updateTray(
+ this.account.isLoggedIn(),
this.tunnelState.tunnelState,
newSettings.blockWhenDisconnected,
);
@@ -714,7 +728,7 @@ class ApplicationMain
windowsSplitTunnelingApplications: this.windowsSplitTunnelingApplications,
macOsScrollbarVisibility: this.macOsScrollbarVisibility,
changelog: this.changelog ?? [],
- forceShowChanges: FORCE_SHOW_CHANGES,
+ forceShowChanges: process.argv.includes(CommandLineOptions.showChanges),
navigationHistory: this.navigationHistory,
scrollPositions: this.scrollPositions,
}));
@@ -773,15 +787,6 @@ class ApplicationMain
await shell.openExternal(url);
}
});
- IpcMainEventChannel.app.handleShowOpenDialog(async (options) => {
- this.browsingFiles = true;
- const response = await dialog.showOpenDialog({
- defaultPath: app.getPath('home'),
- ...options,
- });
- this.browsingFiles = false;
- return response;
- });
IpcMainEventChannel.navigation.handleSetHistory((history) => {
this.navigationHistory = history;
@@ -791,6 +796,7 @@ class ApplicationMain
});
problemReport.registerIpcListeners();
+ this.userInterface!.registerIpcListeners();
this.settings.registerIpcListeners();
this.account.registerIpcListeners();
@@ -839,8 +845,11 @@ class ApplicationMain
relayLocations: relayLocationsTranslations,
};
- this.userInterface?.setTrayContextMenu();
- this.userInterface?.setTrayTooltip();
+ this.userInterface?.updateTray(
+ this.account.isLoggedIn(),
+ this.tunnelState.tunnelState,
+ this.settings.blockWhenDisconnected,
+ );
}
private blockPermissionRequests() {
@@ -949,8 +958,6 @@ class ApplicationMain
return shell.openExternal(url);
}
};
- public isWindowVisible = () => this.userInterface?.isWindowVisible() ?? false;
- public areSystemNotificationsEnabled = () => this.settings.gui.enableSystemNotifications;
// UserInterfaceDelegate
public cancelPendingNotifications = () =>
@@ -958,31 +965,24 @@ class ApplicationMain
public resetTunnelStateAnnouncements = () =>
this.notificationController.resetTunnelStateAnnouncements();
public isUnpinnedWindow = () => this.settings.gui.unpinnedWindow;
- public checkVolumes = () => this.daemonRpc.checkVolumes();
public getAppQuitStage = () => this.quitStage;
- public isConnectedToDaemon = () => this.daemonRpc.isConnected;
- public getTunnelState = () => this.tunnelState.tunnelState;
public updateAccountData = () => this.account.updateAccountData();
- public isBrowsingFiles = () => this.browsingFiles;
public getAccountData = () => this.account.accountData;
- // VersionDelegate
- public notify = (notification: SystemNotification) => {
- this.notificationController.notify(notification);
- };
- public getVersionInfo = () => this.daemonRpc.getVersionInfo();
-
// TunnelStateHandlerDelegate
public handleTunnelStateUpdate = (tunnelState: TunnelState) => {
- this.userInterface?.updateTrayIcon(tunnelState, this.settings.blockWhenDisconnected);
-
- this.userInterface?.setTrayContextMenu();
- this.userInterface?.setTrayTooltip();
+ this.userInterface?.updateTray(
+ this.account.isLoggedIn(),
+ tunnelState,
+ this.settings.blockWhenDisconnected,
+ );
this.notificationController.notifyTunnelState(
tunnelState,
this.settings.blockWhenDisconnected,
this.settings.splitTunnel.enableExclusions && this.settings.splitTunnel.appsList.length > 0,
+ this.userInterface?.isWindowVisible() ?? false,
+ this.settings.gui.enableSystemNotifications,
this.account.accountData?.expiry,
);
@@ -996,12 +996,26 @@ class ApplicationMain
// SettingsDelegate
public handleMonochromaticIconChange = (value: boolean) =>
this.userInterface?.setUseMonochromaticTrayIcon(value) ?? Promise.resolve();
- public handleUnpinnedWindowChange = () => void this.userInterface?.recreateWindow();
+ public handleUnpinnedWindowChange = () =>
+ void this.userInterface?.recreateWindow(
+ this.account.isLoggedIn(),
+ this.tunnelState.tunnelState,
+ );
// AccountDelegate
public getLocale = () => this.locale;
- public isPerformingPostUpgradeCheck = () => this.isPerformingPostUpgrade;
- public setTrayContextMenu = () => this.userInterface?.setTrayContextMenu();
+ public getTunnelState = () => this.tunnelState.tunnelState;
+ public onDeviceEvent = () => {
+ this.userInterface?.updateTray(
+ this.account.isLoggedIn(),
+ this.tunnelState.tunnelState,
+ this.settings.blockWhenDisconnected,
+ );
+
+ if (this.isPerformingPostUpgrade) {
+ void this.performPostUpgradeCheck();
+ }
+ };
/* eslint-enable @typescript-eslint/member-ordering */
}
diff --git a/gui/src/main/notification-controller.ts b/gui/src/main/notification-controller.ts
index a90090945f..06a05366a7 100644
--- a/gui/src/main/notification-controller.ts
+++ b/gui/src/main/notification-controller.ts
@@ -15,11 +15,13 @@ import {
SystemNotificationProvider,
} from '../shared/notifications/notification';
+export interface NotificationSender {
+ notify(notification: SystemNotification): void;
+}
+
export interface NotificationControllerDelegate {
openApp(): void;
openLink(url: string, withAuth?: boolean): Promise<void>;
- isWindowVisible(): boolean;
- areSystemNotificationsEnabled(): boolean;
}
export default class NotificationController {
@@ -52,6 +54,8 @@ export default class NotificationController {
tunnelState: TunnelState,
blockWhenDisconnected: boolean,
hasExcludedApps: boolean,
+ isWindowVisible: boolean,
+ areSystemNotificationsEnabled: boolean,
accountExpiry?: string,
) {
const notificationProviders: SystemNotificationProvider[] = [
@@ -70,7 +74,11 @@ export default class NotificationController {
const notification = notificationProvider.getSystemNotification();
if (notification) {
- this.showTunnelStateNotification(notification);
+ this.showTunnelStateNotification(
+ notification,
+ isWindowVisible,
+ areSystemNotificationsEnabled,
+ );
} else {
log.error(
`Notification providers mayDisplay() returned true but getSystemNotification() returned undefined for ${notificationProvider.constructor.name}`,
@@ -92,8 +100,14 @@ export default class NotificationController {
this.lastTunnelStateAnnouncement = undefined;
}
- public notify(systemNotification: SystemNotification) {
- if (this.evaluateNotification(systemNotification)) {
+ public notify(
+ systemNotification: SystemNotification,
+ isWindowVisible: boolean,
+ areSystemNotificationsEnabled: boolean,
+ ) {
+ if (
+ this.evaluateNotification(systemNotification, isWindowVisible, areSystemNotificationsEnabled)
+ ) {
const notification = this.createNotification(systemNotification);
this.addPendingNotification(notification);
notification.show();
@@ -141,7 +155,11 @@ export default class NotificationController {
}
}
- private showTunnelStateNotification(systemNotification: SystemNotification) {
+ private showTunnelStateNotification(
+ systemNotification: SystemNotification,
+ isWindowVisible: boolean,
+ areSystemNotificationsEnabled: boolean,
+ ) {
const message = systemNotification.message;
const lastAnnouncement = this.lastTunnelStateAnnouncement;
const sameAsLastNotification = lastAnnouncement && lastAnnouncement.body === message;
@@ -154,7 +172,11 @@ export default class NotificationController {
lastAnnouncement.notification.close();
}
- const newNotification = this.notify(systemNotification);
+ const newNotification = this.notify(
+ systemNotification,
+ isWindowVisible,
+ areSystemNotificationsEnabled,
+ );
if (newNotification) {
this.lastTunnelStateAnnouncement = {
@@ -179,13 +201,15 @@ export default class NotificationController {
}
}
- private evaluateNotification(notification: SystemNotification) {
+ private evaluateNotification(
+ notification: SystemNotification,
+ isWindowVisible: boolean,
+ areSystemNotificationsEnabled: boolean,
+ ) {
const suppressDueToDevelopment =
notification.suppressInDevelopment && process.env.NODE_ENV === 'development';
- const suppressDueToVisibleWindow = this.notificationControllerDelegate.isWindowVisible();
- const suppressDueToPreference =
- !this.notificationControllerDelegate.areSystemNotificationsEnabled() &&
- !notification.critical;
+ const suppressDueToVisibleWindow = isWindowVisible;
+ const suppressDueToPreference = !areSystemNotificationsEnabled && !notification.critical;
return (
!suppressDueToDevelopment &&
diff --git a/gui/src/main/tunnel-state.ts b/gui/src/main/tunnel-state.ts
index 596516a8d4..029386f3b7 100644
--- a/gui/src/main/tunnel-state.ts
+++ b/gui/src/main/tunnel-state.ts
@@ -2,6 +2,10 @@ import { connectEnabled, disconnectEnabled, reconnectEnabled } from '../shared/c
import { TunnelState } from '../shared/daemon-rpc-types';
import { Scheduler } from '../shared/scheduler';
+export interface TunnelStateProvider {
+ getTunnelState(): TunnelState;
+}
+
export interface TunnelStateHandlerDelegate {
handleTunnelStateUpdate(tunnelState: TunnelState): void;
}
diff --git a/gui/src/main/user-interface.ts b/gui/src/main/user-interface.ts
index 944d96108f..b7a05c7d23 100644
--- a/gui/src/main/user-interface.ts
+++ b/gui/src/main/user-interface.ts
@@ -1,4 +1,4 @@
-import { app, BrowserWindow, Menu, nativeImage, screen, Tray } from 'electron';
+import { app, BrowserWindow, dialog, Menu, nativeImage, screen, Tray } from 'electron';
import path from 'path';
import { sprintf } from 'sprintf-js';
@@ -18,16 +18,15 @@ import WindowController, { WindowControllerDelegate } from './window-controller'
export interface UserInterfaceDelegate {
cancelPendingNotifications(): void;
resetTunnelStateAnnouncements(): void;
- isUnpinnedWindow(): boolean;
- getAppQuitStage(): AppQuitStage;
updateAccountData(): void;
- isLoggedIn(): boolean;
- getTunnelState(): TunnelState;
- isBrowsingFiles(): boolean;
- getAccountData(): IAccountData | undefined;
connectTunnel(): void;
reconnectTunnel(): void;
disconnectTunnel(): void;
+ isUnpinnedWindow(): boolean;
+ isLoggedIn(): boolean;
+ getAppQuitStage(): AppQuitStage;
+ getAccountData(): IAccountData | undefined;
+ getTunnelState(): TunnelState;
}
export default class UserInterface implements WindowControllerDelegate {
@@ -36,6 +35,10 @@ export default class UserInterface implements WindowControllerDelegate {
private tray: Tray;
private trayIconController?: TrayIconController;
+ // True while file pickers are displayed which is used to decide if the Browser window should be
+ // hidden when losing focus.
+ private browsingFiles = false;
+
private blurNavigationResetScheduler = new Scheduler();
private backgroundThrottleScheduler = new Scheduler();
@@ -52,6 +55,18 @@ export default class UserInterface implements WindowControllerDelegate {
this.tray = this.createTray();
}
+ public registerIpcListeners() {
+ IpcMainEventChannel.app.handleShowOpenDialog(async (options) => {
+ this.browsingFiles = true;
+ const response = await dialog.showOpenDialog({
+ defaultPath: app.getPath('home'),
+ ...options,
+ });
+ this.browsingFiles = false;
+ return response;
+ });
+ }
+
public createTrayIconController(
tunnelState: TunnelState,
blockWhenDisconnected: boolean,
@@ -61,14 +76,14 @@ export default class UserInterface implements WindowControllerDelegate {
this.trayIconController = new TrayIconController(this.tray, iconType, monochromaticIcon);
}
- public async initializeWindow() {
+ public async initializeWindow(isLoggedIn: boolean, tunnelState: TunnelState) {
if (!this.windowController.window) {
throw new Error('No window available in initializeWindow');
}
const window = this.windowController.window;
- this.registerWindowListener(this.delegate.getAccountData());
+ this.registerWindowListener();
this.addContextMenu();
if (process.env.NODE_ENV === 'development') {
@@ -80,14 +95,14 @@ export default class UserInterface implements WindowControllerDelegate {
switch (process.platform) {
case 'win32':
- this.installWindowsMenubarAppWindowHandlers(this.delegate.isBrowsingFiles());
+ this.installWindowsMenubarAppWindowHandlers();
break;
case 'darwin':
this.installMacOsMenubarAppWindowHandlers();
this.setMacOsAppMenu();
break;
case 'linux':
- this.setTrayContextMenu();
+ this.setTrayContextMenu(isLoggedIn, tunnelState);
this.setLinuxAppMenu();
window.setMenuBarVisibility(false);
break;
@@ -110,27 +125,17 @@ export default class UserInterface implements WindowControllerDelegate {
}
}
- public setTrayContextMenu = () => {
- if (process.platform === 'linux') {
- this.tray.setContextMenu(
- this.createContextMenu(
- this.daemonRpc.isConnected,
- this.delegate.isLoggedIn(),
- this.delegate.getTunnelState(),
- ),
- );
- }
- };
-
- public setTrayTooltip = () => {
- const tooltip = this.createTooltipText(
- this.daemonRpc.isConnected,
- this.delegate.getTunnelState(),
- );
- this.tray?.setToolTip(tooltip);
+ public updateTray = (
+ isLoggedIn: boolean,
+ tunnelState: TunnelState,
+ blockWhenDisconnected: boolean,
+ ) => {
+ this.updateTrayIcon(tunnelState, blockWhenDisconnected);
+ this.setTrayContextMenu(isLoggedIn, tunnelState);
+ this.setTrayTooltip(tunnelState);
};
- public async recreateWindow(): Promise<void> {
+ public async recreateWindow(isLoggedIn: boolean, tunnelState: TunnelState): Promise<void> {
if (this.tray) {
this.tray.removeAllListeners();
@@ -140,7 +145,7 @@ export default class UserInterface implements WindowControllerDelegate {
this.windowController.close();
this.windowController = new WindowController(this, window);
- await this.initializeWindow();
+ await this.initializeWindow(isLoggedIn, tunnelState);
this.windowController.show();
}
}
@@ -151,7 +156,6 @@ export default class UserInterface implements WindowControllerDelegate {
public updateTrayTheme = () => this.trayIconController?.updateTheme();
public setUseMonochromaticTrayIcon = (value: boolean) =>
this.trayIconController?.setUseMonochromaticIcon(value);
- public animateTrayToIcon = (type: TrayIconType) => this.trayIconController?.animateToIcon(type);
public setWindowIcon = (icon: string) => this.windowController.window?.setIcon(icon);
public setWindowClosable = (value: boolean) => {
@@ -162,7 +166,7 @@ export default class UserInterface implements WindowControllerDelegate {
public updateTrayIcon(tunnelState: TunnelState, blockWhenDisconnected: boolean) {
const type = this.trayIconType(tunnelState, blockWhenDisconnected);
- this.animateTrayToIcon(type);
+ this.trayIconController?.animateToIcon(type);
}
public dispose = () => this.trayIconController?.dispose();
@@ -267,7 +271,7 @@ export default class UserInterface implements WindowControllerDelegate {
return new WindowController(this, window);
}
- private registerWindowListener(accountData?: IAccountData) {
+ private registerWindowListener() {
this.windowController.window?.on('focus', () => {
IpcMainEventChannel.window.notifyFocus?.(true);
@@ -276,6 +280,7 @@ export default class UserInterface implements WindowControllerDelegate {
// cancel notifications when window appears
this.delegate.cancelPendingNotifications();
+ const accountData = this.delegate.getAccountData();
if (!accountData || closeToExpiry(accountData.expiry, 4) || hasExpired(accountData.expiry)) {
this.delegate.updateAccountData();
}
@@ -304,6 +309,19 @@ export default class UserInterface implements WindowControllerDelegate {
});
}
+ private setTrayContextMenu(isLoggedIn: boolean, tunnelState: TunnelState) {
+ if (process.platform === 'linux') {
+ this.tray.setContextMenu(
+ this.createContextMenu(this.daemonRpc.isConnected, isLoggedIn, tunnelState),
+ );
+ }
+ }
+
+ private setTrayTooltip(tunnelState: TunnelState) {
+ const tooltip = this.createTooltipText(this.daemonRpc.isConnected, tunnelState);
+ this.tray?.setToolTip(tooltip);
+ }
+
private addContextMenu() {
const menuTemplate: Electron.MenuItemConstructorOptions[] = [
{ role: 'cut' },
@@ -400,7 +418,7 @@ export default class UserInterface implements WindowControllerDelegate {
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
- private installWindowsMenubarAppWindowHandlers(browsingFiles: boolean) {
+ private installWindowsMenubarAppWindowHandlers() {
if (this.delegate.isUnpinnedWindow()) {
return;
}
@@ -414,7 +432,7 @@ export default class UserInterface implements WindowControllerDelegate {
cursorPos.y >= trayBounds.y &&
cursorPos.x <= trayBounds.x + trayBounds.width &&
cursorPos.y <= trayBounds.y + trayBounds.height;
- if (!isCursorInside && !browsingFiles) {
+ if (!isCursorInside && !this.browsingFiles) {
this.windowController.hide();
}
});
@@ -467,7 +485,9 @@ export default class UserInterface implements WindowControllerDelegate {
if (this.delegate.isUnpinnedWindow()) {
// This needs to be executed on click since if it is added to the tray icon it will be
// displayed on left click as well.
- this.tray?.on('right-click', () => this.popUpContextMenu());
+ this.tray?.on('right-click', () =>
+ this.popUpContextMenu(this.delegate.isLoggedIn(), this.delegate.getTunnelState()),
+ );
this.tray?.on('click', () => this.windowController.show());
} else {
this.tray?.on('right-click', () => this.windowController.hide());
@@ -499,13 +519,9 @@ export default class UserInterface implements WindowControllerDelegate {
}
}
- private popUpContextMenu() {
+ private popUpContextMenu(isLoggedIn: boolean, tunnelState: TunnelState) {
this.tray.popUpContextMenu(
- this.createContextMenu(
- this.daemonRpc.isConnected,
- this.delegate.isLoggedIn(),
- this.delegate.getTunnelState(),
- ),
+ this.createContextMenu(this.daemonRpc.isConnected, isLoggedIn, tunnelState),
);
}
diff --git a/gui/src/main/version.ts b/gui/src/main/version.ts
index af2891695e..5e45203c6c 100644
--- a/gui/src/main/version.ts
+++ b/gui/src/main/version.ts
@@ -5,21 +5,17 @@ import { ICurrentAppVersionInfo } from '../shared/ipc-types';
import log from '../shared/logging';
import {
InconsistentVersionNotificationProvider,
- SystemNotification,
UnsupportedVersionNotificationProvider,
UpdateAvailableNotificationProvider,
} from '../shared/notifications/notification';
+import { DaemonRpc } from './daemon-rpc';
import { IpcMainEventChannel } from './ipc-event-channel';
+import { NotificationSender } from './notification-controller';
const GUI_VERSION = app.getVersion().replace('.0', '');
/// Mirrors the beta check regex in the daemon. Matches only well formed beta versions
const IS_BETA = /^(\d{4})\.(\d+)-beta(\d+)$/;
-export interface VersionDelegate {
- notify(notification: SystemNotification): void;
- getVersionInfo(): Promise<IAppVersionInfo>;
-}
-
export default class Version {
private currentVersionData: ICurrentAppVersionInfo = {
daemon: undefined,
@@ -34,7 +30,8 @@ export default class Version {
};
public constructor(
- private delegate: VersionDelegate,
+ private delegate: NotificationSender,
+ private daemonRpc: DaemonRpc,
private updateNotificationDisabled: boolean,
) {}
@@ -115,7 +112,7 @@ export default class Version {
public async fetchLatestVersion() {
try {
- this.setLatestVersion(await this.delegate.getVersionInfo());
+ this.setLatestVersion(await this.daemonRpc.getVersionInfo());
} catch (e) {
const error = e as Error;
log.error(`Failed to request the version info: ${error.message}`);