From dc53178ea6e160f2a1550a870b804b5e35c2dfae Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 21 Nov 2024 10:31:55 +0100 Subject: [PATCH 1/2] Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `EventTile.tsx` --- src/components/views/rooms/EventTile.tsx | 32 +++++++++++++++++-- test/test-utils/test-utils.ts | 1 + .../views/dialogs/ForwardDialog-test.tsx | 3 +- .../components/views/rooms/EventTile-test.tsx | 28 +++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 22da73bef7f..5cccd2203a9 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -11,6 +11,7 @@ import React, { createRef, forwardRef, JSX, MouseEvent, ReactNode } from "react" import classNames from "classnames"; import { EventStatus, + EventTimeline, EventType, MatrixEvent, MatrixEventEvent, @@ -21,6 +22,7 @@ import { Room, RoomEvent, RoomMember, + RoomStateEvent, Thread, ThreadEvent, } from "matrix-js-sdk/src/matrix"; @@ -262,6 +264,10 @@ interface IState { thread: Thread | null; threadNotification?: NotificationCountType; + /** + * Whether the event is encrypted. + */ + isRoomEncrypted: boolean; } /** @@ -318,6 +324,7 @@ export class UnwrappedEventTile extends React.Component hover: false, thread, + isRoomEncrypted: false, }; // don't do RR animations until we are mounted @@ -386,7 +393,7 @@ export class UnwrappedEventTile extends React.Component return true; } - public componentDidMount(): void { + public async componentDidMount(): Promise { this.unmounted = false; this.suppressReadReceiptAnimation = false; const client = MatrixClientPeg.safeGet(); @@ -413,6 +420,12 @@ export class UnwrappedEventTile extends React.Component room?.on(ThreadEvent.New, this.onNewThread); this.verifyEvent(); + + room?.getLiveTimeline().getState(EventTimeline.FORWARDS)?.on(RoomStateEvent.Events, this.onRoomStateEvents); + + const crypto = client.getCrypto(); + if (!room || !crypto) return; + this.setState({ isRoomEncrypted: await crypto.isEncryptionEnabledInRoom(room.roomId) }); } private updateThread = (thread: Thread): void => { @@ -434,6 +447,10 @@ export class UnwrappedEventTile extends React.Component client.removeListener(RoomEvent.Receipt, this.onRoomReceipt); const room = client.getRoom(this.props.mxEvent.getRoomId()); room?.off(ThreadEvent.New, this.onNewThread); + room + ?.getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.off(RoomStateEvent.Events, this.onRoomStateEvents); } this.isListeningForReceipts = false; this.props.mxEvent.removeListener(MatrixEventEvent.Decrypted, this.onDecrypted); @@ -470,6 +487,17 @@ export class UnwrappedEventTile extends React.Component } }; + private onRoomStateEvents = async (evt: MatrixEvent): Promise => { + const client = MatrixClientPeg.safeGet(); + const crypto = client.getCrypto(); + if (!crypto) return; + + const room = client.getRoom(evt.getRoomId()); + if (room && evt.getType() === EventType.RoomEncryption) { + this.setState({ isRoomEncrypted: await crypto.isEncryptionEnabledInRoom(room.roomId) }); + } + }; + private get thread(): Thread | null { let thread: Thread | undefined = this.props.mxEvent.getThread(); /** @@ -767,7 +795,7 @@ export class UnwrappedEventTile extends React.Component } } - if (MatrixClientPeg.safeGet().isRoomEncrypted(ev.getRoomId()!)) { + if (this.state.isRoomEncrypted) { // else if room is encrypted // and event is being encrypted or is not_sent (Unknown Devices/Network Error) if (ev.status === EventStatus.ENCRYPTING) { diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 78481c2fd06..790f39d018e 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -135,6 +135,7 @@ export function createTestClient(): MatrixClient { restoreKeyBackupWithPassphrase: jest.fn(), loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(), storeSessionBackupPrivateKey: jest.fn(), + getEncryptionInfoForEvent: jest.fn().mockResolvedValue(null), }), getPushActionsForEvent: jest.fn(), diff --git a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx index 6942f76cda0..2912f47f277 100644 --- a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx @@ -32,6 +32,7 @@ import { mkEvent, mkMessage, mkStubRoom, + mockClientMethodsCrypto, mockPlatformPeg, } from "../../../../test-utils"; import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils"; @@ -67,7 +68,6 @@ describe("ForwardDialog", () => { getAccountData: jest.fn().mockReturnValue(accountDataEvent), getPushActionsForEvent: jest.fn(), mxcUrlToHttp: jest.fn().mockReturnValue(""), - isRoomEncrypted: jest.fn().mockReturnValue(false), getProfileInfo: jest.fn().mockResolvedValue({ displayname: "Alice", }), @@ -76,6 +76,7 @@ describe("ForwardDialog", () => { getClientWellKnown: jest.fn().mockReturnValue({ [TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" }, }), + ...mockClientMethodsCrypto(), }); const defaultRooms = ["a", "A", "b"].map((name) => mkStubRoom(name, name, mockClient)); diff --git a/test/unit-tests/components/views/rooms/EventTile-test.tsx b/test/unit-tests/components/views/rooms/EventTile-test.tsx index 4cb22967608..7400b399b58 100644 --- a/test/unit-tests/components/views/rooms/EventTile-test.tsx +++ b/test/unit-tests/components/views/rooms/EventTile-test.tsx @@ -10,6 +10,7 @@ import * as React from "react"; import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react"; import { mocked } from "jest-mock"; import { + EventTimeline, EventType, IEventDecryptionResult, MatrixClient, @@ -17,6 +18,7 @@ import { NotificationCountType, PendingEventOrdering, Room, + RoomStateEvent, TweakName, } from "matrix-js-sdk/src/matrix"; import { @@ -243,6 +245,7 @@ describe("EventTile", () => { const mockCrypto = { // a mocked version of getEncryptionInfoForEvent which will pick its result from `eventToEncryptionInfoMap` getEncryptionInfoForEvent: async (event: MatrixEvent) => eventToEncryptionInfoMap.get(event.getId()!)!, + isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false), } as unknown as CryptoApi; client.getCrypto = () => mockCrypto; }); @@ -434,7 +437,7 @@ describe("EventTile", () => { }); it("should update the warning when the event is replaced with an unencrypted one", async () => { - jest.spyOn(client, "isRoomEncrypted").mockReturnValue(true); + jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); // we start out with an event from the trusted device mxEvent = await mkEncryptedMatrixEvent({ @@ -578,4 +581,27 @@ describe("EventTile", () => { }); }); }); + + it("should display the not encrypted status for an unencrypted event when the room becomes encrypted", async () => { + jest.spyOn(client.getCrypto()!, "getEncryptionInfoForEvent").mockResolvedValue({ + shieldColour: EventShieldColour.NONE, + shieldReason: null, + }); + + getComponent(); + await flushPromises(); + // The room and the event are unencrypted, the tile should not show the not encrypted status + expect(screen.queryByText("Not encrypted")).toBeNull(); + + // The room is now encrypted + jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); + // Emit a state event to trigger a re-render + const roomState = room!.getLiveTimeline().getState(EventTimeline.FORWARDS)!; + act(() => { + roomState.emit(RoomStateEvent.Events, new MatrixEvent({ type: EventType.RoomEncryption }), roomState, null); + }); + + // The event tile should now show the not encrypted status + await waitFor(() => expect(screen.getByText("Not encrypted")).toBeInTheDocument()); + }); }); From 52c6838add5a73aac457984b4c7733155fa63094 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 27 Nov 2024 10:54:15 +0100 Subject: [PATCH 2/2] Use `roomContext.isRoomEncrypted` --- src/components/views/rooms/EventTile.tsx | 32 ++----------------- .../views/dialogs/ForwardDialog-test.tsx | 2 -- .../components/views/rooms/EventTile-test.tsx | 24 +++++++------- 3 files changed, 13 insertions(+), 45 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 5714216c674..95358030655 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -11,7 +11,6 @@ import React, { createRef, forwardRef, JSX, MouseEvent, ReactNode } from "react" import classNames from "classnames"; import { EventStatus, - EventTimeline, EventType, MatrixEvent, MatrixEventEvent, @@ -22,7 +21,6 @@ import { Room, RoomEvent, RoomMember, - RoomStateEvent, Thread, ThreadEvent, } from "matrix-js-sdk/src/matrix"; @@ -264,10 +262,6 @@ interface IState { thread: Thread | null; threadNotification?: NotificationCountType; - /** - * Whether the event is encrypted. - */ - isRoomEncrypted: boolean; } /** @@ -324,7 +318,6 @@ export class UnwrappedEventTile extends React.Component hover: false, thread, - isRoomEncrypted: false, }; // don't do RR animations until we are mounted @@ -393,7 +386,7 @@ export class UnwrappedEventTile extends React.Component return true; } - public async componentDidMount(): Promise { + public componentDidMount(): void { this.unmounted = false; this.suppressReadReceiptAnimation = false; const client = MatrixClientPeg.safeGet(); @@ -420,12 +413,6 @@ export class UnwrappedEventTile extends React.Component room?.on(ThreadEvent.New, this.onNewThread); this.verifyEvent(); - - room?.getLiveTimeline().getState(EventTimeline.FORWARDS)?.on(RoomStateEvent.Events, this.onRoomStateEvents); - - const crypto = client.getCrypto(); - if (!room || !crypto) return; - this.setState({ isRoomEncrypted: await crypto.isEncryptionEnabledInRoom(room.roomId) }); } private updateThread = (thread: Thread): void => { @@ -447,10 +434,6 @@ export class UnwrappedEventTile extends React.Component client.removeListener(RoomEvent.Receipt, this.onRoomReceipt); const room = client.getRoom(this.props.mxEvent.getRoomId()); room?.off(ThreadEvent.New, this.onNewThread); - room - ?.getLiveTimeline() - .getState(EventTimeline.FORWARDS) - ?.off(RoomStateEvent.Events, this.onRoomStateEvents); } this.isListeningForReceipts = false; this.props.mxEvent.removeListener(MatrixEventEvent.Decrypted, this.onDecrypted); @@ -487,17 +470,6 @@ export class UnwrappedEventTile extends React.Component } }; - private onRoomStateEvents = async (evt: MatrixEvent): Promise => { - const client = MatrixClientPeg.safeGet(); - const crypto = client.getCrypto(); - if (!crypto) return; - - const room = client.getRoom(evt.getRoomId()); - if (room && evt.getType() === EventType.RoomEncryption) { - this.setState({ isRoomEncrypted: await crypto.isEncryptionEnabledInRoom(room.roomId) }); - } - }; - private get thread(): Thread | null { let thread: Thread | undefined = this.props.mxEvent.getThread(); /** @@ -803,7 +775,7 @@ export class UnwrappedEventTile extends React.Component } } - if (this.state.isRoomEncrypted) { + if (this.context.isRoomEncrypted) { // else if room is encrypted // and event is being encrypted or is not_sent (Unknown Devices/Network Error) if (ev.status === EventStatus.ENCRYPTING) { diff --git a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx index 2912f47f277..7307417b074 100644 --- a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx @@ -32,7 +32,6 @@ import { mkEvent, mkMessage, mkStubRoom, - mockClientMethodsCrypto, mockPlatformPeg, } from "../../../../test-utils"; import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils"; @@ -76,7 +75,6 @@ describe("ForwardDialog", () => { getClientWellKnown: jest.fn().mockReturnValue({ [TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" }, }), - ...mockClientMethodsCrypto(), }); const defaultRooms = ["a", "A", "b"].map((name) => mkStubRoom(name, name, mockClient)); diff --git a/test/unit-tests/components/views/rooms/EventTile-test.tsx b/test/unit-tests/components/views/rooms/EventTile-test.tsx index 2ba77d07e67..e3964ac16f7 100644 --- a/test/unit-tests/components/views/rooms/EventTile-test.tsx +++ b/test/unit-tests/components/views/rooms/EventTile-test.tsx @@ -10,7 +10,6 @@ import * as React from "react"; import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react"; import { mocked } from "jest-mock"; import { - EventTimeline, EventType, IEventDecryptionResult, MatrixClient, @@ -18,7 +17,6 @@ import { NotificationCountType, PendingEventOrdering, Room, - RoomStateEvent, TweakName, } from "matrix-js-sdk/src/matrix"; import { @@ -72,9 +70,11 @@ describe("EventTile", () => { function getComponent( overrides: Partial = {}, renderingType: TimelineRenderingType = TimelineRenderingType.Room, + roomContext: Partial = {}, ) { const context = getRoomContext(room, { timelineRenderingType: renderingType, + ...roomContext, }); return render(); } @@ -245,7 +245,6 @@ describe("EventTile", () => { const mockCrypto = { // a mocked version of getEncryptionInfoForEvent which will pick its result from `eventToEncryptionInfoMap` getEncryptionInfoForEvent: async (event: MatrixEvent) => eventToEncryptionInfoMap.get(event.getId()!)!, - isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false), } as unknown as CryptoApi; client.getCrypto = () => mockCrypto; }); @@ -439,8 +438,6 @@ describe("EventTile", () => { }); it("should update the warning when the event is replaced with an unencrypted one", async () => { - jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); - // we start out with an event from the trusted device mxEvent = await mkEncryptedMatrixEvent({ plainContent: { msgtype: "m.text", body: "msg1" }, @@ -454,7 +451,7 @@ describe("EventTile", () => { shieldReason: null, } as EventEncryptionInfo); - const roomContext = getRoomContext(room, {}); + const roomContext = getRoomContext(room, { isRoomEncrypted: true }); const { container, rerender } = render(); await flushPromises(); @@ -590,18 +587,19 @@ describe("EventTile", () => { shieldReason: null, }); - getComponent(); + const { rerender } = getComponent(); await flushPromises(); // The room and the event are unencrypted, the tile should not show the not encrypted status expect(screen.queryByText("Not encrypted")).toBeNull(); // The room is now encrypted - jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true); - // Emit a state event to trigger a re-render - const roomState = room!.getLiveTimeline().getState(EventTimeline.FORWARDS)!; - act(() => { - roomState.emit(RoomStateEvent.Events, new MatrixEvent({ type: EventType.RoomEncryption }), roomState, null); - }); + rerender( + , + ); // The event tile should now show the not encrypted status await waitFor(() => expect(screen.getByText("Not encrypted")).toBeInTheDocument());