Skip to content

Commit af846f8

Browse files
authored
Replace MatrixClient.isRoomEncrypted by MatrixClient.CryptoApi.isEncryptionEnabledInRoom in RoomView (#28278)
* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in RoomView * Add `isRoomEncrypted` to room * Update e2eStatus and urlPreview when isRoomEncrypted is computed * Fix e2e test * Add tests when user verification change * Reduced abusive timeout in e2e test
1 parent de5ddcf commit af846f8

File tree

8 files changed

+775
-106
lines changed

8 files changed

+775
-106
lines changed

Diff for: playwright/e2e/crypto/decryption-failure-messages.spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ test.describe("Cryptography", function () {
6767
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
6868
await app.viewRoomByName("Test room");
6969

70+
// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
71+
await page.waitForTimeout(1000);
72+
7073
// There should be two historical events in the timeline
7174
const tiles = await page.locator(".mx_EventTile").all();
7275
expect(tiles.length).toBeGreaterThanOrEqual(2);

Diff for: src/components/structures/RoomView.tsx

+109-54
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
99
Please see LICENSE files in the repository root for full details.
1010
*/
1111

12-
import React, { ChangeEvent, ComponentProps, createRef, ReactElement, ReactNode, RefObject, useContext } from "react";
12+
import React, {
13+
ChangeEvent,
14+
ComponentProps,
15+
createRef,
16+
ReactElement,
17+
ReactNode,
18+
RefObject,
19+
useContext,
20+
JSX,
21+
} from "react";
1322
import classNames from "classnames";
1423
import {
1524
IRecommendedVersion,
@@ -29,6 +38,7 @@ import {
2938
MatrixError,
3039
ISearchResults,
3140
THREAD_RELATION_TYPE,
41+
MatrixClient,
3242
} from "matrix-js-sdk/src/matrix";
3343
import { KnownMembership } from "matrix-js-sdk/src/types";
3444
import { logger } from "matrix-js-sdk/src/logger";
@@ -233,6 +243,11 @@ export interface IRoomState {
233243
liveTimeline?: EventTimeline;
234244
narrow: boolean;
235245
msc3946ProcessDynamicPredecessor: boolean;
246+
/**
247+
* Whether the room is encrypted or not.
248+
* If null, we are still determining the encryption status.
249+
*/
250+
isRoomEncrypted: boolean | null;
236251

237252
canAskToJoin: boolean;
238253
promptAskToJoin: boolean;
@@ -417,6 +432,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
417432
canAskToJoin: this.askToJoinEnabled,
418433
promptAskToJoin: false,
419434
viewRoomOpts: { buttons: [] },
435+
isRoomEncrypted: null,
420436
};
421437
}
422438

@@ -847,7 +863,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
847863
return isManuallyShown && widgets.length > 0;
848864
}
849865

850-
public componentDidMount(): void {
866+
public async componentDidMount(): Promise<void> {
851867
this.unmounted = false;
852868

853869
this.dispatcherRef = defaultDispatcher.register(this.onAction);
@@ -1342,13 +1358,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
13421358
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
13431359

13441360
this.calculatePeekRules(room);
1345-
this.updatePreviewUrlVisibility(room);
13461361
this.loadMembersIfJoined(room);
13471362
this.calculateRecommendedVersion(room);
1348-
this.updateE2EStatus(room);
13491363
this.updatePermissions(room);
13501364
this.checkWidgets(room);
13511365
this.loadVirtualRoom(room);
1366+
this.updateRoomEncrypted(room);
13521367

13531368
if (
13541369
this.getMainSplitContentType(room) !== MainSplitContentType.Timeline &&
@@ -1377,6 +1392,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
13771392
return room?.currentState.getStateEvents(EventType.RoomTombstone, "") ?? undefined;
13781393
}
13791394

1395+
private async getIsRoomEncrypted(roomId = this.state.roomId): Promise<boolean> {
1396+
const crypto = this.context.client?.getCrypto();
1397+
if (!crypto || !roomId) return false;
1398+
1399+
return await crypto.isEncryptionEnabledInRoom(roomId);
1400+
}
1401+
13801402
private async calculateRecommendedVersion(room: Room): Promise<void> {
13811403
const upgradeRecommendation = await room.getRecommendedVersion();
13821404
if (this.unmounted) return;
@@ -1409,12 +1431,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
14091431
});
14101432
}
14111433

1412-
private updatePreviewUrlVisibility({ roomId }: Room): void {
1413-
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
1414-
const key = this.context.client?.isRoomEncrypted(roomId) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
1415-
this.setState({
1416-
showUrlPreview: SettingsStore.getValue(key, roomId),
1417-
});
1434+
private updatePreviewUrlVisibility(room: Room): void {
1435+
this.setState(({ isRoomEncrypted }) => ({
1436+
showUrlPreview: this.getPreviewUrlVisibility(room, isRoomEncrypted),
1437+
}));
1438+
}
1439+
1440+
private getPreviewUrlVisibility({ roomId }: Room, isRoomEncrypted: boolean | null): boolean {
1441+
const key = isRoomEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
1442+
return SettingsStore.getValue(key, roomId);
14181443
}
14191444

14201445
private onRoom = (room: Room): void => {
@@ -1456,7 +1481,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
14561481
};
14571482

14581483
private async updateE2EStatus(room: Room): Promise<void> {
1459-
if (!this.context.client?.isRoomEncrypted(room.roomId)) return;
1484+
if (!this.context.client || !this.state.isRoomEncrypted) return;
14601485

14611486
// If crypto is not currently enabled, we aren't tracking devices at all,
14621487
// so we don't know what the answer is. Let's error on the safe side and show
@@ -1467,33 +1492,54 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
14671492

14681493
if (this.context.client.getCrypto()) {
14691494
/* At this point, the user has encryption on and cross-signing on */
1470-
e2eStatus = await shieldStatusForRoom(this.context.client, room);
1471-
RoomView.e2eStatusCache.set(room.roomId, e2eStatus);
1495+
e2eStatus = await this.cacheAndGetE2EStatus(room, this.context.client);
14721496
if (this.unmounted) return;
14731497
this.setState({ e2eStatus });
14741498
}
14751499
}
14761500

1501+
private async cacheAndGetE2EStatus(room: Room, client: MatrixClient): Promise<E2EStatus> {
1502+
const e2eStatus = await shieldStatusForRoom(client, room);
1503+
RoomView.e2eStatusCache.set(room.roomId, e2eStatus);
1504+
return e2eStatus;
1505+
}
1506+
14771507
private onUrlPreviewsEnabledChange = (): void => {
14781508
if (this.state.room) {
14791509
this.updatePreviewUrlVisibility(this.state.room);
14801510
}
14811511
};
14821512

1483-
private onRoomStateEvents = (ev: MatrixEvent, state: RoomState): void => {
1513+
private onRoomStateEvents = async (ev: MatrixEvent, state: RoomState): Promise<void> => {
14841514
// ignore if we don't have a room yet
1485-
if (!this.state.room || this.state.room.roomId !== state.roomId) return;
1515+
if (!this.state.room || this.state.room.roomId !== state.roomId || !this.context.client) return;
14861516

14871517
switch (ev.getType()) {
14881518
case EventType.RoomTombstone:
14891519
this.setState({ tombstone: this.getRoomTombstone() });
14901520
break;
1491-
1521+
case EventType.RoomEncryption: {
1522+
await this.updateRoomEncrypted();
1523+
break;
1524+
}
14921525
default:
14931526
this.updatePermissions(this.state.room);
14941527
}
14951528
};
14961529

1530+
private async updateRoomEncrypted(room = this.state.room): Promise<void> {
1531+
if (!room || !this.context.client) return;
1532+
1533+
const isRoomEncrypted = await this.getIsRoomEncrypted(room.roomId);
1534+
const newE2EStatus = isRoomEncrypted ? await this.cacheAndGetE2EStatus(room, this.context.client) : null;
1535+
1536+
this.setState({
1537+
isRoomEncrypted,
1538+
showUrlPreview: this.getPreviewUrlVisibility(room, isRoomEncrypted),
1539+
...(newE2EStatus && { e2eStatus: newE2EStatus }),
1540+
});
1541+
}
1542+
14971543
private onRoomStateUpdate = (state: RoomState): void => {
14981544
// ignore members in other rooms
14991545
if (state.roomId !== this.state.room?.roomId) {
@@ -2027,6 +2073,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
20272073

20282074
public render(): ReactNode {
20292075
if (!this.context.client) return null;
2076+
const { isRoomEncrypted } = this.state;
2077+
const isRoomEncryptionLoading = isRoomEncrypted === null;
20302078

20312079
if (this.state.room instanceof LocalRoom) {
20322080
if (this.state.room.state === LocalRoomState.CREATING) {
@@ -2242,14 +2290,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
22422290
let aux: JSX.Element | undefined;
22432291
let previewBar;
22442292
if (this.state.timelineRenderingType === TimelineRenderingType.Search) {
2245-
aux = (
2246-
<RoomSearchAuxPanel
2247-
searchInfo={this.state.search}
2248-
onCancelClick={this.onCancelSearchClick}
2249-
onSearchScopeChange={this.onSearchScopeChange}
2250-
isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)}
2251-
/>
2252-
);
2293+
if (!isRoomEncryptionLoading) {
2294+
aux = (
2295+
<RoomSearchAuxPanel
2296+
searchInfo={this.state.search}
2297+
onCancelClick={this.onCancelSearchClick}
2298+
onSearchScopeChange={this.onSearchScopeChange}
2299+
isRoomEncrypted={isRoomEncrypted}
2300+
/>
2301+
);
2302+
}
22532303
} else if (showRoomUpgradeBar) {
22542304
aux = <RoomUpgradeWarningBar room={this.state.room} />;
22552305
} else if (myMembership !== KnownMembership.Join) {
@@ -2325,8 +2375,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
23252375

23262376
let messageComposer;
23272377
const showComposer =
2378+
!isRoomEncryptionLoading &&
23282379
// joined and not showing search results
2329-
myMembership === KnownMembership.Join && !this.state.search;
2380+
myMembership === KnownMembership.Join &&
2381+
!this.state.search;
23302382
if (showComposer) {
23312383
messageComposer = (
23322384
<MessageComposer
@@ -2367,34 +2419,37 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
23672419
highlightedEventId = this.state.initialEventId;
23682420
}
23692421

2370-
const messagePanel = (
2371-
<TimelinePanel
2372-
ref={this.gatherTimelinePanelRef}
2373-
timelineSet={this.state.room.getUnfilteredTimelineSet()}
2374-
overlayTimelineSet={this.state.virtualRoom?.getUnfilteredTimelineSet()}
2375-
overlayTimelineSetFilter={isCallEvent}
2376-
showReadReceipts={this.state.showReadReceipts}
2377-
manageReadReceipts={!this.state.isPeeking}
2378-
sendReadReceiptOnLoad={!this.state.wasContextSwitch}
2379-
manageReadMarkers={!this.state.isPeeking}
2380-
hidden={hideMessagePanel}
2381-
highlightedEventId={highlightedEventId}
2382-
eventId={this.state.initialEventId}
2383-
eventScrollIntoView={this.state.initialEventScrollIntoView}
2384-
eventPixelOffset={this.state.initialEventPixelOffset}
2385-
onScroll={this.onMessageListScroll}
2386-
onEventScrolledIntoView={this.resetJumpToEvent}
2387-
onReadMarkerUpdated={this.updateTopUnreadMessagesBar}
2388-
showUrlPreview={this.state.showUrlPreview}
2389-
className={this.messagePanelClassNames}
2390-
membersLoaded={this.state.membersLoaded}
2391-
permalinkCreator={this.permalinkCreator}
2392-
resizeNotifier={this.props.resizeNotifier}
2393-
showReactions={true}
2394-
layout={this.state.layout}
2395-
editState={this.state.editState}
2396-
/>
2397-
);
2422+
let messagePanel: JSX.Element | undefined;
2423+
if (!isRoomEncryptionLoading) {
2424+
messagePanel = (
2425+
<TimelinePanel
2426+
ref={this.gatherTimelinePanelRef}
2427+
timelineSet={this.state.room.getUnfilteredTimelineSet()}
2428+
overlayTimelineSet={this.state.virtualRoom?.getUnfilteredTimelineSet()}
2429+
overlayTimelineSetFilter={isCallEvent}
2430+
showReadReceipts={this.state.showReadReceipts}
2431+
manageReadReceipts={!this.state.isPeeking}
2432+
sendReadReceiptOnLoad={!this.state.wasContextSwitch}
2433+
manageReadMarkers={!this.state.isPeeking}
2434+
hidden={hideMessagePanel}
2435+
highlightedEventId={highlightedEventId}
2436+
eventId={this.state.initialEventId}
2437+
eventScrollIntoView={this.state.initialEventScrollIntoView}
2438+
eventPixelOffset={this.state.initialEventPixelOffset}
2439+
onScroll={this.onMessageListScroll}
2440+
onEventScrolledIntoView={this.resetJumpToEvent}
2441+
onReadMarkerUpdated={this.updateTopUnreadMessagesBar}
2442+
showUrlPreview={this.state.showUrlPreview}
2443+
className={this.messagePanelClassNames}
2444+
membersLoaded={this.state.membersLoaded}
2445+
permalinkCreator={this.permalinkCreator}
2446+
resizeNotifier={this.props.resizeNotifier}
2447+
showReactions={true}
2448+
layout={this.state.layout}
2449+
editState={this.state.editState}
2450+
/>
2451+
);
2452+
}
23982453

23992454
let topUnreadMessagesBar: JSX.Element | undefined;
24002455
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
@@ -2415,7 +2470,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
24152470
);
24162471
}
24172472

2418-
const showRightPanel = this.state.room && this.state.showRightPanel;
2473+
const showRightPanel = !isRoomEncryptionLoading && this.state.room && this.state.showRightPanel;
24192474

24202475
const rightPanel = showRightPanel ? (
24212476
<RightPanel

Diff for: src/contexts/RoomContext.ts

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const RoomContext = createContext<
7575
canAskToJoin: false,
7676
promptAskToJoin: false,
7777
viewRoomOpts: { buttons: [] },
78+
isRoomEncrypted: null,
7879
});
7980
RoomContext.displayName = "RoomContext";
8081
export default RoomContext;

Diff for: test/test-utils/room.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoom
8585
canAskToJoin: false,
8686
promptAskToJoin: false,
8787
viewRoomOpts: { buttons: [] },
88-
88+
isRoomEncrypted: false,
8989
...override,
9090
};
9191
}

Diff for: test/test-utils/test-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function createTestClient(): MatrixClient {
116116

117117
getCrypto: jest.fn().mockReturnValue({
118118
getOwnDeviceKeys: jest.fn(),
119-
getUserDeviceInfo: jest.fn(),
119+
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
120120
getUserVerificationStatus: jest.fn(),
121121
getDeviceVerificationStatus: jest.fn(),
122122
resetKeyBackup: jest.fn(),

0 commit comments

Comments
 (0)