summaryrefslogtreecommitdiffhomepage
path: root/gui/src/main
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2023-06-12 14:31:13 +0200
committerOskar Nyberg <oskar@mullvad.net>2023-06-12 14:31:13 +0200
commita6193ecb0974af6fc8677208e204b9064a9484dd (patch)
tree2147ecef22e855b69aa8569120ad14d82f2061fa /gui/src/main
parent601109ac7845bf603e4b2d17bb72f30401706b71 (diff)
parent2eecab47023011f96d9f76ce13bfd05e113a6c5b (diff)
downloadmullvadvpn-a6193ecb0974af6fc8677208e204b9064a9484dd.tar.xz
mullvadvpn-a6193ecb0974af6fc8677208e204b9064a9484dd.zip
Merge branch 'too-many-devices-view-isnt-shown-des-186'
Diffstat (limited to 'gui/src/main')
-rw-r--r--gui/src/main/account-data-cache.ts47
-rw-r--r--gui/src/main/account.ts23
-rw-r--r--gui/src/main/daemon-rpc.ts27
-rw-r--r--gui/src/main/errors.ts37
4 files changed, 46 insertions, 88 deletions
diff --git a/gui/src/main/account-data-cache.ts b/gui/src/main/account-data-cache.ts
index 38d2cb8a01..a5860f9073 100644
--- a/gui/src/main/account-data-cache.ts
+++ b/gui/src/main/account-data-cache.ts
@@ -1,13 +1,20 @@
import { closeToExpiry, hasExpired } from '../shared/account-expiry';
-import { AccountToken, IAccountData, VoucherResponse } from '../shared/daemon-rpc-types';
+import {
+ AccountDataError,
+ AccountDataResponse,
+ AccountToken,
+ IAccountData,
+ VoucherResponse,
+} from '../shared/daemon-rpc-types';
import { dateByAddingComponent, DateComponent } from '../shared/date-helper';
import log from '../shared/logging';
import { Scheduler } from '../shared/scheduler';
-import { InvalidAccountError } from './errors';
+
+export type AccountFetchError = AccountDataError['error'] | 'cancelled';
interface IAccountFetchWatcher {
onFinish: () => void;
- onError: (error: Error) => void;
+ onError: (error: AccountFetchError) => void;
}
// Account data is valid for 1 minute unless the account has expired.
@@ -26,7 +33,7 @@ export default class AccountDataCache {
private watchers: IAccountFetchWatcher[] = [];
constructor(
- private fetchHandler: (token: AccountToken) => Promise<IAccountData>,
+ private fetchHandler: (token: AccountToken) => Promise<AccountDataResponse>,
private updateHandler: (data?: IAccountData) => void,
) {}
@@ -64,7 +71,7 @@ export default class AccountDataCache {
this.validUntil = undefined;
this.updateHandler();
this.notifyWatchers((watcher) => {
- watcher.onError(new Error('Cancelled'));
+ watcher.onError('cancelled');
});
}
@@ -94,16 +101,20 @@ export default class AccountDataCache {
private async performFetch(accountToken: AccountToken) {
this.performingFetch = true;
- try {
- // it's possible for invalidate() to be called or for a fetch for a different account token
- // to start before this fetch completes, so checking if the current account token is the one
- // used is necessary below.
- const accountData = await this.fetchHandler(accountToken);
-
+ // it's possible for invalidate() to be called or for a fetch for a different account token
+ // to start before this fetch completes, so checking if the current account token is the one
+ // used is necessary below.
+ const response = await this.fetchHandler(accountToken);
+ if ('error' in response) {
if (this.currentAccount === accountToken) {
- this.setValue(accountData);
+ this.handleFetchError(accountToken, response.error);
+ this.performingFetch = false;
+ }
+ } else {
+ if (this.currentAccount === accountToken) {
+ this.setValue(response);
- const refetchDelay = this.calculateRefetchDelay(accountData.expiry);
+ const refetchDelay = this.calculateRefetchDelay(response.expiry);
if (refetchDelay) {
this.scheduleFetch(accountToken, refetchDelay);
}
@@ -111,12 +122,6 @@ export default class AccountDataCache {
this.waitStrategy.reset();
this.performingFetch = false;
}
- } catch (e) {
- const error = e as Error;
- if (this.currentAccount === accountToken) {
- this.handleFetchError(accountToken, error);
- this.performingFetch = false;
- }
}
}
@@ -131,9 +136,9 @@ export default class AccountDataCache {
}
}
- private handleFetchError(accountToken: AccountToken, error: Error) {
+ private handleFetchError(accountToken: AccountToken, error: AccountDataError['error']) {
this.notifyWatchers((w) => w.onError(error));
- if (!(error instanceof InvalidAccountError)) {
+ if (error !== 'invalid-account') {
this.scheduleRetry(accountToken);
}
}
diff --git a/gui/src/main/account.ts b/gui/src/main/account.ts
index 6ee2aafcde..1690445fb2 100644
--- a/gui/src/main/account.ts
+++ b/gui/src/main/account.ts
@@ -1,5 +1,6 @@
import { closeToExpiry } from '../shared/account-expiry';
import {
+ AccountDataError,
AccountToken,
DeviceEvent,
DeviceState,
@@ -7,7 +8,6 @@ import {
IDeviceRemoval,
TunnelState,
} from '../shared/daemon-rpc-types';
-import { messages } from '../shared/gettext';
import log from '../shared/logging';
import {
AccountExpiredNotificationProvider,
@@ -17,7 +17,6 @@ import {
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';
@@ -70,7 +69,9 @@ export default class Account {
public registerIpcListeners() {
IpcMainEventChannel.account.handleCreate(() => this.createNewAccount());
- IpcMainEventChannel.account.handleLogin((token: AccountToken) => this.login(token));
+ IpcMainEventChannel.account.handleLogin(
+ async (token: AccountToken) => (await this.login(token)) ?? undefined,
+ );
IpcMainEventChannel.account.handleLogout(() => this.logout());
IpcMainEventChannel.account.handleGetWwwAuthToken(() => this.daemonRpc.getWwwAuthToken());
IpcMainEventChannel.account.handleSubmitVoucher(async (voucherCode: string) => {
@@ -162,18 +163,12 @@ export default class Account {
}
}
- 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}`);
+ private async login(accountToken: AccountToken): Promise<AccountDataError | void> {
+ const error = await this.daemonRpc.loginAccount(accountToken);
- if (error instanceof InvalidAccountError) {
- throw Error(messages.gettext('Invalid account number'));
- } else {
- throw error;
- }
+ if (error) {
+ log.error(`Failed to login: ${error.error}`);
+ return error;
}
}
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 40794e765c..f3b744e522 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -8,6 +8,8 @@ import {
import { promisify } from 'util';
import {
+ AccountDataError,
+ AccountDataResponse,
AccountToken,
AfterDisconnect,
AuthFailedError,
@@ -23,7 +25,6 @@ import {
ErrorStateCause,
FirewallPolicyError,
FirewallPolicyErrorType,
- IAccountData,
IAppVersionInfo,
IBridgeConstraints,
IDevice,
@@ -61,12 +62,6 @@ import {
VoucherResponse,
} from '../shared/daemon-rpc-types';
import log from '../shared/logging';
-import {
- CommunicationError,
- InvalidAccountError,
- ListDevicesError,
- TooManyDevicesError,
-} from './errors';
import { ManagementServiceClient } from './management_interface/management_interface_grpc_pb';
import * as grpcTypes from './management_interface/management_interface_pb';
@@ -200,22 +195,22 @@ export class DaemonRpc {
}
}
- public async getAccountData(accountToken: AccountToken): Promise<IAccountData> {
+ public async getAccountData(accountToken: AccountToken): Promise<AccountDataResponse> {
try {
const response = await this.callString<grpcTypes.AccountData>(
this.client.getAccountData,
accountToken,
);
const expiry = response.getExpiry()!.toDate().toISOString();
- return { expiry };
+ return { type: 'success', expiry };
} catch (e) {
const error = e as grpc.ServiceError;
if (error.code) {
switch (error.code) {
case grpc.status.UNAUTHENTICATED:
- throw new InvalidAccountError();
+ return { type: 'error', error: 'invalid-account' };
default:
- throw new CommunicationError();
+ return { type: 'error', error: 'communication' };
}
}
throw error;
@@ -277,18 +272,18 @@ export class DaemonRpc {
return response.getValue();
}
- public async loginAccount(accountToken: AccountToken): Promise<void> {
+ public async loginAccount(accountToken: AccountToken): Promise<AccountDataError | void> {
try {
await this.callString(this.client.loginAccount, accountToken);
} catch (e) {
const error = e as grpc.ServiceError;
switch (error.code) {
case grpc.status.RESOURCE_EXHAUSTED:
- throw new TooManyDevicesError();
+ return { type: 'error', error: 'too-many-devices' };
case grpc.status.UNAUTHENTICATED:
- throw new InvalidAccountError();
+ return { type: 'error', error: 'invalid-account' };
default:
- throw new CommunicationError();
+ return { type: 'error', error: 'communication' };
}
}
}
@@ -603,7 +598,7 @@ export class DaemonRpc {
return response.getDevicesList().map(convertFromDevice);
} catch {
- throw new ListDevicesError();
+ throw new Error('Failed to list devices');
}
}
diff --git a/gui/src/main/errors.ts b/gui/src/main/errors.ts
deleted file mode 100644
index 86b482f7bd..0000000000
--- a/gui/src/main/errors.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { messages } from '../shared/gettext';
-
-export class InvalidAccountError extends Error {
- constructor() {
- super(
- // TRANSLATORS: Error message shown above login input when trying to login with a non-existent
- // TRANSLATORS: account number.
- messages.pgettext('login-view', 'Invalid account number'),
- );
- }
-}
-
-export class CommunicationError extends Error {
- constructor() {
- super('api.mullvad.net is blocked, please check your firewall');
- }
-}
-
-export class TooManyDevicesError extends Error {
- constructor() {
- super(
- // TRANSLATORS: Error message shown above login input when trying to login to an account with
- // TRANSLATORS: too many registered devices.
- messages.pgettext('login-view', 'Too many devices'),
- );
- }
-}
-
-export class ListDevicesError extends Error {
- constructor() {
- super(
- // TRANSLATORS: Error message shown above login input when trying to login but the app fails
- // TRANSLATORS: to fetch the list of registered devices.
- messages.pgettext('login-view', 'Failed to fetch list of devices'),
- );
- }
-}