diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8051f18bc..6844d453a 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -643,6 +643,10 @@ } } }, + "cdsMirroringErrorToast": { + "message": "Desktop ran into a Contact Discovery Service inconsistency.", + "description": "An error popup when we discovered an inconsistency between mirrored Contact Discovery Service requests." + }, "decryptionErrorToast": { "message": "Desktop ran into a decryption error from $name$, device $deviceId$", "description": "An error popup when we haven't added an in-timeline error for decryption error, only for beta/internal users.", diff --git a/ts/components/ToastDecryptionError.stories.tsx b/ts/components/ToastDecryptionError.stories.tsx deleted file mode 100644 index 118f0d2c7..000000000 --- a/ts/components/ToastDecryptionError.stories.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import React from 'react'; -import { action } from '@storybook/addon-actions'; -import { ToastDecryptionError } from './ToastDecryptionError'; - -import { setupI18n } from '../util/setupI18n'; -import enMessages from '../../_locales/en/messages.json'; - -const i18n = setupI18n('en', enMessages); - -const defaultProps = { - deviceId: 3, - i18n, - name: 'Someone Somewhere', - onClose: action('onClose'), - onShowDebugLog: action('onShowDebugLog'), -}; - -export default { - title: 'Components/ToastDecryptionError', -}; - -export const _ToastDecryptionError = (): JSX.Element => ( - -); - -_ToastDecryptionError.story = { - name: 'ToastDecryptionError', -}; diff --git a/ts/components/ToastDecryptionError.tsx b/ts/components/ToastDecryptionError.tsx deleted file mode 100644 index c2d0c9aa8..000000000 --- a/ts/components/ToastDecryptionError.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import React from 'react'; -import type { LocalizerType } from '../types/Util'; -import { Toast } from './Toast'; - -export type ToastPropsType = { - deviceId: number; - name: string; - onShowDebugLog: () => unknown; -}; - -type PropsType = { - i18n: LocalizerType; - onClose: () => unknown; -} & ToastPropsType; - -export const ToastDecryptionError = ({ - deviceId, - i18n, - name, - onClose, - onShowDebugLog, -}: PropsType): JSX.Element => { - return ( - - {i18n('decryptionErrorToast', { - name, - deviceId, - })} - - ); -}; diff --git a/ts/components/ToastInternalError.stories.tsx b/ts/components/ToastInternalError.stories.tsx new file mode 100644 index 000000000..292b8dced --- /dev/null +++ b/ts/components/ToastInternalError.stories.tsx @@ -0,0 +1,48 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import { action } from '@storybook/addon-actions'; +import { + ToastInternalError, + ToastInternalErrorKind, +} from './ToastInternalError'; + +import { setupI18n } from '../util/setupI18n'; +import enMessages from '../../_locales/en/messages.json'; + +const i18n = setupI18n('en', enMessages); + +const defaultProps = { + i18n, + onClose: action('onClose'), + onShowDebugLog: action('onShowDebugLog'), +}; + +export default { + title: 'Components/ToastInternalError', +}; + +export const ToastDecryptionError = (): JSX.Element => ( + +); + +ToastDecryptionError.story = { + name: 'ToastDecryptionError', +}; + +export const ToastCDSMirroringError = (): JSX.Element => ( + +); + +ToastDecryptionError.story = { + name: 'ToastCDSMirroringError', +}; diff --git a/ts/components/ToastInternalError.tsx b/ts/components/ToastInternalError.tsx new file mode 100644 index 000000000..240e4cf9d --- /dev/null +++ b/ts/components/ToastInternalError.tsx @@ -0,0 +1,63 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import type { LocalizerType } from '../types/Util'; +import { missingCaseError } from '../util/missingCaseError'; +import { Toast } from './Toast'; + +export enum ToastInternalErrorKind { + DecryptionError = 'DecryptionError', + CDSMirroringError = 'CDSMirroringError', +} + +export type ToastPropsType = { + onShowDebugLog: () => unknown; +} & ( + | { + kind: ToastInternalErrorKind.DecryptionError; + deviceId: number; + name: string; + } + | { + kind: ToastInternalErrorKind.CDSMirroringError; + } +); + +type PropsType = { + i18n: LocalizerType; + onClose: () => unknown; +} & ToastPropsType; + +export const ToastInternalError = (props: PropsType): JSX.Element => { + const { kind, i18n, onClose, onShowDebugLog } = props; + + let body: string; + if (kind === ToastInternalErrorKind.DecryptionError) { + const { deviceId, name } = props; + + body = i18n('decryptionErrorToast', { + name, + deviceId, + }); + } else if (kind === ToastInternalErrorKind.CDSMirroringError) { + body = i18n('cdsMirroringErrorToast'); + } else { + throw missingCaseError(kind); + } + + return ( + + {body} + + ); +}; diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 294667064..ee37a91b9 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -63,6 +63,12 @@ import type { import { handleStatusCode, translateError } from './Utils'; import * as log from '../logging/log'; import { maybeParseUrl } from '../util/url'; +import { + ToastInternalError, + ToastInternalErrorKind, +} from '../components/ToastInternalError'; +import { showToast } from '../util/showToast'; +import { isProduction } from '../util/version'; // Note: this will break some code that expects to be able to use err.response when a // web request fails, because it will force it to text. But it is very useful for @@ -2860,6 +2866,7 @@ export function initialize({ const expectedMap = await expectedMapPromise; let matched = 0; + let warnings = 0; for (const [e164, { aci }] of actualMap) { if (!aci) { continue; @@ -2869,6 +2876,7 @@ export function initialize({ if (expectedACI === aci) { matched += 1; } else { + warnings += 1; log.warn( `cdsLookup: mirrored request has aci=${aci} for ${e164}, while ` + `expected aci=${expectedACI}` @@ -2876,6 +2884,14 @@ export function initialize({ } } + if (warnings !== 0 && !isProduction(window.getVersion())) { + log.info('cdsLookup: showing error toast'); + showToast(ToastInternalError, { + kind: ToastInternalErrorKind.CDSMirroringError, + onShowDebugLog: () => window.showDebugLog(), + }); + } + log.info(`cdsLookup: mirrored request success, matched=${matched}`); } catch (error) { log.error('cdsLookup: mirrored request error', toLogFormat(error)); diff --git a/ts/util/handleRetry.ts b/ts/util/handleRetry.ts index dcc433348..cd295de90 100644 --- a/ts/util/handleRetry.ts +++ b/ts/util/handleRetry.ts @@ -20,7 +20,10 @@ import * as RemoteConfig from '../RemoteConfig'; import { Address } from '../types/Address'; import { QualifiedAddress } from '../types/QualifiedAddress'; import { UUID } from '../types/UUID'; -import { ToastDecryptionError } from '../components/ToastDecryptionError'; +import { + ToastInternalError, + ToastInternalErrorKind, +} from '../components/ToastInternalError'; import { showToast } from './showToast'; import * as Errors from '../types/errors'; @@ -210,7 +213,8 @@ function maybeShowDecryptionToast( } log.info(`maybeShowDecryptionToast/${logId}: Showing decryption error toast`); - showToast(ToastDecryptionError, { + showToast(ToastInternalError, { + kind: ToastInternalErrorKind.DecryptionError, deviceId, name, onShowDebugLog: () => window.showDebugLog(), diff --git a/ts/util/showToast.tsx b/ts/util/showToast.tsx index d65c2f8a9..762dbd03a 100644 --- a/ts/util/showToast.tsx +++ b/ts/util/showToast.tsx @@ -24,9 +24,9 @@ import type { ToastConversationMarkedUnread } from '../components/ToastConversat import type { ToastConversationUnarchived } from '../components/ToastConversationUnarchived'; import type { ToastDangerousFileType } from '../components/ToastDangerousFileType'; import type { - ToastDecryptionError, - ToastPropsType as ToastDecryptionErrorPropsType, -} from '../components/ToastDecryptionError'; + ToastInternalError, + ToastPropsType as ToastInternalErrorPropsType, +} from '../components/ToastInternalError'; import type { ToastDeleteForEveryoneFailed } from '../components/ToastDeleteForEveryoneFailed'; import type { ToastExpired } from '../components/ToastExpired'; import type { @@ -78,8 +78,8 @@ export function showToast(Toast: typeof ToastConversationMarkedUnread): void; export function showToast(Toast: typeof ToastConversationUnarchived): void; export function showToast(Toast: typeof ToastDangerousFileType): void; export function showToast( - Toast: typeof ToastDecryptionError, - props: ToastDecryptionErrorPropsType + Toast: typeof ToastInternalError, + props: ToastInternalErrorPropsType ): void; export function showToast(Toast: typeof ToastDeleteForEveryoneFailed): void; export function showToast(Toast: typeof ToastExpired): void;