Replace MatrixClient.isRoomEncrypted by MatrixClient.CryptoApi.isEncryptionEnabledInRoom in RoomView

This commit is contained in:
Florian Duros 2024-10-23 15:24:38 +02:00
parent c2ce7dbc5e
commit 4be697d4c2
No known key found for this signature in database
GPG Key ID: A5BBB4041B493F15
4 changed files with 109 additions and 67 deletions

View File

@ -9,7 +9,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject, useContext } from "react"; import React, {
ChangeEvent,
ComponentProps,
createRef,
ReactElement,
ReactNode,
RefObject,
useContext,
JSX,
} from "react";
import classNames from "classnames"; import classNames from "classnames";
import { import {
IRecommendedVersion, IRecommendedVersion,
@ -233,6 +242,11 @@ export interface IRoomState {
liveTimeline?: EventTimeline; liveTimeline?: EventTimeline;
narrow: boolean; narrow: boolean;
msc3946ProcessDynamicPredecessor: boolean; msc3946ProcessDynamicPredecessor: boolean;
/**
* Whether the room is encrypted or not.
* If null, we are still determining the encryption status.
*/
isRoomEncrypted: boolean | null;
canAskToJoin: boolean; canAskToJoin: boolean;
promptAskToJoin: boolean; promptAskToJoin: boolean;
@ -417,6 +431,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
canAskToJoin: this.askToJoinEnabled, canAskToJoin: this.askToJoinEnabled,
promptAskToJoin: false, promptAskToJoin: false,
viewRoomOpts: { buttons: [] }, viewRoomOpts: { buttons: [] },
isRoomEncrypted: false,
}; };
} }
@ -847,7 +862,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return isManuallyShown && widgets.length > 0; return isManuallyShown && widgets.length > 0;
} }
public componentDidMount(): void { public async componentDidMount(): Promise<void> {
this.unmounted = false; this.unmounted = false;
this.dispatcherRef = defaultDispatcher.register(this.onAction); this.dispatcherRef = defaultDispatcher.register(this.onAction);
@ -914,6 +929,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const callState = call?.state; const callState = call?.state;
this.setState({ this.setState({
callState, callState,
isRoomEncrypted: await this.getIsRoomEncrypted(),
}); });
this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState); this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
@ -1377,6 +1393,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
return room?.currentState.getStateEvents(EventType.RoomTombstone, "") ?? undefined; return room?.currentState.getStateEvents(EventType.RoomTombstone, "") ?? undefined;
} }
private async getIsRoomEncrypted(roomId = this.state.roomId): Promise<boolean> {
const crypto = this.context.client?.getCrypto();
if (!crypto || !roomId) return false;
return await crypto.isEncryptionEnabledInRoom(roomId);
}
private async calculateRecommendedVersion(room: Room): Promise<void> { private async calculateRecommendedVersion(room: Room): Promise<void> {
const upgradeRecommendation = await room.getRecommendedVersion(); const upgradeRecommendation = await room.getRecommendedVersion();
if (this.unmounted) return; if (this.unmounted) return;
@ -1411,7 +1434,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePreviewUrlVisibility({ roomId }: Room): void { private updatePreviewUrlVisibility({ roomId }: Room): void {
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
const key = this.context.client?.isRoomEncrypted(roomId) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled"; const key = this.state.isRoomEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
this.setState({ this.setState({
showUrlPreview: SettingsStore.getValue(key, roomId), showUrlPreview: SettingsStore.getValue(key, roomId),
}); });
@ -1456,7 +1479,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private async updateE2EStatus(room: Room): Promise<void> { private async updateE2EStatus(room: Room): Promise<void> {
if (!this.context.client?.isRoomEncrypted(room.roomId)) return; if (!this.context.client || !this.state.isRoomEncrypted) return;
// If crypto is not currently enabled, we aren't tracking devices at all, // If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show // so we don't know what the answer is. Let's error on the safe side and show
@ -1480,7 +1503,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
}; };
private onRoomStateEvents = (ev: MatrixEvent, state: RoomState): void => { private onRoomStateEvents = async (ev: MatrixEvent, state: RoomState): Promise<void> => {
// ignore if we don't have a room yet // ignore if we don't have a room yet
if (!this.state.room || this.state.room.roomId !== state.roomId) return; if (!this.state.room || this.state.room.roomId !== state.roomId) return;
@ -1488,7 +1511,14 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
case EventType.RoomTombstone: case EventType.RoomTombstone:
this.setState({ tombstone: this.getRoomTombstone() }); this.setState({ tombstone: this.getRoomTombstone() });
break; break;
case EventType.RoomEncryption:
this.setState({ isRoomEncrypted: await this.getIsRoomEncrypted() }, () => {
if (this.state.room) {
this.updatePreviewUrlVisibility(this.state.room);
this.updateE2EStatus(this.state.room);
}
});
break;
default: default:
this.updatePermissions(this.state.room); this.updatePermissions(this.state.room);
} }
@ -2027,6 +2057,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
public render(): ReactNode { public render(): ReactNode {
if (!this.context.client) return null; if (!this.context.client) return null;
const { isRoomEncrypted } = this.state;
const isRoomEncryptionLoading = isRoomEncrypted === null;
if (this.state.room instanceof LocalRoom) { if (this.state.room instanceof LocalRoom) {
if (this.state.room.state === LocalRoomState.CREATING) { if (this.state.room.state === LocalRoomState.CREATING) {
@ -2242,14 +2274,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
let aux: JSX.Element | undefined; let aux: JSX.Element | undefined;
let previewBar; let previewBar;
if (this.state.timelineRenderingType === TimelineRenderingType.Search) { if (this.state.timelineRenderingType === TimelineRenderingType.Search) {
if (!isRoomEncryptionLoading) {
aux = ( aux = (
<RoomSearchAuxPanel <RoomSearchAuxPanel
searchInfo={this.state.search} searchInfo={this.state.search}
onCancelClick={this.onCancelSearchClick} onCancelClick={this.onCancelSearchClick}
onSearchScopeChange={this.onSearchScopeChange} onSearchScopeChange={this.onSearchScopeChange}
isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)} isRoomEncrypted={isRoomEncrypted}
/> />
); );
}
} else if (showRoomUpgradeBar) { } else if (showRoomUpgradeBar) {
aux = <RoomUpgradeWarningBar room={this.state.room} />; aux = <RoomUpgradeWarningBar room={this.state.room} />;
} else if (myMembership !== KnownMembership.Join) { } else if (myMembership !== KnownMembership.Join) {
@ -2325,8 +2359,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
let messageComposer; let messageComposer;
const showComposer = const showComposer =
!isRoomEncryptionLoading &&
// joined and not showing search results // joined and not showing search results
myMembership === KnownMembership.Join && !this.state.search; myMembership === KnownMembership.Join &&
!this.state.search;
if (showComposer) { if (showComposer) {
messageComposer = ( messageComposer = (
<MessageComposer <MessageComposer
@ -2367,7 +2403,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
highlightedEventId = this.state.initialEventId; highlightedEventId = this.state.initialEventId;
} }
const messagePanel = ( let messagePanel: JSX.Element | undefined;
if (!isRoomEncryptionLoading) {
messagePanel = (
<TimelinePanel <TimelinePanel
ref={this.gatherTimelinePanelRef} ref={this.gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()} timelineSet={this.state.room.getUnfilteredTimelineSet()}
@ -2395,6 +2433,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
editState={this.state.editState} editState={this.state.editState}
/> />
); );
}
let topUnreadMessagesBar: JSX.Element | undefined; let topUnreadMessagesBar: JSX.Element | undefined;
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense // Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
@ -2415,7 +2454,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
); );
} }
const showRightPanel = this.state.room && this.state.showRightPanel; const showRightPanel = !isRoomEncryptionLoading && this.state.room && this.state.showRightPanel;
const rightPanel = showRightPanel ? ( const rightPanel = showRightPanel ? (
<RightPanel <RightPanel

View File

@ -75,6 +75,7 @@ const RoomContext = createContext<
canAskToJoin: false, canAskToJoin: false,
promptAskToJoin: false, promptAskToJoin: false,
viewRoomOpts: { buttons: [] }, viewRoomOpts: { buttons: [] },
isRoomEncrypted: null,
}); });
RoomContext.displayName = "RoomContext"; RoomContext.displayName = "RoomContext";
export default RoomContext; export default RoomContext;

View File

@ -117,7 +117,7 @@ export function createTestClient(): MatrixClient {
getCrypto: jest.fn().mockReturnValue({ getCrypto: jest.fn().mockReturnValue({
getOwnDeviceKeys: jest.fn(), getOwnDeviceKeys: jest.fn(),
getUserDeviceInfo: jest.fn(), getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
getUserVerificationStatus: jest.fn(), getUserVerificationStatus: jest.fn(),
getDeviceVerificationStatus: jest.fn(), getDeviceVerificationStatus: jest.fn(),
resetKeyBackup: jest.fn(), resetKeyBackup: jest.fn(),

View File

@ -10,16 +10,17 @@ import React, { createRef, RefObject } from "react";
import { mocked, MockedObject } from "jest-mock"; import { mocked, MockedObject } from "jest-mock";
import { import {
ClientEvent, ClientEvent,
EventTimeline,
EventType,
IEvent,
JoinRule,
MatrixClient, MatrixClient,
MatrixError,
MatrixEvent,
Room, Room,
RoomEvent, RoomEvent,
EventType,
JoinRule,
MatrixError,
RoomStateEvent, RoomStateEvent,
MatrixEvent,
SearchResult, SearchResult,
IEvent,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { CryptoApi, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api"; import { CryptoApi, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
@ -87,8 +88,7 @@ describe("RoomView", () => {
beforeEach(() => { beforeEach(() => {
mockPlatformPeg({ reload: () => {} }); mockPlatformPeg({ reload: () => {} });
stubClient(); cli = mocked(stubClient());
cli = mocked(MatrixClientPeg.safeGet());
room = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org"); room = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
jest.spyOn(room, "findPredecessor"); jest.spyOn(room, "findPredecessor");
@ -247,8 +247,9 @@ describe("RoomView", () => {
it("updates url preview visibility on encryption state change", async () => { it("updates url preview visibility on encryption state change", async () => {
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join); room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
// we should be starting unencrypted // we should be starting unencrypted
expect(cli.isRoomEncrypted(room.roomId)).toEqual(false); expect(await cli.getCrypto()?.isEncryptionEnabledInRoom(room.roomId)).toEqual(false);
const roomViewInstance = await getRoomViewInstance(); const roomViewInstance = await getRoomViewInstance();
@ -263,23 +264,23 @@ describe("RoomView", () => {
expect(roomViewInstance.state.showUrlPreview).toBe(true); expect(roomViewInstance.state.showUrlPreview).toBe(true);
// now enable encryption // now enable encryption
cli.isRoomEncrypted.mockReturnValue(true); jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
// and fake an encryption event into the room to prompt it to re-check // and fake an encryption event into the room to prompt it to re-check
await act(() => act(() => {
room.addLiveEvents([ const encryptionEvent = new MatrixEvent({
new MatrixEvent({ type: EventType.RoomEncryption,
type: "m.room.encryption",
sender: cli.getUserId()!, sender: cli.getUserId()!,
content: {}, content: {},
event_id: "someid", event_id: "someid",
room_id: room.roomId, room_id: room.roomId,
}), });
]), const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
); cli.emit(RoomStateEvent.Events, encryptionEvent, roomState, null);
});
// URL previews should now be disabled // URL previews should now be disabled
expect(roomViewInstance.state.showUrlPreview).toBe(false); await waitFor(() => expect(roomViewInstance.state.showUrlPreview).toBe(false));
}); });
it("updates live timeline when a timeline reset happens", async () => { it("updates live timeline when a timeline reset happens", async () => {
@ -427,7 +428,8 @@ describe("RoomView", () => {
]); ]);
jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(cli.getSafeUserId()); jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(cli.getSafeUserId());
jest.spyOn(DMRoomMap.shared(), "getRoomIds").mockReturnValue(new Set([room.roomId])); jest.spyOn(DMRoomMap.shared(), "getRoomIds").mockReturnValue(new Set([room.roomId]));
mocked(cli).isRoomEncrypted.mockReturnValue(true); jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
await renderRoomView(); await renderRoomView();
}); });