diff --git a/src/electron-preload/webview/primary/api/db-patch/bootstrap.ts b/src/electron-preload/webview/primary/api/db-patch/bootstrap.ts index f1edbd42..36b5079d 100644 --- a/src/electron-preload/webview/primary/api/db-patch/bootstrap.ts +++ b/src/electron-preload/webview/primary/api/db-patch/bootstrap.ts @@ -8,10 +8,11 @@ import * as DatabaseModel from "src/shared/model/database"; import {DbPatchBundle} from "./model"; import {DEFAULT_MESSAGES_STORE_PORTION_SIZE, ONE_SECOND_MS, PACKAGE_VERSION} from "src/shared/const"; import {FsDbAccount, LABEL_TYPE, SYSTEM_FOLDER_IDENTIFIERS} from "src/shared/model/database"; +import {isIgnorable404Error} from "../../util"; import {Logger} from "src/shared/model/common"; import {PROTON_MAX_CONCURRENT_FETCH, PROTON_MAX_QUERY_PORTION_LIMIT} from "src/electron-preload/webview/lib/const"; import {ProviderApi} from "src/electron-preload/webview/primary/provider-api/model"; -import {resolveCachedConfig, resolveIpcMainApi} from "src/electron-preload/lib/util"; +import {resolveCachedConfig, resolveIpcMainApi, sanitizeProtonApiError} from "src/electron-preload/lib/util"; import * as RestModel from "src/electron-preload/webview/lib/rest-model"; export const bootstrapDbPatch = ( @@ -162,9 +163,20 @@ export const bootstrapDbPatch = ( for (const bootstrappedMessageIdsChunk of chunk(bootstrappedMessageIds, PROTON_MAX_CONCURRENT_FETCH)) { await Promise.all(bootstrappedMessageIdsChunk.map(async ({ID: storedMessageId}) => { - const {Message} = await providerApi.message.getMessage(storedMessageId); - persistencePortion.push(await Database.buildMail(Message, providerApi)); - + try { + const {Message} = await providerApi.message.getMessage(storedMessageId); + persistencePortion.push(await Database.buildMail(Message, providerApi)); + } catch (error) { + if (!isIgnorable404Error(error)) { + throw error; + } + logger.warn( + `skip message fetching as it has been already removed from the trash before fetch action started`, + JSON.stringify(sanitizeProtonApiError(error)), + ); + // skipping "removed" message processing/fetching + return; + } fetchCount++; progress$.next({ progress: [ diff --git a/src/electron-preload/webview/primary/api/db-patch/patch.ts b/src/electron-preload/webview/primary/api/db-patch/patch.ts index 1b53e0a1..3b734da8 100644 --- a/src/electron-preload/webview/primary/api/db-patch/patch.ts +++ b/src/electron-preload/webview/primary/api/db-patch/patch.ts @@ -1,11 +1,12 @@ import {curryFunctionMembers} from "src/shared/util"; import * as Database from "src/electron-preload/webview/lib/database-entity"; import {DbPatch} from "src/shared/api/common"; -import {isProtonApiError, sanitizeProtonApiError} from "src/electron-preload/lib/util"; +import {isIgnorable404Error} from "src/electron-preload/webview/primary/util"; import {LABEL_TYPE, SYSTEM_FOLDER_IDENTIFIERS} from "src/shared/model/database"; import {Logger} from "src/shared/model/common"; import {ProviderApi} from "src/electron-preload/webview/primary/provider-api/model"; import * as RestModel from "src/electron-preload/webview/lib/rest-model"; +import {sanitizeProtonApiError} from "src/electron-preload/lib/util"; export const buildDbPatch = async ( providerApi: ProviderApi, @@ -103,9 +104,9 @@ export const buildDbPatch = async ( }; if (!nullUpsert) { - // TODO process 404 error of fetching individual entity ("message" case is handled, see "error.data.Code === 15052" check below) + // TODO process 404/422 error of fetching individual entity ("message" case is handled, see "error.data.Code === 15052" check below) // so we could catch the individual entity fetching error - // 404 error can be ignored as if it occurs because user was moving the email from here to there ending up + // 404/422 error can be ignored as if it occurs because user was moving the email from here to there ending up // removing it while syncing cycle was in progress // fetching mails @@ -114,19 +115,10 @@ export const buildDbPatch = async ( const response = await providerApi.message.getMessage(id); patch.mails.upsert.push(await Database.buildMail(response.Message, providerApi)); } catch (error) { - if ( - gotTrashed - && isProtonApiError(error) - && ( // message has already been removed error condition: - error.status === 422 - && error.data?.Code === 15052 - ) - ) { // ignoring the error as permissible - // TODO figure how to suppress displaying this error on "proton ui" in the case we initiated it (triggered the fetching) + if (gotTrashed && isIgnorable404Error(error)) { + // TODO suppress displaying the error on "proton ui" if message fetching got explicitly triggered vs background sync logger.warn( - // WARN don't log message-specific data as it might include sensitive fields `skip message fetching as it has been already removed from the trash before fetch action started`, - // WARN don't log full error as it might include sensitive data JSON.stringify(sanitizeProtonApiError(error)), ); } else { diff --git a/src/electron-preload/webview/primary/types.ts b/src/electron-preload/webview/primary/types.ts index 7f526de9..7278e429 100644 --- a/src/electron-preload/webview/primary/types.ts +++ b/src/electron-preload/webview/primary/types.ts @@ -1,8 +1,8 @@ export interface ProtonApiError { config?: unknown; - data?: - & {Error?: unknown; ErrorDescription?: unknown} - & Partial>; + data?: {Error?: unknown; ErrorDescription?: unknown; Code?: number; dataCode?: number}; + dataCode?: number; + dataError?: string; message: string; name: string; response?: Partial; diff --git a/src/electron-preload/webview/primary/util.ts b/src/electron-preload/webview/primary/util.ts index d0eb17f5..d3ef9490 100644 --- a/src/electron-preload/webview/primary/util.ts +++ b/src/electron-preload/webview/primary/util.ts @@ -1,6 +1,14 @@ import {buildDbPatchRetryPipeline, isErrorOnRateLimitedMethodCall} from "src/electron-preload/webview/lib/util"; import {isProtonApiError, sanitizeProtonApiError} from "src/electron-preload/lib/util"; +export const isIgnorable404Error: (error: unknown) => boolean = (() => { + const errorCodes: ReadonlyArray = [2501, 15052]; + return (error: unknown): boolean => { + if (!isProtonApiError(error) || error.status !== 422) return false; + return errorCodes.includes(error.data?.Code) || errorCodes.includes(error.dataCode); + }; +})(); + type preprocessErrorType = Parameters[0]; export const preprocessError: preprocessErrorType = (() => {