Skip to content

Commit 9a3c410

Browse files
committed
Rework UrlPreviewSettings to use MatrixClient.CryptoApi.isEncryptionEnabledInRoom
1 parent 9a12679 commit 9a3c410

File tree

4 files changed

+405
-87
lines changed

4 files changed

+405
-87
lines changed

src/components/views/room_settings/UrlPreviewSettings.tsx

Lines changed: 119 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -9,107 +9,140 @@ 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, { ReactNode } from "react";
12+
import React, { ReactNode, JSX } from "react";
1313
import { Room } from "matrix-js-sdk/src/matrix";
1414

15-
import { _t, _td } from "../../../languageHandler";
15+
import { _t } from "../../../languageHandler";
1616
import SettingsStore from "../../../settings/SettingsStore";
1717
import dis from "../../../dispatcher/dispatcher";
18-
import { MatrixClientPeg } from "../../../MatrixClientPeg";
1918
import { Action } from "../../../dispatcher/actions";
2019
import { SettingLevel } from "../../../settings/SettingLevel";
2120
import SettingsFlag from "../elements/SettingsFlag";
2221
import SettingsFieldset from "../settings/SettingsFieldset";
2322
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
23+
import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
24+
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext.tsx";
25+
import { useSettingValueAt } from "../../../hooks/useSettings.ts";
2426

