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

Commit 46037d2

Browse files
Germainandybalaam
andauthored
Add E2E status in room header (#11493)
* Add E2E status in room header * Clearer logic for dmRoomList Co-authored-by: Andy Balaam <[email protected]> * Add test for E2E shield * Remove dead code --------- Co-authored-by: Andy Balaam <[email protected]>
1 parent 6b3243b commit 46037d2

File tree

6 files changed

+127
-17
lines changed

6 files changed

+127
-17
lines changed

res/css/_common.pcss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ code {
134134
color: $muted-fg-color;
135135
}
136136

137+
.mx_Verified {
138+
color: $e2e-verified-color;
139+
}
140+
141+
.mx_Untrusted {
142+
color: $e2e-warning-color;
143+
}
144+
137145
b {
138146
/* On Firefox, the default weight for `<b>` is `bolder` which results in no bold */
139147
/* effect since we only have specific weights of our fonts available. */

src/components/views/rooms/RoomHeader.tsx

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useCallback, useMemo } from "react";
18-
import { Body as BodyText, IconButton } from "@vector-im/compound-web";
17+
import React, { useCallback, useEffect, useMemo, useState } from "react";
18+
import { Body as BodyText, IconButton, Tooltip } from "@vector-im/compound-web";
1919
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call.svg";
2020
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
2121
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
2222
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
23+
import { Icon as VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
24+
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
2325
import { CallType } from "matrix-js-sdk/src/webrtc/call";
24-
import { EventType } from "matrix-js-sdk/src/matrix";
26+
import { EventType, type Room } from "matrix-js-sdk/src/matrix";
2527

26-
import type { Room } from "matrix-js-sdk/src/matrix";
2728
import { useRoomName } from "../../../hooks/useRoomName";
2829
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
2930
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
@@ -45,6 +46,8 @@ import { NotificationColor } from "../../../stores/notifications/NotificationCol
4546
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
4647
import SdkConfig from "../../../SdkConfig";
4748
import { useFeatureEnabled } from "../../../hooks/useSettings";
49+
import { useEncryptionStatus } from "../../../hooks/useEncryptionStatus";
50+
import { E2EStatus } from "../../../utils/ShieldUtils";
4851
import FacePile from "../elements/FacePile";
4952

5053
/**
@@ -80,16 +83,6 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
8083
const members = useRoomMembers(room);
8184
const memberCount = useRoomMemberCount(room);
8285

83-
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
84-
const isDirectMessage = useMemo(() => {
85-
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
86-
if (dmRoomList.includes(room?.roomId ?? "")) {
87-
return true;
88-
}
89-
}
90-
return false;
91-
}, [directRoomsList, room?.roomId]);
92-
9386
const { voiceCallDisabledReason, voiceCallType, videoCallDisabledReason, videoCallType } = useRoomCallStatus(room);
9487

9588
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
@@ -132,6 +125,18 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
132125
const threadNotifications = useRoomThreadNotifications(room);
133126
const globalNotificationState = useGlobalNotificationState();
134127

128+
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
129+
const [isDirectMessage, setDirectMessage] = useState(false);
130+
useEffect(() => {
131+
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
132+
if (dmRoomList.includes(room?.roomId ?? "")) {
133+
setDirectMessage(true);
134+
break;
135+
}
136+
}
137+
}, [room, directRoomsList]);
138+
const e2eStatus = useEncryptionStatus(client, room);
139+
135140
return (
136141
<Flex
137142
as="header"
@@ -154,6 +159,28 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
154159
aria-level={1}
155160
>
156161
{roomName}
162+
163+
{isDirectMessage && e2eStatus === E2EStatus.Verified && (
164+
<Tooltip label={_t("Verified")}>
165+
<VerifiedIcon
166+
width="16px"
167+
height="16px"
168+
className="mx_Verified"
169+
aria-label={_t("Verified")}
170+
/>
171+
</Tooltip>
172+
)}
173+
174+
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
175+
<Tooltip label={_t("Untrusted")}>
176+
<ErrorIcon
177+
width="16px"
178+
height="16px"
179+
className="mx_Untrusted"
180+
aria-label={_t("Untrusted")}
181+
/>
182+
</Tooltip>
183+
)}
157184
</BodyText>
158185
{roomTopic && (
159186
<BodyText as="div" size="sm" className="mx_RoomHeader_topic">

src/hooks/useAccountData.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export const useAccountData = <T extends {}>(cli: MatrixClient, eventType: strin
3737
return value || ({} as T);
3838
};
3939

40-
// Hook to simplify listening to Matrix room account data
4140
// Currently not used, commenting out otherwise the dead code CI is unhappy.
4241
// But this code is valid and probably will be needed.
4342

src/hooks/useEncryptionStatus.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
18+
import { useEffect, useState } from "react";
19+
20+
import { E2EStatus, shieldStatusForRoom } from "../utils/ShieldUtils";
21+
22+
export function useEncryptionStatus(client: MatrixClient, room: Room): E2EStatus | null {
23+
const [e2eStatus, setE2eStatus] = useState<E2EStatus | null>(null);
24+
25+
useEffect(() => {
26+
if (client.isCryptoEnabled()) {
27+
shieldStatusForRoom(client, room).then((e2eStatus) => {
28+
setE2eStatus(e2eStatus);
29+
});
30+
}
31+
}, [client, room]);
32+
33+
return e2eStatus;
34+
}

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,7 @@
18511851
"Room %(name)s": "Room %(name)s",
18521852
"Recently visited rooms": "Recently visited rooms",
18531853
"No recently visited rooms": "No recently visited rooms",
1854+
"Untrusted": "Untrusted",
18541855
"%(count)s members": {
18551856
"other": "%(count)s members",
18561857
"one": "%(count)s member"

test/components/views/rooms/RoomHeader-test.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ limitations under the License.
1717
import React from "react";
1818
import userEvent from "@testing-library/user-event";
1919
import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
20-
import { EventType, MatrixEvent, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
21-
import { getAllByTitle, getByLabelText, getByText, getByTitle, render, screen } from "@testing-library/react";
20+
import { EventType, MatrixClient, MatrixEvent, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
21+
import { getAllByTitle, getByLabelText, getByText, getByTitle, render, screen, waitFor } from "@testing-library/react";
2222

2323
import { mkEvent, stubClient, withClientContextRenderOptions } from "../../../test-utils";
2424
import RoomHeader from "../../../../src/components/views/rooms/RoomHeader";
@@ -32,6 +32,9 @@ import SdkConfig from "../../../../src/SdkConfig";
3232
import dispatcher from "../../../../src/dispatcher/dispatcher";
3333
import { CallStore } from "../../../../src/stores/CallStore";
3434
import { Call, ElementCall } from "../../../../src/models/Call";
35+
import * as ShieldUtils from "../../../../src/utils/ShieldUtils";
36+
37+
jest.mock("../../../../src/utils/ShieldUtils");
3538

3639
describe("RoomHeader", () => {
3740
let room: Room;
@@ -418,6 +421,44 @@ describe("RoomHeader", () => {
418421
expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({ view_call: true }));
419422
});
420423
});
424+
425+
describe("dm", () => {
426+
let client: MatrixClient;
427+
beforeEach(() => {
428+
client = MatrixClientPeg.get()!;
429+
430+
// Make the mocked room a DM
431+
jest.spyOn(client, "getAccountData").mockImplementation((eventType: string): MatrixEvent | undefined => {
432+
if (eventType === EventType.Direct) {
433+
return mkEvent({
434+
event: true,
435+
content: {
436+
[client.getUserId()!]: [room.roomId],
437+
},
438+
type: EventType.Direct,
439+
user: client.getSafeUserId(),
440+
});
441+
}
442+
443+
return undefined;
444+
});
445+
jest.spyOn(client, "isCryptoEnabled").mockReturnValue(true);
446+
});
447+
448+
it.each([
449+
[ShieldUtils.E2EStatus.Verified, "Verified"],
450+
[ShieldUtils.E2EStatus.Warning, "Untrusted"],
451+
])("shows the %s icon", async (value: ShieldUtils.E2EStatus, expectedLabel: string) => {
452+
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(value);
453+
454+
const { container } = render(
455+
<RoomHeader room={room} />,
456+
withClientContextRenderOptions(MatrixClientPeg.get()!),
457+
);
458+
459+
await waitFor(() => expect(getByLabelText(container, expectedLabel)).toBeInTheDocument());
460+
});
461+
});
421462
});
422463

423464
/**

0 commit comments

Comments
 (0)