diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-06-12 14:31:13 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2023-06-12 14:31:13 +0200 |
| commit | a6193ecb0974af6fc8677208e204b9064a9484dd (patch) | |
| tree | 2147ecef22e855b69aa8569120ad14d82f2061fa | |
| parent | 601109ac7845bf603e4b2d17bb72f30401706b71 (diff) | |
| parent | 2eecab47023011f96d9f76ce13bfd05e113a6c5b (diff) | |
| download | mullvadvpn-a6193ecb0974af6fc8677208e204b9064a9484dd.tar.xz mullvadvpn-a6193ecb0974af6fc8677208e204b9064a9484dd.zip | |
Merge branch 'too-many-devices-view-isnt-shown-des-186'
| -rw-r--r-- | gui/locales/messages.pot | 11 | ||||
| -rw-r--r-- | gui/src/main/account-data-cache.ts | 47 | ||||
| -rw-r--r-- | gui/src/main/account.ts | 23 | ||||
| -rw-r--r-- | gui/src/main/daemon-rpc.ts | 27 | ||||
| -rw-r--r-- | gui/src/main/errors.ts | 37 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 15 | ||||
| -rw-r--r-- | gui/src/renderer/components/Login.tsx | 25 | ||||
| -rw-r--r-- | gui/src/renderer/redux/account/actions.ts | 10 | ||||
| -rw-r--r-- | gui/src/renderer/redux/account/reducers.ts | 8 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 8 | ||||
| -rw-r--r-- | gui/src/shared/ipc-schema.ts | 3 | ||||
| -rw-r--r-- | gui/test/unit/account-data-cache.spec.ts | 55 |
12 files changed, 126 insertions, 143 deletions
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index d8d1019060..7d4e03e504 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -155,9 +155,6 @@ msgstr "" msgid "Got it!" msgstr "" -msgid "Invalid account number" -msgstr "" - msgid "IPv4" msgstr "" @@ -685,8 +682,8 @@ msgctxt "login-view" msgid "Finishing upgrade." msgstr "" -#. Error message shown above login input when trying to login with a non-existent -#. account number. +#. Error message shown above login input when trying to login with a +#. non-existent account number. msgctxt "login-view" msgid "Invalid account number" msgstr "" @@ -717,8 +714,8 @@ msgctxt "login-view" msgid "Please wait" msgstr "" -#. Error message shown above login input when trying to login to an account with -#. too many registered devices. +#. Error message shown above login input when trying to login to an account +#. with too many registered devices. msgctxt "login-view" msgid "Too many devices" msgstr "" 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'), - ); - } -} diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index c5377b5d54..6efcd81887 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -345,25 +345,22 @@ export default class AppRenderer { this.previousLoginState = this.loginState; this.loginState = 'logging in'; - try { - await IpcRendererEventChannel.account.login(accountToken); - } catch (e) { - const error = e as Error; - if (error.message === 'Too many devices') { + const response = await IpcRendererEventChannel.account.login(accountToken); + if (response?.type === 'error') { + if (response.error === 'too-many-devices') { try { await this.fetchDevices(accountToken); - actions.account.loginTooManyDevices(error); + actions.account.loginTooManyDevices(); this.loginState = 'too many devices'; this.history.reset(RoutePath.tooManyDevices, { transition: transitions.push }); } catch (e) { - const error = e as Error; log.error('Failed to fetch device list'); - actions.account.loginFailed(error); + actions.account.loginFailed('list-devices'); } } else { - actions.account.loginFailed(error); + actions.account.loginFailed(response.error); } } }; diff --git a/gui/src/renderer/components/Login.tsx b/gui/src/renderer/components/Login.tsx index 4219c0572f..07d4814f1b 100644 --- a/gui/src/renderer/components/Login.tsx +++ b/gui/src/renderer/components/Login.tsx @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'; import { sprintf } from 'sprintf-js'; import { colors } from '../../config.json'; -import { AccountToken } from '../../shared/daemon-rpc-types'; +import { AccountDataError, AccountToken } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import { useAppContext } from '../context'; import { formatAccountToken } from '../lib/account'; @@ -180,7 +180,7 @@ export default class Login extends React.Component<IProps, IState> { switch (this.props.loginState.type) { case 'failed': return this.props.loginState.method === 'existing_account' - ? this.props.loginState.error.message || messages.pgettext('login-view', 'Unknown error') + ? this.errorString(this.props.loginState.error) : messages.pgettext('login-view', 'Failed to create account'); case 'too many devices': return messages.pgettext('login-view', 'Too many devices'); @@ -197,6 +197,27 @@ export default class Login extends React.Component<IProps, IState> { } } + private errorString(error: AccountDataError['error']): string { + switch (error) { + case 'invalid-account': + // TRANSLATORS: Error message shown above login input when trying to login with a + // TRANSLATORS: non-existent account number. + return messages.pgettext('login-view', 'Invalid account number'); + case 'too-many-devices': + // TRANSLATORS: Error message shown above login input when trying to login to an account + // TRANSLATORS: with too many registered devices. + return messages.pgettext('login-view', 'Too many devices'); + case 'list-devices': + // TRANSLATORS: Error message shown above login input when trying to login but the app fails + // TRANSLATORS: to fetch the list of registered devices. + return messages.pgettext('login-view', 'Failed to fetch list of devices'); + case 'communication': + return 'api.mullvad.net is blocked, please check your firewall'; + default: + return messages.pgettext('login-view', 'Unknown error'); + } + } + private getStatusIcon() { const statusIconPath = this.getStatusIconPath(); return ( diff --git a/gui/src/renderer/redux/account/actions.ts b/gui/src/renderer/redux/account/actions.ts index 2b186fb831..61fc09d981 100644 --- a/gui/src/renderer/redux/account/actions.ts +++ b/gui/src/renderer/redux/account/actions.ts @@ -1,4 +1,4 @@ -import { AccountToken, IDevice } from '../../../shared/daemon-rpc-types'; +import { AccountDataError, AccountToken, IDevice } from '../../../shared/daemon-rpc-types'; interface IStartLoginAction { type: 'START_LOGIN'; @@ -13,12 +13,11 @@ interface ILoggedInAction { interface ILoginFailedAction { type: 'LOGIN_FAILED'; - error: Error; + error: AccountDataError['error']; } interface ILoginTooManyDevicesAction { type: 'TOO_MANY_DEVICES'; - error: Error; } interface IPrepareLogoutAction { @@ -115,17 +114,16 @@ function loggedIn(accountToken: AccountToken, device?: IDevice): ILoggedInAction }; } -function loginFailed(error: Error): ILoginFailedAction { +function loginFailed(error: AccountDataError['error']): ILoginFailedAction { return { type: 'LOGIN_FAILED', error, }; } -function loginTooManyDevices(error: Error): ILoginTooManyDevicesAction { +function loginTooManyDevices(): ILoginTooManyDevicesAction { return { type: 'TOO_MANY_DEVICES', - error, }; } diff --git a/gui/src/renderer/redux/account/reducers.ts b/gui/src/renderer/redux/account/reducers.ts index a017d16be7..a78a777c0a 100644 --- a/gui/src/renderer/redux/account/reducers.ts +++ b/gui/src/renderer/redux/account/reducers.ts @@ -1,11 +1,13 @@ -import { AccountToken, IDevice } from '../../../shared/daemon-rpc-types'; +import { AccountDataError, AccountToken, IDevice } from '../../../shared/daemon-rpc-types'; import { ReduxAction } from '../store'; type LoginMethod = 'existing_account' | 'new_account'; export type LoginState = | { type: 'none'; deviceRevoked: boolean } | { type: 'logging in' | 'ok'; method: LoginMethod } - | { type: 'failed' | 'too many devices'; method: LoginMethod; error: Error }; + | { type: 'too many devices'; method: LoginMethod } + | { type: 'failed'; method: 'existing_account'; error: AccountDataError['error'] } + | { type: 'failed'; method: 'new_account'; error: Error }; export interface IAccountReduxState { accountToken?: AccountToken; deviceName?: string; @@ -52,7 +54,7 @@ export default function ( case 'TOO_MANY_DEVICES': return { ...state, - status: { type: 'too many devices', method: 'existing_account', error: action.error }, + status: { type: 'too many devices', method: 'existing_account' }, }; case 'PREPARE_LOG_OUT': return { diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index f8c9fa5fff..a1997d06ad 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -1,6 +1,14 @@ export interface IAccountData { expiry: string; } + +export type AccountDataError = { + type: 'error'; + error: 'invalid-account' | 'too-many-devices' | 'list-devices' | 'communication'; +}; + +export type AccountDataResponse = ({ type: 'success' } & IAccountData) | AccountDataError; + export type AccountToken = string; export type Ip = string; export interface ILocation { diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts index 1dc7d82701..5a50fa315a 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -2,6 +2,7 @@ import { GetTextTranslations } from 'gettext-parser'; import { ILinuxSplitTunnelingApplication, IWindowsApplication } from './application-types'; import { + AccountDataError, AccountToken, BridgeSettings, BridgeState, @@ -183,7 +184,7 @@ export const ipcSchema = { device: notifyRenderer<DeviceEvent>(), devices: notifyRenderer<Array<IDevice>>(), create: invoke<void, string>(), - login: invoke<AccountToken, void>(), + login: invoke<AccountToken, AccountDataError | undefined>(), logout: invoke<void, void>(), getWwwAuthToken: invoke<void, string>(), submitVoucher: invoke<string, VoucherResponse>(), diff --git a/gui/test/unit/account-data-cache.spec.ts b/gui/test/unit/account-data-cache.spec.ts index 68595b5586..f3d6df3142 100644 --- a/gui/test/unit/account-data-cache.spec.ts +++ b/gui/test/unit/account-data-cache.spec.ts @@ -1,11 +1,12 @@ -import AccountDataCache from '../../src/main/account-data-cache'; -import { IAccountData } from '../../src/shared/daemon-rpc-types'; +import AccountDataCache, { AccountFetchError } from '../../src/main/account-data-cache'; +import { AccountDataResponse, IAccountData } from '../../src/shared/daemon-rpc-types'; import sinon from 'sinon'; import { expect, spy } from 'chai'; describe('IAccountData cache', () => { const dummyAccountToken = '9876543210'; - const dummyAccountData: IAccountData = { + const dummyAccountData: AccountDataResponse = { + type: 'success', expiry: new Date('2038-01-01').toISOString(), }; @@ -28,7 +29,7 @@ describe('IAccountData cache', () => { const watcher = new Promise<void>((resolve, reject) => { cache.fetch(dummyAccountToken, { onFinish: () => resolve(), - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }); @@ -37,14 +38,14 @@ describe('IAccountData cache', () => { it('should notify when fetch fails on the first attempt', async () => { const cache = new AccountDataCache( - (_token) => Promise.reject(new Error('Fetch fail')), + (_token) => Promise.resolve({ type: 'error', error: 'invalid-account' }), (_data) => {}, ); const watcher = new Promise<void>((resolve, reject) => { cache.fetch(dummyAccountToken, { onFinish: (_reason?: any) => resolve(), - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }); @@ -60,7 +61,7 @@ describe('IAccountData cache', () => { cache.fetch(dummyAccountToken, { onFinish: () => {}, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }); @@ -85,7 +86,7 @@ describe('IAccountData cache', () => { cache.fetch(dummyAccountToken, { onFinish: () => reject(), - onError: (_error: Error) => {}, + onError: (_error: AccountFetchError) => {}, }); }); @@ -93,7 +94,7 @@ describe('IAccountData cache', () => { }); it('should cancel first fetch', async () => { - const firstError = spy((_error: Error) => {}); + const firstError = spy((_error: AccountFetchError) => {}); const secondSuccess = spy(); const update = new Promise<IAccountData | void>((resolve, reject) => { @@ -107,7 +108,7 @@ describe('IAccountData cache', () => { onError: () => reject(), }); - return new Promise<IAccountData>((resolve) => { + return new Promise<AccountDataResponse>((resolve) => { setTimeout(() => resolve(dummyAccountData), 1000); }); } else { @@ -142,10 +143,10 @@ describe('IAccountData cache', () => { const update = new Promise((resolve, reject) => { let attempts = 0; - const fetch = () => { + const fetch = (): Promise<AccountDataResponse> => { attempts++; if (attempts === 1) { - return Promise.reject(new Error('First attempt fails')); + return Promise.resolve({ type: 'error', error: 'invalid-account' }); } else if (attempts === 2) { setTimeout(() => clock.tick(8000)); return Promise.resolve(dummyAccountData); @@ -159,7 +160,7 @@ describe('IAccountData cache', () => { cache.fetch(dummyAccountToken, { onFinish: () => {}, - onError: (_error: Error) => firstError(), + onError: (_error: AccountFetchError) => firstError(), }); setTimeout(() => { cache.fetch(dummyAccountToken, { @@ -167,7 +168,7 @@ describe('IAccountData cache', () => { secondSuccess(); setTimeout(resolve); }, - onError: (_error: Error) => {}, + onError: (_error: AccountFetchError) => {}, }); }); }); @@ -188,7 +189,7 @@ describe('IAccountData cache', () => { }; const cache = new AccountDataCache(fetch, () => {}); - const onError = (_error: Error) => {}; + const onError = (_error: AccountFetchError) => {}; cache.fetch(dummyAccountToken, { onFinish: () => {}, onError }); cache.fetch(dummyAccountToken, { onFinish: () => resolve(), onError }); }); @@ -205,14 +206,14 @@ describe('IAccountData cache', () => { const update = new Promise<void>((resolve, reject) => { let firstAttempt = true; - const fetch = () => { + const fetch = (_accountToken: string): Promise<AccountDataResponse> => { if (firstAttempt) { firstAttempt = false; setTimeout(() => clock.tick(120_000), 0); - return Promise.resolve({ expiry }); + return Promise.resolve({ type: 'success', expiry }); } else { resolve(); - return Promise.resolve({ expiry }); + return Promise.resolve({ type: 'success', expiry }); } }; @@ -220,7 +221,7 @@ describe('IAccountData cache', () => { cache.fetch(dummyAccountToken, { onFinish: () => {}, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }); @@ -235,7 +236,7 @@ describe('IAccountData cache', () => { const cache = new AccountDataCache( (_accountToken) => { fetchSpy(); - return Promise.resolve({ expiry }); + return Promise.resolve<AccountDataResponse>({ type: 'success', expiry }); }, () => {}, ); @@ -256,13 +257,13 @@ describe('IAccountData cache', () => { onFinish: async () => { resolve(); }, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }); @@ -279,7 +280,7 @@ describe('IAccountData cache', () => { const cache = new AccountDataCache( (_accountToken) => { fetchSpy(); - return Promise.resolve({ expiry }); + return Promise.resolve<AccountDataResponse>({ type: 'success', expiry }); }, () => {}, ); @@ -300,13 +301,13 @@ describe('IAccountData cache', () => { onFinish: async () => { resolve(); }, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }, - onError: (_error: Error) => reject(), + onError: (_error: AccountFetchError) => reject(), }); }); |