25-
interface IProps {
27+
/**
28+
* The URL preview settings for a room
29+
*/
30+
interface UrlPreviewSettingsProps {
31+
/**
32+
* The room.
33+
*/
2634
room: Room;
2735
}
2836

29-
export default class UrlPreviewSettings extends React.Component<IProps> {
30-
private onClickUserSettings = (e: ButtonEvent): void => {
31-
e.preventDefault();
32-
e.stopPropagation();
33-
dis.fire(Action.ViewUserSettings);
34-
};
35-
36-
public render(): ReactNode {
37-
const roomId = this.props.room.roomId;
38-
const isEncrypted = MatrixClientPeg.safeGet().isRoomEncrypted(roomId);
39-
40-
let previewsForAccount: ReactNode | undefined;
41-
let previewsForRoom: ReactNode | undefined;
42-
43-
if (!isEncrypted) {
44-
// Only show account setting state and room state setting state in non-e2ee rooms where they apply
45-
const accountEnabled = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "urlPreviewsEnabled");
46-
if (accountEnabled) {
47-
previewsForAccount = _t(
48-
"room_settings|general|user_url_previews_default_on",
49-
{},
50-
{
51-
a: (sub) => (
52-
<AccessibleButton kind="link_inline" onClick={this.onClickUserSettings}>
53-
{sub}
54-
</AccessibleButton>
55-
),
56-
},
57-
);
58-
} else {
59-
previewsForAccount = _t(
60-
"room_settings|general|user_url_previews_default_off",
61-
{},
62-
{
63-
a: (sub) => (
64-
<AccessibleButton kind="link_inline" onClick={this.onClickUserSettings}>
65-
{sub}
66-
</AccessibleButton>
67-
),
68-
},
69-
);
70-
}
71-
72-
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, SettingLevel.ROOM)) {
73-
previewsForRoom = (
74-
<SettingsFlag
75-
name="urlPreviewsEnabled"
76-
level={SettingLevel.ROOM}
77-
roomId={roomId}
78-
isExplicit={true}
79-
/>
80-
);
81-
} else {
82-
let str = _td("room_settings|general|default_url_previews_on");
83-
if (!SettingsStore.getValueAt(SettingLevel.ROOM, "urlPreviewsEnabled", roomId, /*explicit=*/ true)) {
84-
str = _td("room_settings|general|default_url_previews_off");
85-
}
86-
previewsForRoom = <div>{_t(str)}</div>;
87-
}
88-
} else {
89-
previewsForAccount = _t("room_settings|general|url_preview_encryption_warning");
90-
}
91-
92-
const previewsForRoomAccount = // in an e2ee room we use a special key to enforce per-room opt-in
93-
(
94-
<SettingsFlag
95-
name={isEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled"}
96-
level={SettingLevel.ROOM_DEVICE}
97-
roomId={roomId}
98-
/>
99-
);
100-
101-
const description = (
102-
<>
103-
<p>{_t("room_settings|general|url_preview_explainer")}</p>
104-
<p>{previewsForAccount}</p>
105-
</>
37+
export function UrlPreviewSettings({ room }: UrlPreviewSettingsProps): JSX.Element {
38+
const { roomId } = room;
39+
const matrixClient = useMatrixClientContext();
40+
const isEncrypted = Boolean(useIsEncrypted(matrixClient, room));
41+
const previewsForRoomAccount = // in an e2ee room we use a special key to enforce per-room opt-in
42+
(
43+
<SettingsFlag
44+
name={isEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled"}
45+
level={SettingLevel.ROOM_DEVICE}
46+
roomId={roomId}
47+
/>
10648
);
10749

108-
return (
109-
<SettingsFieldset legend={_t("room_settings|general|url_previews_section")} description={description}>
110-
{previewsForRoom}
111-
{previewsForRoomAccount}
112-
</SettingsFieldset>
50+
return (
51+
<SettingsFieldset
52+
legend={_t("room_settings|general|url_previews_section")}
53+
description={<Description isEncrypted={isEncrypted} />}
54+
>
55+
<PreviewsForRoom isEncrypted={isEncrypted} roomId={roomId} />
56+
{previewsForRoomAccount}
57+
</SettingsFieldset>
58+
);
59+
}
60+
61+
/**
62+
* Click handler for the user settings link
63+
* @param e
64+
*/
65+
function onClickUserSettings(e: ButtonEvent): void {
66+
e.preventDefault();
67+
e.stopPropagation();
68+
dis.fire(Action.ViewUserSettings);
69+
}
70+
71+
/**
72+
* The description for the URL preview settings
73+
*/
74+
interface DescriptionProps {
75+
/**
76+
* Whether the room is encrypted
77+
*/
78+
isEncrypted: boolean;
79+
}
80+
81+
function Description({ isEncrypted }: DescriptionProps): JSX.Element {
82+
const urlPreviewsEnabled = useSettingValueAt(SettingLevel.ACCOUNT, "urlPreviewsEnabled");
83+
84+
let previewsForAccount: ReactNode | undefined;
85+
if (isEncrypted) {
86+
previewsForAccount = _t("room_settings|general|url_preview_encryption_warning");
87+
} else {
88+
const button = {
89+
a: (sub: string) => (
90+
<AccessibleButton kind="link_inline" onClick={onClickUserSettings}>
91+
{sub}
92+
</AccessibleButton>
93+
),
94+
};
95+
96+
previewsForAccount = urlPreviewsEnabled
97+
? _t("room_settings|general|user_url_previews_default_on", {}, button)
98+
: _t("room_settings|general|user_url_previews_default_off", {}, button);
99+
}
100+
101+
return (
102+
<>
103+
<p>{_t("room_settings|general|url_preview_explainer")}</p>
104+
<p>{previewsForAccount}</p>
105+
</>
106+
);
107+
}
108+
109+
/**
110+
* The description for the URL preview settings
111+
*/
112+
interface PreviewsForRoomProps {
113+
/**
114+
* Whether the room is encrypted
115+
*/
116+
isEncrypted: boolean;
117+
/**
118+
* The room ID
119+
*/
120+
roomId: string;
121+
}
122+
123+
function PreviewsForRoom({ isEncrypted, roomId }: PreviewsForRoomProps): JSX.Element | null {
124+
const urlPreviewsEnabled = useSettingValueAt(
125+
SettingLevel.ACCOUNT,
126+
"urlPreviewsEnabled",
127+
roomId,
128+
/*explicit=*/ true,
129+
);
130+
if (isEncrypted) return null;
131+
132+
let previewsForRoom: ReactNode;
133+
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, SettingLevel.ROOM)) {
134+
previewsForRoom = (
135+
<SettingsFlag name="urlPreviewsEnabled" level={SettingLevel.ROOM} roomId={roomId} isExplicit={true} />
136+
);
137+
} else {
138+
previewsForRoom = (
139+
<div>
140+
{urlPreviewsEnabled
141+
? _t("room_settings|general|default_url_previews_on")
142+
: _t("room_settings|general|default_url_previews_off")}
143+
</div>
113144
);
114145
}
146+
147+
return previewsForRoom;
115148
}

src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import dis from "../../../../../dispatcher/dispatcher";
1616
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
1717
import SettingsStore from "../../../../../settings/SettingsStore";
1818
import { UIFeature } from "../../../../../settings/UIFeature";
19-
import UrlPreviewSettings from "../../../room_settings/UrlPreviewSettings";
2019
import AliasSettings from "../../../room_settings/AliasSettings";
2120
import PosthogTrackers from "../../../../../PosthogTrackers";
2221
import { SettingsSubsection } from "../../shared/SettingsSubsection";
2322
import SettingsTab from "../SettingsTab";
2423
import { SettingsSection } from "../../shared/SettingsSection";
24+
import { UrlPreviewSettings } from "../../../room_settings/UrlPreviewSettings";
2525

2626
interface IProps {
2727
room: Room;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React from "react";
9+
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
10+
import { render, screen } from "jest-matrix-react";
11+
import { waitFor } from "@testing-library/dom";
12+
13+
import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../test-utils";
14+
import { UrlPreviewSettings } from "../../../../../src/components/views/room_settings/UrlPreviewSettings.tsx";
15+
import SettingsStore from "../../../../../src/settings/SettingsStore.ts";
16+
import dis from "../../../../../src/dispatcher/dispatcher.ts";
17+
import { Action } from "../../../../../src/dispatcher/actions.ts";
18+
19+
describe("UrlPreviewSettings", () => {
20+
let client: MatrixClient;
21+
let room: Room;
22+
23+
beforeEach(() => {
24+
client = createTestClient();
25+
room = mkStubRoom("roomId", "room", client);
26+
});
27+
28+
afterEach(() => {
29+
jest.restoreAllMocks();
30+
});
31+
32+
function renderComponent() {
33+
return render(<UrlPreviewSettings room={room} />, withClientContextRenderOptions(client));
34+
}
35+
36+
it("should display the correct preview when the room is encrypted and the url preview is enabled", async () => {
37+
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
38+
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
39+
40+
const { asFragment } = renderComponent();
41+
await waitFor(() => {
42+
expect(
43+
screen.getByText(
44+
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
45+
),
46+
).toBeInTheDocument();
47+
});
48+
expect(asFragment()).toMatchSnapshot();
49+
});
50+
51+
it("should display the correct preview when the room is unencrypted and the url preview is enabled", async () => {
52+
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
53+
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
54+
jest.spyOn(dis, "fire").mockReturnValue(undefined);
55+
56+
const { asFragment } = renderComponent();
57+
await waitFor(() => {
58+
expect(screen.getByRole("button", { name: "enabled" })).toBeInTheDocument();
59+
expect(
60+
screen.getByText("URL previews are enabled by default for participants in this room."),
61+
).toBeInTheDocument();
62+
});
63+
expect(asFragment()).toMatchSnapshot();
64+
65+
screen.getByRole("button", { name: "enabled" }).click();
66+
expect(dis.fire).toHaveBeenCalledWith(Action.ViewUserSettings);
67+
});
68+
69+
it("should display the correct preview when the room is unencrypted and the url preview is disabled", async () => {
70+
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
71+
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(false);
72+
73+
const { asFragment } = renderComponent();
74+
await waitFor(() => {
75+
expect(screen.getByRole("button", { name: "disabled" })).toBeInTheDocument();
76+
expect(
77+
screen.getByText("URL previews are disabled by default for participants in this room."),
78+
).toBeInTheDocument();
79+
});
80+
expect(asFragment()).toMatchSnapshot();
81+
});
82+
});

0 commit comments

Comments
 (0)