Show internal error toast on CDS errors

This commit is contained in:
Fedor Indutny 2022-08-30 17:03:42 -07:00 committed by GitHub
parent 39354b11b7
commit 7632f31cf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 142 additions and 81 deletions

View File

@ -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.",

View File

@ -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 {...defaultProps} />
);
_ToastDecryptionError.story = {
name: 'ToastDecryptionError',
};

View File

@ -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 (
<Toast
autoDismissDisabled
className="decryption-error"
onClose={onClose}
style={{ maxWidth: '500px' }}
toastAction={{
label: i18n('decryptionErrorToastAction'),
onClick: onShowDebugLog,
}}
>
{i18n('decryptionErrorToast', {
name,
deviceId,
})}
</Toast>
);
};

View File

@ -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 => (
<ToastInternalError
kind={ToastInternalErrorKind.DecryptionError}
deviceId={3}
name="Someone Somewhere"
{...defaultProps}
/>
);
ToastDecryptionError.story = {
name: 'ToastDecryptionError',
};
export const ToastCDSMirroringError = (): JSX.Element => (
<ToastInternalError
kind={ToastInternalErrorKind.CDSMirroringError}
{...defaultProps}
/>
);
ToastDecryptionError.story = {
name: 'ToastCDSMirroringError',
};

View File

@ -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 (
<Toast
autoDismissDisabled
className="internal-error-toast"
onClose={onClose}
style={{ maxWidth: '500px' }}
toastAction={{
label: i18n('decryptionErrorToastAction'),
onClick: onShowDebugLog,
}}
>
{body}
</Toast>
);
};

View File

@ -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));

View File

@ -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(),

View File

@ -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;