diff --git a/src/electron-main/constants.ts b/src/electron-main/constants.ts index 1fe52e5ad..f475d7091 100644 --- a/src/electron-main/constants.ts +++ b/src/electron-main/constants.ts @@ -36,6 +36,7 @@ export const INITIAL_STORES: Readonly<{ timeouts: { // "fetchingRateLimiting" values need to be taking into the account defining the "fetching" timeout fetching: ONE_SECOND_MS * 60 * 60, // 60 minutes + webViewApiPing: ONE_SECOND_MS * 15, }, }; }, diff --git a/src/electron-main/storage-upgrade.ts b/src/electron-main/storage-upgrade.ts index 1ab6e47cc..5338d6839 100644 --- a/src/electron-main/storage-upgrade.ts +++ b/src/electron-main/storage-upgrade.ts @@ -24,6 +24,11 @@ const CONFIG_UPGRADES: Record void> = { config.timeouts = INITIAL_STORES.config().timeouts; } }, + "2.0.0-beta.8": (config) => { + if (typeof config.timeouts.webViewApiPing === "undefined") { + config.timeouts.webViewApiPing = INITIAL_STORES.config().timeouts.webViewApiPing; + } + }, }; const SETTINGS_UPGRADES: Record void> = { diff --git a/src/electron-preload/webview/protonmail/api.ts b/src/electron-preload/webview/protonmail/api.ts index 61bae3aca..9ea02d2ca 100644 --- a/src/electron-preload/webview/protonmail/api.ts +++ b/src/electron-preload/webview/protonmail/api.ts @@ -17,6 +17,7 @@ import {PROTONMAIL_IPC_WEBVIEW_API, ProtonmailApi, ProtonmailNotificationOutput} import {StatusCodeError} from "src/shared/model/error"; import {Unpacked} from "src/shared/types"; import {angularJsHttpResponseTypeGuard, isUpsertOperationType, preprocessError} from "./lib/uilt"; +import {asyncDelay, curryFunctionMembers, isEntityUpdatesPatchNotEmpty} from "src/shared/util"; import {buildContact, buildFolder, buildMail} from "./lib/database"; import { buildDbPatchRetryPipeline, @@ -27,7 +28,6 @@ import { waitElements, } from "src/electron-preload/webview/util"; import {buildLoggerBundle} from "src/electron-preload/util"; -import {curryFunctionMembers, isEntityUpdatesPatchNotEmpty} from "src/shared/util"; const _logger = curryFunctionMembers(WEBVIEW_LOGGERS.protonmail, "[api]"); const twoFactorCodeElementId = "twoFactorCode"; @@ -84,7 +84,14 @@ const endpoints: ProtonmailApi = { logger.info(); if (!isLoggedIn()) { - throw new Error("protonmail:buildDbPatch(): user is supposed to be logged-in"); + // TODO handle switching from built-in webclient to remote and back more properly + // the account state keeps the "signed-in" state despite of page still being reloded + // so we need to reset "signed-in" state with "account.entryUrl" value change + await asyncDelay(ONE_SECOND_MS * 5); + + if (!isLoggedIn()) { + throw new Error("protonmail:buildDbPatch(): user is supposed to be logged-in"); + } } if (!input.metadata || !input.metadata.latestEventId) { diff --git a/src/shared/model/options.ts b/src/shared/model/options.ts index 082db8504..87d2ea3ef 100644 --- a/src/shared/model/options.ts +++ b/src/shared/model/options.ts @@ -28,6 +28,7 @@ export interface Config extends BaseConfig, Partial { }; timeouts: { fetching: number; + webViewApiPing: number; }; } diff --git a/src/web/src/app/_core/electron.service.ts b/src/web/src/app/_core/electron.service.ts index 6aa2864f6..64964d02b 100644 --- a/src/web/src/app/_core/electron.service.ts +++ b/src/web/src/app/_core/electron.service.ts @@ -1,12 +1,15 @@ import {Injectable} from "@angular/core"; import {Model} from "pubsub-to-stream-api"; -import {concat, concatMap, delay, retryWhen, takeWhile} from "rxjs/operators"; +import {Store, select} from "@ngrx/store"; +import {concat, concatMap, delay, retryWhen, switchMap, take, takeWhile} from "rxjs/operators"; import {from, of, throwError} from "rxjs"; import {AccountType} from "src/shared/model/account"; import {IPC_MAIN_API} from "src/shared/api/main"; import {ONE_SECOND_MS} from "src/shared/constants"; +import {OptionsSelectors} from "src/web/src/app/store/selectors"; import {PROTONMAIL_IPC_WEBVIEW_API} from "src/shared/api/webview/protonmail"; +import {State} from "src/web/src/app/store/reducers/options"; import {TUTANOTA_IPC_WEBVIEW_API} from "src/shared/api/webview/tutanota"; import {WebViewApi} from "src/shared/api/webview/common"; import {getZoneNameBoundWebLogger} from "src/web/src/util"; @@ -17,11 +20,13 @@ const logger = getZoneNameBoundWebLogger("[accounts.effects]"); @Injectable() export class ElectronService { - readonly webViewPingIntervalMs = ONE_SECOND_MS / 2; - readonly webViewPingTimeoutMs = ONE_SECOND_MS * 20; - readonly apiCallTimeoutMs = ONE_SECOND_MS * 15; + readonly defaultCommonApiCallTimeoutMs = ONE_SECOND_MS * 15; + readonly webViewApiPingIntervalMs = ONE_SECOND_MS / 2; + readonly timeouts$ = this.store.pipe(select(OptionsSelectors.CONFIG.timeouts)); - constructor() {} + constructor( + private store: Store, + ) {} webViewClient(webView: Electron.WebviewTag, type: T, options?: CallOptions) { // TODO TS: get rid of "as any" @@ -31,13 +36,19 @@ export class ElectronService { // TODO consider removing "ping" API // TODO it's sufficient to "ping" API initialization only once since there is no dynamic api de-registration enabled const pingStart = Number(new Date()); - const ping$ = from(apiClient("ping", {timeoutMs: 1})({zoneName: logger.zoneName()}).pipe( - retryWhen((errors) => errors.pipe( - takeWhile(() => (Number(new Date()) - pingStart) < this.webViewPingTimeoutMs), - delay(this.webViewPingIntervalMs), - concat(throwError(new Error(`Failed to wait for "webview:${type}" service provider initialization`))), - )), - ).toPromise()); + const ping$ = this.timeouts$.pipe( + take(1), + // tslint:disable-next-line:ban + switchMap((timeouts) => { + return from(apiClient("ping", {timeoutMs: 1})({zoneName: logger.zoneName()}).pipe( + retryWhen((errors) => errors.pipe( + takeWhile(() => (Number(new Date()) - pingStart) < timeouts.webViewApiPing), + delay(this.webViewApiPingIntervalMs), + concat(throwError(new Error(`Failed to wait for "webview:${type}" service provider initialization`))), + )), + ).toPromise()); + }), + ); return ping$.pipe( concatMap(() => of(apiClient)), @@ -53,7 +64,7 @@ export class ElectronService { private buildApiCallOptions(options: CallOptions = {}): Model.CallOptions { return Object.assign( - {timeoutMs: this.apiCallTimeoutMs, notificationWrapper: Zone.current.run.bind(Zone.current)}, + {timeoutMs: this.defaultCommonApiCallTimeoutMs, notificationWrapper: Zone.current.run.bind(Zone.current)}, options, ); }