Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

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

Merged
merged 46 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
1461d3b
Improve decryption error UI by consolidating error messages and provi…
duxovni Sep 29, 2022
ab5be3b
Fix TS strict errors
duxovni Nov 4, 2022
a1bbde0
Rename .scss to .pcss
duxovni Nov 4, 2022
12e2454
Avoid accessing clipboard, Cypress doesn't like it
duxovni Nov 4, 2022
6c0ed4f
Display DecryptionFailureBar alongside other AuxPanel bars
duxovni Nov 4, 2022
4400e55
Add comments
duxovni Nov 4, 2022
1075c3c
Add small margin off-screen for visible decryption failures
duxovni Nov 4, 2022
e067337
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Nov 4, 2022
dcd5b73
Fix some more TS strict errors
duxovni Nov 4, 2022
3c24224
Add unit tests for DecryptionFailureBar
duxovni Nov 4, 2022
b2a0b68
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 1, 2022
e0ef3c5
Add button to resend key requests manually
duxovni Dec 1, 2022
a7583c5
Remove references to matrix-js-sdk crypto internals
duxovni Dec 1, 2022
8277459
Add hysteresis to visible decryption failures
duxovni Dec 1, 2022
d286543
Add comment
duxovni Dec 1, 2022
a75d090
Add comment
duxovni Dec 1, 2022
79071c9
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 1, 2022
dc7dfae
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 1, 2022
2b84452
Don't create empty div if we're not showing resend requests button
duxovni Dec 1, 2022
24c9707
cancel updateSessions on unmount
duxovni Dec 1, 2022
b73fd4c
Update unit tests
duxovni Dec 1, 2022
077d690
Fix lint and implicit any
duxovni Dec 1, 2022
b417e34
Simplify visible event bounds checking
duxovni Dec 6, 2022
348fb35
Adjust cypress test descriptions
duxovni Dec 6, 2022
f4af1db
Add percy snapshots
duxovni Dec 6, 2022
cad8009
Merge branch 'develop' into fayed/decryption-error-ui
t3chguy Dec 6, 2022
0ea3cd2
Update src/components/structures/TimelinePanel.tsx
duxovni Dec 6, 2022
47899e4
Add comments on TimelinePanel IState
duxovni Dec 6, 2022
27c486d
comment
duxovni Dec 6, 2022
dc90be6
Add names to percy snapshots
duxovni Dec 6, 2022
c1465a5
Show Resend Key Requests button when there are sessions that haven't …
duxovni Dec 8, 2022
0c8733d
We no longer request keys from senders
duxovni Dec 9, 2022
e3c07f1
update i18n
duxovni Dec 9, 2022
877c740
update expected text in cypress test
duxovni Dec 9, 2022
9a33a1c
don't download keys ourselves, update device info in response to upda…
duxovni Dec 9, 2022
f0e676b
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 9, 2022
b9d63e3
fix ts strict errors
duxovni Dec 10, 2022
d96214f
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 10, 2022
d218154
visibledecryptionfailures undefined handling
duxovni Dec 10, 2022
ec5cd26
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 12, 2022
bd58a32
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 12, 2022
b2c9643
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 13, 2022
547dd92
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 14, 2022
a72a1db
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 14, 2022
fb418ee
Fix implicitAny errors
duxovni Dec 15, 2022
3f5a0fa
Merge branch 'develop' into fayed/decryption-error-ui
duxovni Dec 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions cypress/e2e/crypto/decryption-failure.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,13 @@ describe("Decryption Failures", () => {

cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_button").click();

cy.get(".mx_CreateSecretStorageDialog button.mx_Dialog_primary").click();
cy.get(".mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn").click();
cy.get("button.mx_Dialog_primary").click();
cy.get(".mx_Dialog").within(() => {
cy.contains(".mx_Dialog_primary", "Continue").click();
cy.get(".mx_CreateSecretStorageDialog_recoveryKey code").invoke("text").as("securityKey");
// Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851
cy.contains(".mx_AccessibleButton", "Download").click();
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
});

cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline")
.should("have.text", "Requesting keys to decrypt messages...");
Expand Down
4 changes: 2 additions & 2 deletions res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@
@import "./views/messages/_CallEvent.pcss";
@import "./views/messages/_CreateEvent.pcss";
@import "./views/messages/_DateSeparator.pcss";
@import "./views/messages/_DecryptionFailureBody.scss";
@import "./views/messages/_DecryptionFailureBody.pcss";
@import "./views/messages/_DisambiguatedProfile.pcss";
@import "./views/messages/_EventTileBubble.pcss";
@import "./views/messages/_HiddenBody.pcss";
Expand Down Expand Up @@ -260,7 +260,7 @@
@import "./views/rooms/_Autocomplete.pcss";
@import "./views/rooms/_AuxPanel.pcss";
@import "./views/rooms/_BasicMessageComposer.pcss";
@import "./views/rooms/_DecryptionFailureBar.scss";
@import "./views/rooms/_DecryptionFailureBar.pcss";
@import "./views/rooms/_E2EIcon.pcss";
@import "./views/rooms/_EmojiButton.pcss";
@import "./views/rooms/_EditMessageComposer.pcss";
Expand Down
9 changes: 7 additions & 2 deletions src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export interface IRoomState {
threadId?: string;
liveTimeline?: EventTimeline;
narrow: boolean;
// List of undecryptable events currently visible on-screen
visibleDecryptionFailures?: MatrixEvent[];
duxovni marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -2219,8 +2220,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
) }
</AccessibleButton>
);
} else if (this.state.visibleDecryptionFailures && this.state.visibleDecryptionFailures.length > 0) {
aux = <DecryptionFailureBar
}

