diff options
Diffstat (limited to 'gui/src/renderer/lib')
| -rw-r--r-- | gui/src/renderer/lib/account-data-cache.ts | 118 | ||||
| -rw-r--r-- | gui/src/renderer/lib/auth-failure.ts | 3 |
2 files changed, 119 insertions, 2 deletions
diff --git a/gui/src/renderer/lib/account-data-cache.ts b/gui/src/renderer/lib/account-data-cache.ts new file mode 100644 index 0000000000..51154c9792 --- /dev/null +++ b/gui/src/renderer/lib/account-data-cache.ts @@ -0,0 +1,118 @@ +import log from 'electron-log'; +import { AccountToken, IAccountData } from '../../shared/daemon-rpc-types'; + +export enum AccountFetchRetryAction { + stop, + retry, +} +interface IAccountFetchWatcher { + onFinish: () => void; + onError: (error: Error) => AccountFetchRetryAction; +} + +// An account data cache that helps to throttle RPC requests to get_account_data and retain the +// cached value for 1 minute. +export default class AccountDataCache { + private currentAccount?: AccountToken; + private expiresAt?: Date; + private fetchAttempt = 0; + private fetchRetryTimeout?: NodeJS.Timeout; + private watchers: IAccountFetchWatcher[] = []; + + constructor( + private fetchHandler: (token: AccountToken) => Promise<IAccountData>, + private updateHandler: (data?: IAccountData) => void, + ) {} + + public fetch(accountToken: AccountToken, watcher?: IAccountFetchWatcher) { + // invalidate cache if account token has changed + if (accountToken !== this.currentAccount) { + this.invalidate(); + this.currentAccount = accountToken; + } + + // Only fetch is value has expired + if (this.isExpired()) { + if (watcher) { + this.watchers.push(watcher); + } + + this.performFetch(accountToken); + } else if (watcher) { + watcher.onFinish(); + } + } + + public invalidate() { + if (this.fetchRetryTimeout) { + clearTimeout(this.fetchRetryTimeout); + this.fetchRetryTimeout = undefined; + this.fetchAttempt = 0; + } + + this.expiresAt = undefined; + this.updateHandler(); + this.notifyWatchers((watcher) => { + watcher.onError(new Error('Cancelled')); + }); + } + + private setValue(value: IAccountData) { + this.expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration + this.updateHandler(value); + this.notifyWatchers((watcher) => watcher.onFinish()); + } + + private isExpired() { + return !this.expiresAt || this.expiresAt < new Date(); + } + + private async performFetch(accountToken: AccountToken) { + 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); + + if (this.currentAccount === accountToken) { + this.setValue(accountData); + } + } catch (error) { + if (this.currentAccount === accountToken) { + this.handleFetchError(accountToken, error); + } + } + } + + private handleFetchError(accountToken: AccountToken, error: any) { + let shouldRetry = true; + + this.notifyWatchers((watcher) => { + if (watcher.onError(error) === AccountFetchRetryAction.stop) { + shouldRetry = false; + } + }); + + if (shouldRetry) { + this.scheduleRetry(accountToken); + } + } + + private scheduleRetry(accountToken: AccountToken) { + this.fetchAttempt += 1; + + // tslint:disable-next-line + const delay = Math.min(2048, 1 << (this.fetchAttempt + 2)) * 1000; + + log.warn(`Failed to fetch account data. Retrying in ${delay} ms`); + + this.fetchRetryTimeout = global.setTimeout(() => { + this.fetchRetryTimeout = undefined; + this.performFetch(accountToken); + }, delay); + } + + private notifyWatchers(notify: (watcher: IAccountFetchWatcher) => void) { + this.watchers.splice(0).forEach(notify); + } +} diff --git a/gui/src/renderer/lib/auth-failure.ts b/gui/src/renderer/lib/auth-failure.ts index ae381d5677..6428d0f65a 100644 --- a/gui/src/renderer/lib/auth-failure.ts +++ b/gui/src/renderer/lib/auth-failure.ts @@ -18,8 +18,7 @@ export function parseAuthFailure(rawFailureMessage?: string): IAuthFailure { if (results && results.length === 3) { const kind = parseRawFailureKind(results[1]); - const message = - kind === AuthFailureKind.unknown ? rawFailureMessage : messageForFailureKind(kind); + const message = kind === AuthFailureKind.unknown ? results[2] : messageForFailureKind(kind); return { kind, |
