diff --git a/cypress/e2e/crypto/decryption-failure.spec.ts b/cypress/e2e/crypto/decryption-failure.spec.ts deleted file mode 100644 index 4de2af0e818..00000000000 --- a/cypress/e2e/crypto/decryption-failure.spec.ts +++ /dev/null @@ -1,277 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; -import type { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { HomeserverInstance } from "../../plugins/utils/homeserver"; -import { UserCredentials } from "../../support/login"; -import { handleVerificationRequest } from "./utils"; -import { skipIfRustCrypto } from "../../support/util"; - -const ROOM_NAME = "Test room"; -const TEST_USER = "Alia"; -const BOT_USER = "Benjamin"; - -type EmojiMapping = [emoji: string, name: string]; - -const waitForVerificationRequest = (cli: MatrixClient): Promise => { - return new Promise((resolve) => { - const onVerificationRequestEvent = (request: VerificationRequest) => { - // @ts-ignore CryptoEvent is not exported to window.matrixcs; using the string value here - cli.off("crypto.verification.request", onVerificationRequestEvent); - resolve(request); - }; - // @ts-ignore - cli.on("crypto.verification.request", onVerificationRequestEvent); - }); -}; - -const checkTimelineNarrow = (button = true) => { - cy.viewport(800, 600); // SVGA - cy.get(".mx_LeftPanel_minimized").should("exist"); // Wait until the left panel is minimized - cy.findByRole("button", { name: "Room info" }).click(); // Open the right panel to make the timeline narrow - cy.get(".mx_BaseCard").should("exist"); - - // Ensure the failure bar does not cover the timeline - cy.get(".mx_RoomView_body .mx_EventTile.mx_EventTile_last").should("be.visible"); - - // Ensure the indicator does not overflow the timeline - cy.findByTestId("decryption-failure-bar-indicator").should("be.visible"); - - if (button) { - // Ensure the button does not overflow the timeline - cy.get("[data-testid='decryption-failure-bar-button']:last-of-type").should("be.visible"); - } - - cy.findByRole("button", { name: "Room info" }).click(); // Close the right panel - cy.get(".mx_BaseCard").should("not.exist"); - cy.viewport(1000, 660); // Reset to the default size -}; - -describe("Decryption Failure Bar", () => { - let homeserver: HomeserverInstance | undefined; - let testUser: UserCredentials | undefined; - let bot: MatrixClient | undefined; - let roomId: string; - - beforeEach(function () { - skipIfRustCrypto(); - cy.startHomeserver("default").then((hs: HomeserverInstance) => { - homeserver = hs; - cy.initTestUser(homeserver, TEST_USER) - .then((creds: UserCredentials) => { - testUser = creds; - }) - .then(() => { - cy.getBot(homeserver, { displayName: BOT_USER }).then((cli) => { - bot = cli; - }); - }) - .then(() => { - cy.createRoom({ name: ROOM_NAME }).then((id) => { - roomId = id; - }); - }) - .then(() => { - cy.inviteUser(roomId, bot.getUserId()); - cy.visit("/#/room/" + roomId); - cy.findByText(BOT_USER + " joined the room").should("exist"); - }) - .then(() => { - cy.getClient() - .then(async (cli) => { - await cli.setRoomEncryption(roomId, { algorithm: "m.megolm.v1.aes-sha2" }); - await bot.setRoomEncryption(roomId, { algorithm: "m.megolm.v1.aes-sha2" }); - }) - .then(() => { - bot.getRoom(roomId).setBlacklistUnverifiedDevices(true); - }); - }); - }); - }); - - afterEach(() => { - cy.stopHomeserver(homeserver); - }); - - it( - "should prompt the user to verify, if this device isn't verified " + - "and there are other verified devices or backups", - () => { - let otherDevice: MatrixClient | undefined; - cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true }) - .then(async (cli) => { - otherDevice = cli; - }) - .then(() => { - cy.botSendMessage(bot, roomId, "test"); - cy.get(".mx_DecryptionFailureBar_start_headline").within(() => { - cy.findByText("Decrypting messages…").should("be.visible"); - cy.findByText("Verify this device to access all messages").should("be.visible"); - }); - - checkTimelineNarrow(); - - cy.get(".mx_DecryptionFailureBar").percySnapshotElement( - "DecryptionFailureBar prompts user to verify", - { - widths: [320, 640], - }, - ); - - cy.get(".mx_DecryptionFailureBar_end").within(() => { - cy.findByText("Resend key requests").should("not.exist"); - cy.findByRole("button", { name: "Verify" }).click(); - }); - - const verificationRequestPromise = waitForVerificationRequest(otherDevice); - cy.findByRole("button", { name: "Verify with another device" }).click(); - cy.findByText("To proceed, please accept the verification request on your other device.").should( - "be.visible", - ); - cy.wrap(verificationRequestPromise).then((verificationRequest: VerificationRequest) => { - cy.wrap(verificationRequest.accept()); - cy.wrap( - handleVerificationRequest(verificationRequest), - // extra timeout, as this sometimes takes a while - { timeout: 30_000 }, - ).then((emojis: EmojiMapping[]) => { - cy.get(".mx_VerificationShowSas_emojiSas_block").then((emojiBlocks) => { - emojis.forEach((emoji: EmojiMapping, index: number) => { - expect(emojiBlocks[index].textContent.toLowerCase()).to.eq(emoji[0] + emoji[1]); - }); - }); - }); - }); - }); - cy.findByRole("button", { name: "They match" }).click(); - cy.get(".mx_VerificationPanel_verified_section .mx_E2EIcon_verified").should("exist"); - cy.findByRole("button", { name: "Got it" }).click(); - - cy.get(".mx_DecryptionFailureBar_start_headline").within(() => { - cy.findByText("Open another device to load encrypted messages").should("be.visible"); - }); - - checkTimelineNarrow(); - - cy.get(".mx_DecryptionFailureBar").percySnapshotElement( - "DecryptionFailureBar prompts user to open another device, with Resend Key Requests button", - { - widths: [320, 640], - }, - ); - - cy.intercept("/_matrix/client/r0/sendToDevice/m.room_key_request/*").as("keyRequest"); - cy.findByRole("button", { name: "Resend key requests" }).click(); - cy.wait("@keyRequest"); - cy.get(".mx_DecryptionFailureBar_end").within(() => { - cy.findByText("Resend key requests").should("not.exist"); - cy.findByRole("button", { name: "View your device list" }).should("be.visible"); - }); - - checkTimelineNarrow(); - - cy.get(".mx_DecryptionFailureBar").percySnapshotElement( - "DecryptionFailureBar prompts user to open another device, without Resend Key Requests button", - { - widths: [320, 640], - }, - ); - }, - ); - - it( - "should prompt the user to reset keys, if this device isn't verified " + - "and there are no other verified devices or backups", - () => { - cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true }).then( - async (cli) => { - await cli.logout(true); - }, - ); - - cy.botSendMessage(bot, roomId, "test"); - cy.get(".mx_DecryptionFailureBar_start_headline").within(() => { - cy.findByText("Reset your keys to prevent future decryption errors").should("be.visible"); - }); - - checkTimelineNarrow(); - - cy.get(".mx_DecryptionFailureBar").percySnapshotElement("DecryptionFailureBar prompts user to reset keys", { - widths: [320, 640], - }); - - cy.findByRole("button", { name: "Reset" }).click(); - - // Set up key backup - cy.get(".mx_Dialog").within(() => { - cy.findByRole("button", { name: "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.findByRole("button", { name: "Download" }).click(); - cy.get(".mx_Dialog_primary:not([disabled])").should("have.length", 3); - cy.findByRole("button", { name: "Continue" }).click(); - cy.findByRole("button", { name: "Done" }).click(); - }); - - cy.get(".mx_DecryptionFailureBar_start_headline").within(() => { - cy.findByText("Some messages could not be decrypted").should("be.visible"); - }); - - checkTimelineNarrow(false); // button should not be rendered here - - cy.get(".mx_DecryptionFailureBar").percySnapshotElement( - "DecryptionFailureBar displays general message with no call to action", - { - widths: [320, 640], - }, - ); - }, - ); - - it("should appear and disappear as undecryptable messages enter and leave view", () => { - cy.getClient().then((cli) => { - for (let i = 0; i < 25; i++) { - cy.botSendMessage(cli, roomId, `test ${i}`); - } - }); - cy.botSendMessage(bot, roomId, "test"); - cy.get(".mx_DecryptionFailureBar").should("exist"); - cy.get(".mx_DecryptionFailureBar .mx_Spinner").should("exist"); - - cy.get(".mx_DecryptionFailureBar").percySnapshotElement("DecryptionFailureBar displays loading spinner", { - allowSpinners: true, - widths: [320, 640], - }); - - checkTimelineNarrow(); - - cy.wait(5000); - cy.get(".mx_DecryptionFailureBar .mx_Spinner").should("not.exist"); - cy.findByTestId("decryption-failure-bar-icon").should("be.visible"); - - cy.get(".mx_RoomView_messagePanel").scrollTo("top"); - cy.get(".mx_DecryptionFailureBar").should("not.exist"); - - cy.botSendMessage(bot, roomId, "another test"); - cy.get(".mx_DecryptionFailureBar").should("not.exist"); - - cy.get(".mx_RoomView_messagePanel").scrollTo("bottom"); - cy.get(".mx_DecryptionFailureBar").should("exist"); - - checkTimelineNarrow(); - }); -}); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 9c7cd645d6b..6e4dd7559f2 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -62,7 +62,6 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; import RoomPreviewCard from "../views/rooms/RoomPreviewCard"; import SearchBar, { SearchScope } from "../views/rooms/SearchBar"; import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; -import { DecryptionFailureBar } from "../views/rooms/DecryptionFailureBar"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader, { ISearchInfo } from "../views/rooms/RoomHeader"; import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore"; @@ -227,8 +226,6 @@ export interface IRoomState { threadId?: string; liveTimeline?: EventTimeline; narrow: boolean; - // List of undecryptable events currently visible on-screen - visibleDecryptionFailures?: MatrixEvent[]; msc3946ProcessDynamicPredecessor: boolean; } @@ -428,7 +425,6 @@ export class RoomView extends React.Component { timelineRenderingType: TimelineRenderingType.Room, liveTimeline: undefined, narrow: false, - visibleDecryptionFailures: [], msc3946ProcessDynamicPredecessor: SettingsStore.getValue("feature_dynamic_room_predecessors"), }; @@ -1244,7 +1240,6 @@ export class RoomView extends React.Component { private onEventDecrypted = (ev: MatrixEvent): void => { if (!this.state.room || !this.state.matrixClientIsReady) return; // not ready at all if (ev.getRoomId() !== this.state.room.roomId) return; // not for us - this.updateVisibleDecryptionFailures(); if (ev.isDecryptionFailure()) return; this.handleEffects(ev); }; @@ -1552,20 +1547,6 @@ export class RoomView extends React.Component { } }; - private updateVisibleDecryptionFailures = throttle( - () => - this.setState((prevState) => ({ - visibleDecryptionFailures: - this.messagePanel?.getVisibleDecryptionFailures( - // If there were visible failures last time we checked, - // add a margin to provide hysteresis and prevent flickering - (prevState.visibleDecryptionFailures?.length ?? 0) > 0, - ) ?? [], - })), - 500, - { leading: false, trailing: true }, - ); - private onMessageListScroll = (): void => { if (this.messagePanel?.isAtEndOfLiveTimeline()) { this.setState({ @@ -1578,7 +1559,6 @@ export class RoomView extends React.Component { }); } this.updateTopUnreadMessagesBar(); - this.updateVisibleDecryptionFailures(); }; private resetJumpToEvent = (eventId?: string): void => { @@ -2203,11 +2183,6 @@ export class RoomView extends React.Component { ); } - let decryptionFailureBar: JSX.Element | undefined; - if (this.state.visibleDecryptionFailures && this.state.visibleDecryptionFailures.length > 0) { - decryptionFailureBar = ; - } - if (this.state.room?.isSpaceRoom() && !this.props.forceTimeline) { return ( { resizeNotifier={this.props.resizeNotifier} > {aux} - {decryptionFailureBar} ); diff --git a/src/components/views/rooms/DecryptionFailureBar.tsx b/src/components/views/rooms/DecryptionFailureBar.tsx deleted file mode 100644 index 74631688676..00000000000 --- a/src/components/views/rooms/DecryptionFailureBar.tsx +++ /dev/null @@ -1,270 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { useCallback, useContext, useEffect, useState } from "react"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { CryptoEvent } from "matrix-js-sdk/src/crypto"; - -import Modal from "../../../Modal"; -import { _t } from "../../../languageHandler"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { Action } from "../../../dispatcher/actions"; -import AccessibleButton from "../elements/AccessibleButton"; -import { OpenToTabPayload } from "../../../dispatcher/payloads/OpenToTabPayload"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import SetupEncryptionDialog from "../dialogs/security/SetupEncryptionDialog"; -import { SetupEncryptionStore } from "../../../stores/SetupEncryptionStore"; -import Spinner from "../elements/Spinner"; - -interface IProps { - failures: MatrixEvent[]; -} - -// Number of milliseconds to display a loading spinner before prompting the user for action -const WAIT_PERIOD = 5000; - -export const DecryptionFailureBar: React.FC = ({ failures }) => { - 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(true); - useEffect(() => { - const timeout = setTimeout(() => setWaiting(false), WAIT_PERIOD); - return () => clearTimeout(timeout); - }, []); - - // Is this device unverified? - const [needsVerification, setNeedsVerification] = useState(false); - // Does this user have verified devices other than this device? - const [hasOtherVerifiedDevices, setHasOtherVerifiedDevices] = useState(false); - // Does this user have key backups? - const [hasKeyBackup, setHasKeyBackup] = useState(false); - - // Keep track of session IDs that the user has sent key - // requests for using the Resend Key Requests button - const [requestedSessions, setRequestedSessions] = useState>(new Set()); - // Keep track of whether there are any sessions the user has not yet sent requests for - const [anyUnrequestedSessions, setAnyUnrequestedSessions] = useState(true); - - useEffect(() => { - setAnyUnrequestedSessions( - failures.some((event) => { - const sessionId = event.getWireContent().session_id; - return sessionId && !requestedSessions.has(sessionId); - }), - ); - }, [failures, requestedSessions, setAnyUnrequestedSessions]); - - // Send key requests for any sessions that we haven't previously - // sent requests for. This is important if, for instance, we - // failed to decrypt a message because a key was withheld (in - // which case, we wouldn't bother requesting the key), but have - // since verified our device. In that case, now that the device is - // verified, other devices might be willing to share the key with us - // now. - const sendKeyRequests = useCallback(() => { - const newRequestedSessions = new Set(requestedSessions); - - for (const event of failures) { - const sessionId = event.getWireContent().session_id; - if (!sessionId || newRequestedSessions.has(sessionId)) continue; - newRequestedSessions.add(sessionId); - context.cancelAndResendEventRoomKeyRequest(event); - } - setRequestedSessions(newRequestedSessions); - }, [context, requestedSessions, setRequestedSessions, failures]); - - // Recheck which devices are verified and whether we have key backups - const updateDeviceInfo = useCallback(async () => { - 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); - } catch (e) { - console.error("Error getting device cross-signing info", e); - } - setNeedsVerification(!verified); - - let otherVerifiedDevices = false; - try { - const devices = context.getStoredDevicesForUser(context.getUserId()!); - otherVerifiedDevices = devices.some( - (device) => device.deviceId !== deviceId && context.checkIfOwnDeviceCrossSigned(device.deviceId), - ); - } catch (e) { - console.error("Error getting info about other devices", e); - } - setHasOtherVerifiedDevices(otherVerifiedDevices); - - let keyBackup = false; - try { - const keys = await context.isSecretStored("m.cross_signing.master"); - keyBackup = keys !== null && Object.keys(keys).length > 0; - } catch (e) { - console.error("Error getting info about key backups", e); - } - setHasKeyBackup(keyBackup); - }, [context]); - - // Update our device info on initial render, and continue updating - // it whenever the client has an update - useEffect(() => { - updateDeviceInfo().catch(console.error); - context.on(CryptoEvent.DevicesUpdated, updateDeviceInfo); - return () => { - context.off(CryptoEvent.DevicesUpdated, updateDeviceInfo); - }; - }, [context, updateDeviceInfo]); - - const onVerifyClick = (): void => { - Modal.createDialog(SetupEncryptionDialog); - }; - - const onDeviceListClick = (): void => { - const payload: OpenToTabPayload = { action: Action.ViewUserDeviceSettings }; - defaultDispatcher.dispatch(payload); - }; - - const onResetClick = (): void => { - const store = SetupEncryptionStore.sharedInstance(); - store.resetConfirm(); - }; - - const statusIndicator = waiting ? ( - - ) : ( -
- ); - - let className; - let headline: JSX.Element; - let message: JSX.Element; - let button = ; - if (waiting) { - className = "mx_DecryptionFailureBar"; - headline = {_t("Decrypting messages…")}; - message = ( - - {_t("Please wait as we try to decrypt your messages. This may take a few moments.")} - - ); - } else if (needsVerification) { - if (hasOtherVerifiedDevices || hasKeyBackup) { - className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"; - headline = {_t("Verify this device to access all messages")}; - message = ( - - {_t("This device was unable to decrypt some messages because it has not been verified yet.")} - - ); - button = ( - - {_t("Verify")} - - ); - } else { - className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"; - headline = {_t("Reset your keys to prevent future decryption errors")}; - message = ( - - {_t( - "You will not be able to access old undecryptable messages, " + - "but resetting your keys will allow you to receive new messages.", - )} - - ); - button = ( - - {_t("Reset")} - - ); - } - } else if (hasOtherVerifiedDevices) { - className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"; - headline = {_t("Open another device to load encrypted messages")}; - message = ( - - {_t( - "This device is requesting decryption keys from your other devices. " + - "Opening one of your other devices may speed this up.", - )} - - ); - button = ( - - {_t("View your device list")} - - ); - } else { - className = "mx_DecryptionFailureBar"; - headline = {_t("Some messages could not be decrypted")}; - message = ( - - {_t( - "Unfortunately, there are no other verified devices to request decryption keys from. " + - "Signing in and verifying other devices may help avoid this situation in the future.", - )} - - ); - } - - let keyRequestButton = ; - if (!needsVerification && hasOtherVerifiedDevices && anyUnrequestedSessions) { - className = "mx_DecryptionFailureBar mx_DecryptionFailureBar--withEnd"; - keyRequestButton = ( - - {_t("Resend key requests")} - - ); - } - - return ( -
-
-
-
{statusIndicator}
-
-
{headline}
-
{message}
-
-
- {button} - {keyRequestButton} -
-
- ); -}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f4e329696e0..413ec7801e1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1883,18 +1883,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Decrypting messages…": "Decrypting messages…", - "Please wait as we try to decrypt your messages. This may take a few moments.": "Please wait as we try to decrypt your messages. This may take a few moments.", - "Verify this device to access all messages": "Verify this device to access all messages", - "This device was unable to decrypt some messages because it has not been verified yet.": "This device was unable to decrypt some messages because it has not been verified yet.", - "Reset your keys to prevent future decryption errors": "Reset your keys to prevent future decryption errors", - "You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.": "You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.", - "Open another device to load encrypted messages": "Open another device to load encrypted messages", - "This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.", - "View your device list": "View your device list", - "Some messages could not be decrypted": "Some messages could not be decrypted", - "Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.", - "Resend key requests": "Resend key requests", "This user has not verified all of their sessions.": "This user has not verified all of their sessions.", "You have not verified this user.": "You have not verified this user.", "You have verified this user. This user has verified all of their sessions.": "You have verified this user. This user has verified all of their sessions.", diff --git a/test/components/views/rooms/DecryptionFailureBar-test.tsx b/test/components/views/rooms/DecryptionFailureBar-test.tsx deleted file mode 100644 index 25b028d7da9..00000000000 --- a/test/components/views/rooms/DecryptionFailureBar-test.tsx +++ /dev/null @@ -1,449 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import { act, fireEvent, render, screen, waitFor, RenderResult } from "@testing-library/react"; -import "@testing-library/jest-dom"; - -import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; -import { DecryptionFailureBar } from "../../../../src/components/views/rooms/DecryptionFailureBar"; -import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../src/dispatcher/actions"; - -type MockDevice = { deviceId: string }; - -const verifiedDevice1: MockDevice = { deviceId: "verified1" }; -const verifiedDevice2: MockDevice = { deviceId: "verified2" }; -const unverifiedDevice1: MockDevice = { deviceId: "unverified1" }; -const unverifiedDevice2: MockDevice = { deviceId: "unverified2" }; - -const mockEvent1 = { - event: { event_id: "mockEvent1" }, - getWireContent: () => ({ session_id: "sessionA" }), -}; - -const mockEvent2 = { - event: { event_id: "mockEvent2" }, - getWireContent: () => ({ session_id: "sessionB" }), -}; - -const mockEvent3 = { - event: { event_id: "mockEvent3" }, - getWireContent: () => ({ session_id: "sessionB" }), -}; - -const userId = "@user:example.com"; - -let ourDevice: MockDevice | undefined; -let allDevices: MockDevice[] | undefined; -let keyBackup = false; -let callback = async () => {}; - -const mockClient = { - getUserId: () => userId, - getDeviceId: () => ourDevice?.deviceId, - getStoredDevicesForUser: () => allDevices, - isSecretStored: jest.fn(() => Promise.resolve(keyBackup ? { key: "yes" } : null)), - checkIfOwnDeviceCrossSigned: (deviceId: string) => deviceId.startsWith("verified"), - downloadKeys: jest.fn(() => {}), - cancelAndResendEventRoomKeyRequest: jest.fn(() => {}), - on: (_: any, cb: () => Promise) => { - callback = cb; - }, - off: () => {}, -}; - -function getBar(wrapper: RenderResult) { - return wrapper.container.querySelector(".mx_DecryptionFailureBar"); -} - -describe("", () => { - beforeEach(() => { - jest.useFakeTimers(); - jest.spyOn(defaultDispatcher, "dispatch").mockRestore(); - }); - - afterEach(() => { - const container = document.body.firstChild; - container && document.body.removeChild(container); - - jest.runOnlyPendingTimers(); - jest.useRealTimers(); - - mockClient.cancelAndResendEventRoomKeyRequest.mockClear(); - }); - - it("Displays a loading spinner", async () => { - ourDevice = unverifiedDevice1; - allDevices = [unverifiedDevice1]; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Prompts the user to verify if they have other devices", async () => { - ourDevice = unverifiedDevice1; - allDevices = [unverifiedDevice1, verifiedDevice1]; - keyBackup = false; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Prompts the user to verify if they have backups", async () => { - ourDevice = unverifiedDevice1; - allDevices = [unverifiedDevice1]; - keyBackup = true; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Prompts the user to reset if they have no other verified devices and no backups", async () => { - ourDevice = unverifiedDevice1; - allDevices = [unverifiedDevice1, unverifiedDevice2]; - keyBackup = false; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Recommends opening other devices if there are other verified devices", async () => { - ourDevice = verifiedDevice1; - allDevices = [verifiedDevice1, verifiedDevice2]; - keyBackup = false; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Displays a general error message if there are no other verified devices", async () => { - ourDevice = verifiedDevice1; - allDevices = [verifiedDevice1, unverifiedDevice1]; - keyBackup = true; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Displays button to resend key requests if we are verified", async () => { - ourDevice = verifiedDevice1; - allDevices = [verifiedDevice1, verifiedDevice2]; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - fireEvent.click(screen.getByText("Resend key requests")); - - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledTimes(2); - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent1); - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent2); - - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - it("Displays button to review device list if we are verified", async () => { - // stub so we dont have to deal with launching modals - jest.spyOn(defaultDispatcher, "dispatch").mockImplementation(() => {}); - ourDevice = verifiedDevice1; - allDevices = [verifiedDevice1, verifiedDevice2]; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - - fireEvent.click(screen.getByText("View your device list")); - - expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ViewUserDeviceSettings }); - - bar.unmount(); - }); - - it("Does not display a button to send key requests if we are unverified", async () => { - ourDevice = unverifiedDevice1; - allDevices = [unverifiedDevice1, verifiedDevice2]; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Displays the button to resend key requests only if there are sessions we haven't already requested", async () => { - ourDevice = verifiedDevice1; - allDevices = [verifiedDevice1, verifiedDevice2]; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - fireEvent.click(screen.getByText("Resend key requests")); - - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledTimes(1); - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent3); - - expect(getBar(bar)).toMatchSnapshot(); - - bar.rerender( - // @ts-ignore - - - , - , - ); - - expect(getBar(bar)).toMatchSnapshot(); - - mockClient.cancelAndResendEventRoomKeyRequest.mockClear(); - - fireEvent.click(screen.getByText("Resend key requests")); - - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledTimes(1); - expect(mockClient.cancelAndResendEventRoomKeyRequest).toHaveBeenCalledWith(mockEvent1); - - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); - - it("Handles device updates", async () => { - ourDevice = unverifiedDevice1; - allDevices = [unverifiedDevice1, verifiedDevice2]; - - const bar = render( - // @ts-ignore - - - , - , - ); - - await waitFor(() => expect(mockClient.isSecretStored).toHaveBeenCalled()); - act(() => { - jest.advanceTimersByTime(5000); - }); - expect(getBar(bar)).toMatchSnapshot(); - - ourDevice = verifiedDevice1; - await act(callback); - expect(getBar(bar)).toMatchSnapshot(); - - bar.unmount(); - }); -}); diff --git a/test/components/views/rooms/__snapshots__/DecryptionFailureBar-test.tsx.snap b/test/components/views/rooms/__snapshots__/DecryptionFailureBar-test.tsx.snap deleted file mode 100644 index 35b95a37f46..00000000000 --- a/test/components/views/rooms/__snapshots__/DecryptionFailureBar-test.tsx.snap +++ /dev/null @@ -1,705 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` Displays a general error message if there are no other verified devices 1`] = ` -
-
-
-
-
-
-
-
- Some messages could not be decrypted -
-
- Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future. -
-
-
-
-`; - -exports[` Displays a loading spinner 1`] = ` -
-
-
-
-
-
-
-
-
-
- Decrypting messages… -
-
- Please wait as we try to decrypt your messages. This may take a few moments. -
-
-
-
-`; - -exports[` Displays button to resend key requests if we are verified 1`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
- Resend key requests -
-
-
-`; - -exports[` Displays button to resend key requests if we are verified 2`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
-
-`; - -exports[` Displays the button to resend key requests only if there are sessions we haven't already requested 1`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
- Resend key requests -
-
-
-`; - -exports[` Displays the button to resend key requests only if there are sessions we haven't already requested 2`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
-
-`; - -exports[` Displays the button to resend key requests only if there are sessions we haven't already requested 3`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
- Resend key requests -
-
-
-`; - -exports[` Displays the button to resend key requests only if there are sessions we haven't already requested 4`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
-
-`; - -exports[` Does not display a button to send key requests if we are unverified 1`] = ` -
-
-
-
-
-
-
-
- Verify this device to access all messages -
-
- This device was unable to decrypt some messages because it has not been verified yet. -
-
-
-
- Verify -
-
-
-`; - -exports[` Handles device updates 1`] = ` -
-
-
-
-
-
-
-
- Verify this device to access all messages -
-
- This device was unable to decrypt some messages because it has not been verified yet. -
-
-
-
- Verify -
-
-
-`; - -exports[` Handles device updates 2`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
- Resend key requests -
-
-
-`; - -exports[` Prompts the user to reset if they have no other verified devices and no backups 1`] = ` -
-
-
-
-
-
-
-
- Reset your keys to prevent future decryption errors -
-
- You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages. -
-
-
-
- Reset -
-
-
-`; - -exports[` Prompts the user to verify if they have backups 1`] = ` -
-
-
-
-
-
-
-
- Verify this device to access all messages -
-
- This device was unable to decrypt some messages because it has not been verified yet. -
-
-
-
- Verify -
-
-
-`; - -exports[` Prompts the user to verify if they have other devices 1`] = ` -
-
-
-
-
-
-
-
- Verify this device to access all messages -
-
- This device was unable to decrypt some messages because it has not been verified yet. -
-
-
-
- Verify -
-
-
-`; - -exports[` Recommends opening other devices if there are other verified devices 1`] = ` -
-
-
-
-
-
-
-
- Open another device to load encrypted messages -
-
- This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up. -
-
-
-
- View your device list -
-
- Resend key requests -
-
-
-`;