Improve decryption error UI by consolidating error messages and providing instructions when possible (#9544)

* Improve decryption error UI by consolidating error messages and providing instructions when possible

* Fix TS strict errors

* Rename .scss to .pcss

* Avoid accessing clipboard, Cypress doesn't like it

* Display DecryptionFailureBar alongside other AuxPanel bars

* Add comments

* Add small margin off-screen for visible decryption failures

* Fix some more TS strict errors

* Add unit tests for DecryptionFailureBar

* Add button to resend key requests manually

* Remove references to matrix-js-sdk crypto internals

* Add hysteresis to visible decryption failures

* Add comment

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Add comment

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Don't create empty div if we're not showing resend requests button

* cancel updateSessions on unmount

* Update unit tests

* Fix lint and implicit any

* Simplify visible event bounds checking

* Adjust cypress test descriptions

* Add percy snapshots

* Update src/components/structures/TimelinePanel.tsx

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Add comments on TimelinePanel IState

* comment

* Add names to percy snapshots

* Show Resend Key Requests button when there are sessions that haven't already been requested via this bar

* We no longer request keys from senders

* update i18n

* update expected text in cypress test

* don't download keys ourselves, update device info in response to updates from client

* fix ts strict errors

* visibledecryptionfailures undefined handling

* Fix implicitAny errors

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Faye Duxovni
2022-12-15 12:24:33 -05:00
committed by GitHub
parent b728b27435
commit 4724506320
18 changed files with 1779 additions and 165 deletions

View File

@@ -55,7 +55,6 @@ import PlatformPeg from "../../../PlatformPeg";
import MemberAvatar from "../avatars/MemberAvatar";
import SenderProfile from "../messages/SenderProfile";
import MessageTimestamp from "../messages/MessageTimestamp";
import TooltipButton from "../elements/TooltipButton";
import { IReadReceiptInfo } from "./ReadReceiptMarker";
import MessageActionBar from "../messages/MessageActionBar";
import ReactionsRow from "../messages/ReactionsRow";
@@ -70,7 +69,7 @@ import { ThreadNotificationState } from "../../../stores/notifications/ThreadNot
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationStateEvents } from "../../../stores/notifications/NotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { ButtonEvent } from "../elements/AccessibleButton";
import { copyPlaintext, getSelectedText } from "../../../utils/strings";
import { DecryptionFailureTracker } from "../../../DecryptionFailureTracker";
import RedactedBody from "../messages/RedactedBody";
@@ -234,8 +233,6 @@ interface IState {
actionBarFocused: boolean;
// Whether the event's sender has been verified.
verified: string;
// Whether onRequestKeysClick has been called since mounting.
previouslyRequestedKeys: boolean;
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions?: Relations | null | undefined;
@@ -283,8 +280,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
actionBarFocused: false,
// Whether the event's sender has been verified.
verified: null,
// Whether onRequestKeysClick has been called since mounting.
previouslyRequestedKeys: false,
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),
// Context menu position
@@ -758,20 +753,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
});
};
private onRequestKeysClick = () => {
this.setState({
// Indicate in the UI that the keys have been requested (this is expected to
// be reset if the component is mounted in the future).
previouslyRequestedKeys: true,
});
// Cancel any outgoing key request for this event and resend it. If a response
// is received for the request with the required keys, the event could be
// decrypted successfully.
MatrixClientPeg.get().cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
};
private onPermalinkClicked = (e) => {
private onPermalinkClicked = (e: MouseEvent) => {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Element when clicked.
e.preventDefault();
@@ -789,11 +771,11 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const ev = this.props.mxEvent;
// no icon for local rooms
if (isLocalRoom(ev.getRoomId())) return;
if (isLocalRoom(ev.getRoomId()!)) return;
// event could not be decrypted
if (ev.getContent().msgtype === "m.bad.encrypted") {
return <E2ePadlockUndecryptable />;
if (ev.isDecryptionFailure()) {
return <E2ePadlockDecryptionFailure />;
}
// event is encrypted and not redacted, display padlock corresponding to whether or not it is verified
@@ -1160,55 +1142,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const timestamp = showTimestamp && ts ? messageTimestamp : null;
const keyRequestHelpText = (
<div className="mx_EventTile_keyRequestInfo_tooltip_contents">
<p>
{this.state.previouslyRequestedKeys
? _t(
"Your key share request has been sent - please check your other sessions " +
"for key share requests.",
)
: _t(
"Key share requests are sent to your other sessions automatically. If you " +
"rejected or dismissed the key share request on your other sessions, click " +
"here to request the keys for this session again.",
)}
</p>
<p>
{_t(
"If your other sessions do not have the key for this message you will not " +
"be able to decrypt them.",
)}
</p>
</div>
);
const keyRequestInfoContent = this.state.previouslyRequestedKeys
? _t("Key request sent.")
: _t(
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.",
{},
{
requestLink: (sub) => (
<AccessibleButton
className="mx_EventTile_rerequestKeysCta"
kind="link_inline"
tabIndex={0}
onClick={this.onRequestKeysClick}
>
{sub}
</AccessibleButton>
),
},
);
const keyRequestInfo =
isEncryptionFailure && !isRedacted ? (
<div className="mx_EventTile_keyRequestInfo">
<span className="mx_EventTile_keyRequestInfo_text">{keyRequestInfoContent}</span>
<TooltipButton helpText={keyRequestHelpText} />
</div>
) : null;
let reactionsRow;
if (!isRedacted) {
reactionsRow = (
@@ -1543,7 +1476,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
},
this.context.showHiddenEvents,
)}
{keyRequestInfo}
{actionBar}
{this.props.layout === Layout.IRC && (
<>
@@ -1578,23 +1510,19 @@ const SafeEventTile = forwardRef((props: EventTileProps, ref: RefObject<Unwrappe
});
export default SafeEventTile;
function E2ePadlockUndecryptable(props) {
return <E2ePadlock title={_t("This message cannot be decrypted")} icon={E2ePadlockIcon.Warning} {...props} />;
}
function E2ePadlockUnverified(props) {
function E2ePadlockUnverified(props: Omit<IE2ePadlockProps, "title" | "icon">) {
return <E2ePadlock title={_t("Encrypted by an unverified session")} icon={E2ePadlockIcon.Warning} {...props} />;
}
function E2ePadlockUnencrypted(props) {
function E2ePadlockUnencrypted(props: Omit<IE2ePadlockProps, "title" | "icon">) {
return <E2ePadlock title={_t("Unencrypted")} icon={E2ePadlockIcon.Warning} {...props} />;
}
function E2ePadlockUnknown(props) {
function E2ePadlockUnknown(props: Omit<IE2ePadlockProps, "title" | "icon">) {
return <E2ePadlock title={_t("Encrypted by a deleted session")} icon={E2ePadlockIcon.Normal} {...props} />;
}
function E2ePadlockUnauthenticated(props) {
function E2ePadlockUnauthenticated(props: Omit<IE2ePadlockProps, "title" | "icon">) {
return (
<E2ePadlock
title={_t("The authenticity of this encrypted message can't be guaranteed on this device.")}
@@ -1604,9 +1532,20 @@ function E2ePadlockUnauthenticated(props) {
);
}
function E2ePadlockDecryptionFailure(props: Omit<IE2ePadlockProps, "title" | "icon">) {
return (
<E2ePadlock
title={_t("This message could not be decrypted")}
icon={E2ePadlockIcon.DecryptionFailure}
{...props}
/>
);
}
enum E2ePadlockIcon {
Normal = "normal",
Warning = "warning",
DecryptionFailure = "decryption_failure",
}
interface IE2ePadlockProps {