let decryptionFailureBar: JSX.Element | undefined;
if (this.state.visibleDecryptionFailures && this.state.visibleDecryptionFailures.length > 0) {
decryptionFailureBar = <DecryptionFailureBar
failures={this.state.visibleDecryptionFailures}
room={this.state.room}
/>;
Expand All @@ -2246,6 +2250,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
resizeNotifier={this.props.resizeNotifier}
>
{ aux }
{ decryptionFailureBar }
</AuxPanel>
);

Expand Down
13 changes: 12 additions & 1 deletion src/components/structures/TimelinePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ const READ_RECEIPT_INTERVAL_MS = 500;

const READ_MARKER_DEBOUNCE_MS = 100;

// How far off-screen a decryption failure can be for it to still count as "visible"
const VISIBLE_DECRYPTION_FAILURE_MARGIN = 100;

const debuglog = (...args: any[]) => {
if (SettingsStore.getValue("debug_timeline_panel")) {
logger.log.call(console, "TimelinePanel debuglog:", ...args);
Expand Down Expand Up @@ -1584,13 +1587,21 @@ class TimelinePanel extends React.Component<IProps, IState> {
: null;
}

/**
* Get a list of undecryptable events currently visible on-screen.
*
* @returns {MatrixEvent[] | null} A list of undecryptable events, or null if
* the list of events could not be determined.
*/
public getVisibleDecryptionFailures(): MatrixEvent[] | null {
duxovni marked this conversation as resolved.
Show resolved Hide resolved
const messagePanel = this.messagePanel.current;
if (!messagePanel) return null;

const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
if (!messagePanelNode) return null; // sometimes this happens for fresh rooms/post-sync
const wrapperRect = messagePanelNode.getBoundingClientRect();
const screenTop = wrapperRect.top - VISIBLE_DECRYPTION_FAILURE_MARGIN;
const screenBottom = wrapperRect.bottom + VISIBLE_DECRYPTION_FAILURE_MARGIN;
duxovni marked this conversation as resolved.
Show resolved Hide resolved

let enteredVisibleRange = false;

Expand All @@ -1602,7 +1613,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
if (!node) continue;

const boundingRect = node.getBoundingClientRect();
if (boundingRect.top <= wrapperRect.bottom && boundingRect.bottom >= wrapperRect.top) {
if (boundingRect.top <= screenBottom && boundingRect.bottom >= screenTop) {
enteredVisibleRange = true;
duxovni marked this conversation as resolved.
Show resolved Hide resolved
duxovni marked this conversation as resolved.
Show resolved Hide resolved
if (ev.isDecryptionFailure()) result.push(ev);
richvdh marked this conversation as resolved.
Show resolved Hide resolved
} else if (enteredVisibleRange) {
duxovni marked this conversation as resolved.
Show resolved Hide resolved
duxovni marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
1 change: 1 addition & 0 deletions src/components/views/messages/DecryptionFailureBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import { IBodyProps } from "./IBodyProps";

// A placeholder element for messages that could not be decrypted
export default class DecryptionFailureBody extends React.Component<Partial<IBodyProps>> {
duxovni marked this conversation as resolved.
Show resolved Hide resolved
render() {
return (
Expand Down
13 changes: 11 additions & 2 deletions src/components/views/rooms/DecryptionFailureBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,29 @@ const WAIT_PERIOD = 5000;
export const DecryptionFailureBar: React.FC<IProps> = ({ failures, room }) => {
const context = useContext(MatrixClientContext);

// Display a spinner for a few seconds before presenting an error message,
// in case keys are about to arrive
const [waiting, setWaiting] = useState<boolean>(true);
duxovni marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
const timeout = setTimeout(() => setWaiting(false), WAIT_PERIOD);
return () => clearTimeout(timeout);
}, []);

// Is this device unverified?
const [needsVerification, setNeedsVerification] = useState<boolean>(false);
// Does this user have verified devices other than this device?
const [hasOtherVerifiedDevices, setHasOtherVerifiedDevices] = useState<boolean>(false);
// Does this user have key backups?
const [hasKeyBackup, setHasKeyBackup] = useState<boolean>(false);

// Keep track of session IDs we've re-sent key requests for
const [requestedSessions, setRequestedSessions] = useState<Set<string>>(new Set());

useEffect(() => {
if (needsVerification || !hasOtherVerifiedDevices) return;

// Aggregate session IDs of undecryptable messages, and resend key requests
// for any sessions we weren't already handling.
const newRequestedSessions = new Set<string>();
for (const event of failures) {
const sessionId = event.getWireContent().session_id;
Expand All @@ -74,11 +82,12 @@ export const DecryptionFailureBar: React.FC<IProps> = ({ failures, room }) => {
}
}, [needsVerification, hasOtherVerifiedDevices, failures, room, requestedSessions, context]);

// Recheck which devices are verified and whether we have key backups
const updateDeviceInfo = useCallback(async () => {
context.crypto.deviceList.invalidateUserDeviceList(context.getUserId());
context.crypto.deviceList.invalidateUserDeviceList(context.getUserId()!);
await context.crypto.deviceList.refreshOutdatedDeviceLists();
duxovni marked this conversation as resolved.
Show resolved Hide resolved

const deviceId = context.getDeviceId();
const deviceId = context.getDeviceId()!;
let verified = true; // if we can't get a clear answer, don't bug the user about verifying
try {
verified = context.checkIfOwnDeviceCrossSigned(deviceId);
Expand Down
Loading