summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-08-08 17:56:14 +0200
committerOskar Nyberg <oskar@mullvad.net>2022-08-22 08:34:37 +0200
commit52f09be3760a2744cba00ac2a5eb52b14ba6b726 (patch)
treeaa8adae95aa59ba07f6a74cc2191741dff7d5b88 /gui/src
parent9c3b92019bf7a004e149c416454e676f7e5e3712 (diff)
downloadmullvadvpn-52f09be3760a2744cba00ac2a5eb52b14ba6b726.tar.xz
mullvadvpn-52f09be3760a2744cba00ac2a5eb52b14ba6b726.zip
Move account code to it's own file
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/account.ts232
-rw-r--r--gui/src/main/index.ts267
2 files changed, 273 insertions, 226 deletions
diff --git a/gui/src/main/account.ts b/gui/src/main/account.ts
new file mode 100644
index 0000000000..e3509cd98d
--- /dev/null
+++ b/gui/src/main/account.ts
@@ -0,0 +1,232 @@
+import {
+ AccountToken,
+ DeviceEvent,
+ DeviceState,
+ IAccountData,
+ IDeviceRemoval,
+ TunnelState,
+} from '../shared/daemon-rpc-types';
+import { messages } from '../shared/gettext';
+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';
+
+export interface AccountDelegate {
+ notify(notification: SystemNotification): void;
+ getTunnelState(): TunnelState;
+ getLocale(): string;
+ isPerformingPostUpgradeCheck(): boolean;
+ performPostUpgradeCheck(): void;
+ setTrayContextMenu(): void;
+}
+
+export default class Account {
+ private accountDataValue?: IAccountData = undefined;
+ private accountHistoryValue?: AccountToken = undefined;
+ private accountExpiryNotificationScheduler = new Scheduler();
+ private accountDataCache = new AccountDataCache(
+ (accountToken) => {
+ return this.daemonRpc.getAccountData(accountToken);
+ },
+ (accountData) => {
+ this.accountDataValue = accountData;
+
+ IpcMainEventChannel.account.notify?.(this.accountData);
+
+ this.handleAccountExpiry();
+ },
+ );
+
+ private deviceStateValue?: DeviceState;
+
+ public constructor(private delegate: AccountDelegate, private daemonRpc: DaemonRpc) {}
+
+ public get accountData() {
+ return this.accountDataValue;
+ }
+
+ public get accountHistory() {
+ return this.accountHistoryValue;
+ }
+
+ public get deviceState() {
+ return this.deviceStateValue;
+ }
+
+ public registerIpcListeners() {
+ IpcMainEventChannel.account.handleCreate(() => this.createNewAccount());
+ IpcMainEventChannel.account.handleLogin((token: AccountToken) => this.login(token));
+ IpcMainEventChannel.account.handleLogout(() => this.logout());
+ IpcMainEventChannel.account.handleGetWwwAuthToken(() => this.daemonRpc.getWwwAuthToken());
+ IpcMainEventChannel.account.handleSubmitVoucher(async (voucherCode: string) => {
+ const currentAccountToken = this.getAccountToken();
+ const response = await this.daemonRpc.submitVoucher(voucherCode);
+
+ if (currentAccountToken) {
+ this.accountDataCache.handleVoucherResponse(currentAccountToken, response);
+ }
+
+ return response;
+ });
+ IpcMainEventChannel.account.handleUpdateData(() => this.updateAccountData());
+
+ IpcMainEventChannel.accountHistory.handleClear(async () => {
+ await this.daemonRpc.clearAccountHistory();
+ void this.updateAccountHistory();
+ });
+
+ IpcMainEventChannel.account.handleGetDeviceState(async () => {
+ try {
+ await this.daemonRpc.updateDevice();
+ } catch (e) {
+ const error = e as Error;
+ log.warn(`Failed to update device info: ${error.message}`);
+ }
+ return this.daemonRpc.getDevice();
+ });
+ IpcMainEventChannel.account.handleListDevices((accountToken: AccountToken) => {
+ return this.daemonRpc.listDevices(accountToken);
+ });
+ IpcMainEventChannel.account.handleRemoveDevice((deviceRemoval: IDeviceRemoval) => {
+ return this.daemonRpc.removeDevice(deviceRemoval);
+ });
+ }
+
+ public isLoggedIn(): boolean {
+ return this.deviceState?.type === 'logged in';
+ }
+
+ public updateAccountData = () => {
+ if (this.daemonRpc.isConnected && this.isLoggedIn()) {
+ this.accountDataCache.fetch(this.getAccountToken()!);
+ }
+ };
+
+ public detectStaleAccountExpiry(tunnelState: TunnelState) {
+ const hasExpired = !this.accountData || new Date() >= new Date(this.accountData.expiry);
+
+ // It's likely that the account expiry is stale if the daemon managed to establish the tunnel.
+ if (tunnelState.state === 'connected' && hasExpired) {
+ log.info('Detected the stale account expiry.');
+ this.accountDataCache.invalidate();
+ }
+ }
+
+ 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);
+ break;
+ case 'logged out':
+ case 'revoked':
+ this.accountDataCache.invalidate();
+ break;
+ }
+
+ void this.updateAccountHistory();
+ this.delegate.setTrayContextMenu();
+
+ IpcMainEventChannel.account.notifyDevice?.(deviceEvent);
+ }
+
+ public setAccountHistory(accountHistory?: AccountToken) {
+ this.accountHistoryValue = accountHistory;
+
+ IpcMainEventChannel.accountHistory.notify?.(accountHistory);
+ }
+
+ private async createNewAccount(): Promise<string> {
+ try {
+ return await this.daemonRpc.createNewAccount();
+ } catch (e) {
+ const error = e as Error;
+ log.error(`Failed to create account: ${error.message}`);
+ throw error;
+ }
+ }
+
+ private async login(accountToken: AccountToken): Promise<void> {
+ try {
+ await this.daemonRpc.loginAccount(accountToken);
+ } catch (e) {
+ const error = e as Error;
+ log.error(`Failed to login: ${error.message}`);
+
+ if (error instanceof InvalidAccountError) {
+ throw Error(messages.gettext('Invalid account number'));
+ } else {
+ throw error;
+ }
+ }
+ }
+
+ private async logout(): Promise<void> {
+ try {
+ await this.daemonRpc.logoutAccount();
+
+ this.accountExpiryNotificationScheduler.cancel();
+ } catch (e) {
+ const error = e as Error;
+ log.info(`Failed to logout: ${error.message}`);
+
+ throw error;
+ }
+ }
+
+ private handleAccountExpiry() {
+ if (this.accountData) {
+ const expiredNotification = new AccountExpiredNotificationProvider({
+ accountExpiry: this.accountData.expiry,
+ tunnelState: this.delegate.getTunnelState(),
+ });
+ const closeToExpiryNotification = new CloseToAccountExpiryNotificationProvider({
+ accountExpiry: this.accountData.expiry,
+ locale: this.delegate.getLocale(),
+ });
+
+ if (expiredNotification.mayDisplay()) {
+ this.accountExpiryNotificationScheduler.cancel();
+ this.delegate.notify(expiredNotification.getSystemNotification());
+ } else if (
+ !this.accountExpiryNotificationScheduler.isRunning &&
+ closeToExpiryNotification.mayDisplay()
+ ) {
+ this.delegate.notify(closeToExpiryNotification.getSystemNotification());
+
+ const twelveHours = 12 * 60 * 60 * 1000;
+ const remainingMilliseconds = new Date(this.accountData.expiry).getTime() - Date.now();
+ const delay = Math.min(twelveHours, remainingMilliseconds);
+ this.accountExpiryNotificationScheduler.schedule(() => this.handleAccountExpiry(), delay);
+ }
+ }
+ }
+
+ private async updateAccountHistory(): Promise<void> {
+ try {
+ this.setAccountHistory(await this.daemonRpc.getAccountHistory());
+ } catch (e) {
+ const error = e as Error;
+ log.error(`Failed to fetch the account history: ${error.message}`);
+ }
+ }
+
+ private getAccountToken(): AccountToken | undefined {
+ return this.deviceState?.type === 'logged in'
+ ? this.deviceState.accountAndDevice.accountToken
+ : undefined;
+ }
+}
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index f95a334eb6..4f1769bba9 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -7,33 +7,18 @@ import util from 'util';
import config from '../config.json';
import { hasExpired } from '../shared/account-expiry';
import { IWindowsApplication } from '../shared/application-types';
-import {
- AccountToken,
- DaemonEvent,
- DeviceEvent,
- DeviceState,
- IAccountData,
- IDeviceRemoval,
- ISettings,
- TunnelState,
-} from '../shared/daemon-rpc-types';
+import { DaemonEvent, DeviceEvent, ISettings, TunnelState } from '../shared/daemon-rpc-types';
import { messages, relayLocations } from '../shared/gettext';
import { SYSTEM_PREFERRED_LOCALE_KEY } from '../shared/gui-settings-state';
import { ITranslations, MacOsScrollbarVisibility } from '../shared/ipc-schema';
import { IChangelog, IHistoryObject, ScrollPositions } from '../shared/ipc-types';
import log, { ConsoleOutput, Logger } from '../shared/logging';
import { LogLevel } from '../shared/logging-types';
-import {
- AccountExpiredNotificationProvider,
- CloseToAccountExpiryNotificationProvider,
- SystemNotification,
-} from '../shared/notifications/notification';
-import { Scheduler } from '../shared/scheduler';
-import AccountDataCache from './account-data-cache';
+import { SystemNotification } from '../shared/notifications/notification';
+import Account, { AccountDelegate } from './account';
import { getOpenAtLogin } from './autostart';
import { readChangelog } from './changelog';
import { ConnectionObserver, DaemonRpc, SubscriptionListener } from './daemon-rpc';
-import { InvalidAccountError } from './errors';
import Expectation from './expectation';
import { IpcMainEventChannel } from './ipc-event-channel';
import { findIconPath } from './linux-desktop-entry';
@@ -89,14 +74,15 @@ class ApplicationMain
UserInterfaceDelegate,
VersionDelegate,
TunnelStateHandlerDelegate,
- SettingsDelegate {
+ SettingsDelegate,
+ AccountDelegate {
private daemonRpc = new DaemonRpc();
private notificationController = new NotificationController(this);
private version = new Version(this, 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
@@ -109,30 +95,11 @@ class ApplicationMain
private isPerformingPostUpgrade = false;
private quitStage = AppQuitStage.unready;
- private accountData?: IAccountData = undefined;
- private accountHistory?: AccountToken = undefined;
-
- private deviceState?: DeviceState;
private tunnelStateExpectation?: Expectation;
// The UI locale which is set once from onReady handler
private locale = 'en';
- private accountExpiryNotificationScheduler = new Scheduler();
-
- private accountDataCache = new AccountDataCache(
- (accountToken) => {
- return this.daemonRpc.getAccountData(accountToken);
- },
- (accountData) => {
- this.accountData = accountData;
-
- IpcMainEventChannel.account.notify?.(this.accountData);
-
- this.handleAccountExpiry();
- },
- );
-
private rendererLog?: Logger;
private translations: ITranslations = { locale: this.locale };
@@ -211,6 +178,14 @@ class ApplicationMain
app.on('quit', this.onQuit);
}
+ public async performPostUpgradeCheck(): Promise<void> {
+ const oldValue = this.isPerformingPostUpgrade;
+ this.isPerformingPostUpgrade = await this.daemonRpc.isPerformingPostUpgrade();
+ if (this.isPerformingPostUpgrade !== oldValue) {
+ IpcMainEventChannel.daemon.notifyIsPerformingPostUpgrade?.(this.isPerformingPostUpgrade);
+ }
+ }
+
private addSecondInstanceEventHandler() {
app.on('second-instance', (_event, argv, _workingDirectory) => {
if (argv.includes(CommandLineOptions.quitWithoutDisconnect)) {
@@ -487,7 +462,7 @@ class ApplicationMain
// fetch account history
try {
- this.setAccountHistory(await this.daemonRpc.getAccountHistory());
+ this.account.setAccountHistory(await this.daemonRpc.getAccountHistory());
} catch (e) {
const error = e as Error;
log.error(`Failed to fetch the account history: ${error.message}`);
@@ -508,7 +483,7 @@ class ApplicationMain
// fetch device
try {
const deviceState = await this.daemonRpc.getDevice();
- this.handleDeviceEvent({ type: deviceState.type, deviceState } as DeviceEvent);
+ this.account.handleDeviceEvent({ type: deviceState.type, deviceState } as DeviceEvent);
if (deviceState.type === 'logged in') {
void this.daemonRpc
.updateDevice()
@@ -578,7 +553,7 @@ class ApplicationMain
}
// show window when account is not set
- if (!this.isLoggedIn()) {
+ if (!this.account.isLoggedIn()) {
this.userInterface?.showWindow();
}
};
@@ -647,7 +622,7 @@ class ApplicationMain
} else if ('appVersionInfo' in daemonEvent) {
this.version.setLatestVersion(daemonEvent.appVersionInfo);
} else if ('device' in daemonEvent) {
- this.handleDeviceEvent(daemonEvent.device);
+ this.account.handleDeviceEvent(daemonEvent.device);
} else if ('deviceRemoval' in daemonEvent) {
IpcMainEventChannel.account.notifyDevices?.(daemonEvent.deviceRemoval);
}
@@ -662,20 +637,6 @@ class ApplicationMain
return daemonEventListener;
}
- private async performPostUpgradeCheck(): Promise<void> {
- const oldValue = this.isPerformingPostUpgrade;
- this.isPerformingPostUpgrade = await this.daemonRpc.isPerformingPostUpgrade();
- if (this.isPerformingPostUpgrade !== oldValue) {
- IpcMainEventChannel.daemon.notifyIsPerformingPostUpgrade?.(this.isPerformingPostUpgrade);
- }
- }
-
- private setAccountHistory(accountHistory?: AccountToken) {
- this.accountHistory = accountHistory;
-
- IpcMainEventChannel.accountHistory.notify?.(accountHistory);
- }
-
private setSettings(newSettings: ISettings) {
const oldSettings = this.settings;
this.settings.handleNewSettings(newSettings);
@@ -709,45 +670,16 @@ class ApplicationMain
IpcMainEventChannel.windowsSplitTunneling.notify?.(applications);
}
- private handleDeviceEvent(deviceEvent: DeviceEvent) {
- this.deviceState = deviceEvent.deviceState;
-
- if (this.isPerformingPostUpgrade) {
- void this.performPostUpgradeCheck();
- }
-
- switch (deviceEvent.deviceState.type) {
- case 'logged in':
- this.accountDataCache.fetch(deviceEvent.deviceState.accountAndDevice.accountToken);
- break;
- case 'logged out':
- case 'revoked':
- this.accountDataCache.invalidate();
- break;
- }
-
- void this.updateAccountHistory();
- this.userInterface?.setTrayContextMenu();
-
- IpcMainEventChannel.account.notifyDevice?.(deviceEvent);
- }
-
- private getAccountToken(): AccountToken | undefined {
- return this.deviceState?.type === 'logged in'
- ? this.deviceState.accountAndDevice.accountToken
- : undefined;
- }
-
private registerIpcListeners() {
IpcMainEventChannel.state.handleGet(() => ({
isConnected: this.daemonRpc.isConnected,
autoStart: getOpenAtLogin(),
- accountData: this.accountData,
- accountHistory: this.accountHistory,
+ accountData: this.account.accountData,
+ accountHistory: this.account.accountHistory,
tunnelState: this.tunnelState.tunnelState,
settings: this.settings.all,
isPerformingPostUpgrade: this.isPerformingPostUpgrade,
- deviceState: this.deviceState,
+ deviceState: this.account.deviceState,
relayListPair: this.relayList.getProcessedRelays(
this.settings.relaySettings,
this.settings.bridgeState,
@@ -776,43 +708,6 @@ class ApplicationMain
return Promise.resolve(this.translations);
});
- IpcMainEventChannel.account.handleCreate(() => this.createNewAccount());
- IpcMainEventChannel.account.handleLogin((token: AccountToken) => this.login(token));
- IpcMainEventChannel.account.handleLogout(() => this.logout());
- IpcMainEventChannel.account.handleGetWwwAuthToken(() => this.daemonRpc.getWwwAuthToken());
- IpcMainEventChannel.account.handleSubmitVoucher(async (voucherCode: string) => {
- const currentAccountToken = this.getAccountToken();
- const response = await this.daemonRpc.submitVoucher(voucherCode);
-
- if (currentAccountToken) {
- this.accountDataCache.handleVoucherResponse(currentAccountToken, response);
- }
-
- return response;
- });
- IpcMainEventChannel.account.handleUpdateData(() => this.updateAccountData());
-
- IpcMainEventChannel.account.handleGetDeviceState(async () => {
- try {
- await this.daemonRpc.updateDevice();
- } catch (e) {
- const error = e as Error;
- log.warn(`Failed to update device info: ${error.message}`);
- }
- return this.daemonRpc.getDevice();
- });
- IpcMainEventChannel.account.handleListDevices((accountToken: AccountToken) => {
- return this.daemonRpc.listDevices(accountToken);
- });
- IpcMainEventChannel.account.handleRemoveDevice((deviceRemoval: IDeviceRemoval) => {
- return this.daemonRpc.removeDevice(deviceRemoval);
- });
-
- IpcMainEventChannel.accountHistory.handleClear(async () => {
- await this.daemonRpc.clearAccountHistory();
- void this.updateAccountHistory();
- });
-
IpcMainEventChannel.linuxSplitTunneling.handleGetApplications(() => {
return linuxSplitTunneling.getApplications(this.locale);
});
@@ -874,6 +769,7 @@ class ApplicationMain
problemReport.registerIpcListeners();
this.settings.registerIpcListeners();
+ this.account.registerIpcListeners();
if (windowsSplitTunneling) {
this.settings.gui.browsedForSplitTunnelingApplications.forEach(
@@ -882,35 +778,13 @@ class ApplicationMain
}
}
- private async createNewAccount(): Promise<string> {
- try {
- return await this.daemonRpc.createNewAccount();
- } catch (e) {
- const error = e as Error;
- log.error(`Failed to create account: ${error.message}`);
- throw error;
- }
- }
-
- private async login(accountToken: AccountToken): Promise<void> {
- try {
- await this.daemonRpc.loginAccount(accountToken);
- } catch (e) {
- const error = e as Error;
- log.error(`Failed to login: ${error.message}`);
-
- if (error instanceof InvalidAccountError) {
- throw Error(messages.gettext('Invalid account number'));
- } else {
- throw error;
- }
- }
- }
-
private async autoConnect() {
if (process.env.NODE_ENV === 'development') {
log.info('Skip autoconnect in development');
- } else if (this.isLoggedIn() && (!this.accountData || !hasExpired(this.accountData.expiry))) {
+ } else if (
+ this.account.isLoggedIn() &&
+ (!this.account.accountData || !hasExpired(this.account.accountData.expiry))
+ ) {
if (this.settings.gui.autoConnect) {
try {
log.info('Autoconnect the tunnel');
@@ -928,66 +802,6 @@ class ApplicationMain
}
}
- private async logout(): Promise<void> {
- try {
- await this.daemonRpc.logoutAccount();
-
- this.accountExpiryNotificationScheduler.cancel();
- } catch (e) {
- const error = e as Error;
- log.info(`Failed to logout: ${error.message}`);
-
- throw error;
- }
- }
-
- private detectStaleAccountExpiry(tunnelState: TunnelState, accountExpiry: Date) {
- const hasExpired = new Date() >= accountExpiry;
-
- // It's likely that the account expiry is stale if the daemon managed to establish the tunnel.
- if (tunnelState.state === 'connected' && hasExpired) {
- log.info('Detected the stale account expiry.');
- this.accountDataCache.invalidate();
- }
- }
-
- private handleAccountExpiry() {
- if (this.accountData) {
- const expiredNotification = new AccountExpiredNotificationProvider({
- accountExpiry: this.accountData.expiry,
- tunnelState: this.tunnelState.tunnelState,
- });
- const closeToExpiryNotification = new CloseToAccountExpiryNotificationProvider({
- accountExpiry: this.accountData.expiry,
- locale: this.locale,
- });
-
- if (expiredNotification.mayDisplay()) {
- this.accountExpiryNotificationScheduler.cancel();
- this.notificationController.notify(expiredNotification.getSystemNotification());
- } else if (
- !this.accountExpiryNotificationScheduler.isRunning &&
- closeToExpiryNotification.mayDisplay()
- ) {
- this.notificationController.notify(closeToExpiryNotification.getSystemNotification());
-
- const twelveHours = 12 * 60 * 60 * 1000;
- const remainingMilliseconds = new Date(this.accountData.expiry).getTime() - Date.now();
- const delay = Math.min(twelveHours, remainingMilliseconds);
- this.accountExpiryNotificationScheduler.schedule(() => this.handleAccountExpiry(), delay);
- }
- }
- }
-
- private async updateAccountHistory(): Promise<void> {
- try {
- this.setAccountHistory(await this.daemonRpc.getAccountHistory());
- } catch (e) {
- const error = e as Error;
- log.error(`Failed to fetch the account history: ${error.message}`);
- }
- }
-
private updateCurrentLocale() {
this.locale = this.detectLocale();
@@ -1125,26 +939,20 @@ class ApplicationMain
public getAppQuitStage = () => this.quitStage;
public isConnectedToDaemon = () => this.daemonRpc.isConnected;
public getTunnelState = () => this.tunnelState.tunnelState;
- public updateAccountData = () => {
- if (this.daemonRpc.isConnected && this.isLoggedIn()) {
- this.accountDataCache.fetch(this.getAccountToken()!);
- }
- };
- public isLoggedIn(): boolean {
- return this.deviceState?.type === 'logged in';
- }
+ public updateAccountData = () => this.account.updateAccountData();
+ public isLoggedIn = () => this.account.isLoggedIn();
public isBrowsingFiles = () => this.browsingFiles;
- public getAccountData = () => this.accountData;
+ public getAccountData = () => this.account.accountData;
public connectTunnel = async (): Promise<void> => {
- if (this.tunnelState.allowConnect(this.daemonRpc.isConnected, this.isLoggedIn())) {
+ if (this.tunnelState.allowConnect(this.daemonRpc.isConnected, this.account.isLoggedIn())) {
this.tunnelState.expectNextTunnelState('connecting');
await this.daemonRpc.connectTunnel();
}
};
public reconnectTunnel = async (): Promise<void> => {
- if (this.tunnelState.allowReconnect(this.daemonRpc.isConnected, this.isLoggedIn())) {
+ if (this.tunnelState.allowReconnect(this.daemonRpc.isConnected, this.account.isLoggedIn())) {
this.tunnelState.expectNextTunnelState('connecting');
await this.daemonRpc.reconnectTunnel();
}
@@ -1174,13 +982,13 @@ class ApplicationMain
tunnelState,
this.settings.blockWhenDisconnected,
this.settings.splitTunnel.enableExclusions && this.settings.splitTunnel.appsList.length > 0,
- this.accountData?.expiry,
+ this.account.accountData?.expiry,
);
IpcMainEventChannel.tunnel.notify?.(tunnelState);
- if (this.accountData) {
- this.detectStaleAccountExpiry(tunnelState, new Date(this.accountData.expiry));
+ if (this.account.accountData) {
+ this.account.detectStaleAccountExpiry(tunnelState);
}
};
@@ -1191,6 +999,13 @@ class ApplicationMain
public handleUnpinnedWindowChange() {
void this.userInterface?.recreateWindow();
}
+
+ // AccountDelegate
+ public getLocale = () => this.locale;
+ public isPerformingPostUpgradeCheck = () => this.isPerformingPostUpgrade;
+ public setTrayContextMenu() {
+ this.userInterface?.setTrayContextMenu();
+ }
/* eslint-enable @typescript-eslint/member-ordering */
}