diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-08-12 13:17:20 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-08-22 08:34:37 +0200 |
| commit | 33547846ab4b7594d23b1772fea35e30e0b460fc (patch) | |
| tree | 2ca36afbe949dcb6db3d0f66aceac4340141fb77 /gui/src | |
| parent | 8feded87c6f13d4caaeca6114b1cd964a4b9a051 (diff) | |
| download | mullvadvpn-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.ts | 25 | ||||
| -rw-r--r-- | gui/src/main/daemon-rpc.ts | 24 | ||||
| -rw-r--r-- | gui/src/main/index.ts | 132 | ||||
| -rw-r--r-- | gui/src/main/notification-controller.ts | 48 | ||||
| -rw-r--r-- | gui/src/main/tunnel-state.ts | 4 | ||||
| -rw-r--r-- | gui/src/main/user-interface.ts | 102 | ||||
| -rw-r--r-- | gui/src/main/version.ts | 13 |
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}`); |
