From 7632f31cf20a9b65fd0f24209ea805f9f69c9c53 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
Date: Tue, 30 Aug 2022 17:03:42 -0700
Subject: [PATCH] Show internal error toast on CDS errors
---
_locales/en/messages.json | 4 ++
.../ToastDecryptionError.stories.tsx | 31 ---------
ts/components/ToastDecryptionError.tsx | 43 -------------
ts/components/ToastInternalError.stories.tsx | 48 ++++++++++++++
ts/components/ToastInternalError.tsx | 63 +++++++++++++++++++
ts/textsecure/WebAPI.ts | 16 +++++
ts/util/handleRetry.ts | 8 ++-
ts/util/showToast.tsx | 10 +--
8 files changed, 142 insertions(+), 81 deletions(-)
delete mode 100644 ts/components/ToastDecryptionError.stories.tsx
delete mode 100644 ts/components/ToastDecryptionError.tsx
create mode 100644 ts/components/ToastInternalError.stories.tsx
create mode 100644 ts/components/ToastInternalError.tsx
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;