summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/lib
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-04-01 16:00:22 +0200
committerAndrej Mihajlov <and@mullvad.net>2019-04-01 16:00:22 +0200
commit33c7666cc6d03b393b4e003c04c4ca1d3a4ff5cc (patch)
tree6a155396bedff004b0f733ad7fc304b8cc215336 /gui/src/renderer/lib
parentbcf731749bddebb2eb25ccc6a5b83b45de8fdb0e (diff)
parentc54a8e8af8ed5e53151ded7f66d9eea7cf7a5d20 (diff)
downloadmullvadvpn-33c7666cc6d03b393b4e003c04c4ca1d3a4ff5cc.tar.xz
mullvadvpn-33c7666cc6d03b393b4e003c04c4ca1d3a4ff5cc.zip
Merge branch 'fix-typescript-tests'
Diffstat (limited to 'gui/src/renderer/lib')
-rw-r--r--gui/src/renderer/lib/account-data-cache.ts118
-rw-r--r--gui/src/renderer/lib/auth-failure.ts3
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,