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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user