Second batch: Replace MatrixClient.isRoomEncrypted by MatrixClient.CryptoApi.isEncryptionEnabledInRoom (#28466)

* Add `asyncFilter`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `MemberListStore.tsx`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `EventIndex.tsx`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `SendMessageComposer.tsx`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `ScalarMessaging.ts`

* Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `RolesRoomSettingsTab.tsx`

* Add reject doc to `asyncFilter`

* Reverse `MemberListStore.loadMembers` condition

* Remove async for `ScalarMessaging.ts`

* Display permission section only after `isEncrypted` is computed

* Display composer only after `isEncrypted` is computed

* Revert "Display composer only after `isEncrypted` is computed"

This reverts commit 4d4e037391.

* Revert "Replace `MatrixClient.isRoomEncrypted` by `MatrixClient.CryptoApi.isEncryptionEnabledInRoom` in `SendMessageComposer.tsx`"

This reverts commit 6bf06da02c.
This commit is contained in:
Florian Duros 2024-11-20 15:27:09 +01:00 committed by GitHub
parent ca33d9165a
commit 5cdcf44b6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 93 additions and 49 deletions

View File

@ -514,7 +514,7 @@ function getWidgets(event: MessageEvent<any>, roomId: string | null): void {
sendResponse(event, widgetStateEvents); sendResponse(event, widgetStateEvents);
} }
function getRoomEncState(event: MessageEvent<any>, roomId: string): void { async function getRoomEncState(event: MessageEvent<any>, roomId: string): Promise<void> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
sendError(event, _t("widget|error_need_to_be_logged_in")); sendError(event, _t("widget|error_need_to_be_logged_in"));
@ -525,7 +525,7 @@ function getRoomEncState(event: MessageEvent<any>, roomId: string): void {
sendError(event, _t("scalar|error_room_unknown")); sendError(event, _t("scalar|error_room_unknown"));
return; return;
} }
const roomIsEncrypted = MatrixClientPeg.safeGet().isRoomEncrypted(roomId); const roomIsEncrypted = Boolean(await client.getCrypto()?.isEncryptionEnabledInRoom(roomId));
sendResponse(event, roomIsEncrypted); sendResponse(event, roomIsEncrypted);
} }

View File

@ -127,12 +127,30 @@ interface IProps {
room: Room; room: Room;
} }
export default class RolesRoomSettingsTab extends React.Component<IProps> { interface RolesRoomSettingsTabState {
isRoomEncrypted: boolean;
isReady: boolean;
}
export default class RolesRoomSettingsTab extends React.Component<IProps, RolesRoomSettingsTabState> {
public static contextType = MatrixClientContext; public static contextType = MatrixClientContext;
public declare context: React.ContextType<typeof MatrixClientContext>; public declare context: React.ContextType<typeof MatrixClientContext>;
public componentDidMount(): void { public constructor(props: IProps) {
super(props);
this.state = {
isReady: false,
isRoomEncrypted: false,
};
}
public async componentDidMount(): Promise<void> {
this.context.on(RoomStateEvent.Update, this.onRoomStateUpdate); this.context.on(RoomStateEvent.Update, this.onRoomStateUpdate);
this.setState({
isRoomEncrypted:
(await this.context.getCrypto()?.isEncryptionEnabledInRoom(this.props.room.roomId)) || false,
isReady: true,
});
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
@ -416,7 +434,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
.filter(Boolean); .filter(Boolean);
// hide the power level selector for enabling E2EE if it the room is already encrypted // hide the power level selector for enabling E2EE if it the room is already encrypted
if (client.isRoomEncrypted(this.props.room.roomId)) { if (this.state.isRoomEncrypted) {
delete eventsLevels[EventType.RoomEncryption]; delete eventsLevels[EventType.RoomEncryption];
} }
@ -458,6 +476,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
{canChangeLevels && <AddPrivilegedUsers room={room} defaultUserLevel={defaultUserLevel} />} {canChangeLevels && <AddPrivilegedUsers room={room} defaultUserLevel={defaultUserLevel} />}
{mutedUsersSection} {mutedUsersSection}
{bannedUsersSection} {bannedUsersSection}
{this.state.isReady && (
<SettingsFieldset <SettingsFieldset
legend={_t("room_settings|permissions|permissions_section")} legend={_t("room_settings|permissions|permissions_section")}
description={ description={
@ -469,6 +488,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
{powerSelectors} {powerSelectors}
{eventPowerSelectors} {eventPowerSelectors}
</SettingsFieldset> </SettingsFieldset>
)}
</SettingsSection> </SettingsSection>
</SettingsTab> </SettingsTab>
); );

View File

@ -39,6 +39,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel"; import { SettingLevel } from "../settings/SettingLevel";
import { ICrawlerCheckpoint, IEventAndProfile, IIndexStats, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager"; import { ICrawlerCheckpoint, IEventAndProfile, IIndexStats, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager";
import { asyncFilter } from "../utils/arrays.ts";
// The time in ms that the crawler will wait loop iterations if there // The time in ms that the crawler will wait loop iterations if there
// have not been any checkpoints to consume in the last iteration. // have not been any checkpoints to consume in the last iteration.
@ -103,13 +104,11 @@ export default class EventIndex extends EventEmitter {
const client = MatrixClientPeg.safeGet(); const client = MatrixClientPeg.safeGet();
const rooms = client.getRooms(); const rooms = client.getRooms();
const isRoomEncrypted = (room: Room): boolean => {
return client.isRoomEncrypted(room.roomId);
};
// We only care to crawl the encrypted rooms, non-encrypted // We only care to crawl the encrypted rooms, non-encrypted
// rooms can use the search provided by the homeserver. // rooms can use the search provided by the homeserver.
const encryptedRooms = rooms.filter(isRoomEncrypted); const encryptedRooms = await asyncFilter(rooms, async (room) =>
Boolean(await client.getCrypto()?.isEncryptionEnabledInRoom(room.roomId)),
);
logger.log("EventIndex: Adding initial crawler checkpoints"); logger.log("EventIndex: Adding initial crawler checkpoints");

View File

@ -70,7 +70,7 @@ export class MemberListStore {
return []; return [];
} }
if (!this.isLazyLoadingEnabled(roomId) || this.loadedRooms.has(roomId)) { if (this.loadedRooms.has(roomId) || !(await this.isLazyLoadingEnabled(roomId))) {
// nice and easy, we must already have all the members so just return them. // nice and easy, we must already have all the members so just return them.
return this.loadMembersInRoom(room); return this.loadMembersInRoom(room);
} }
@ -121,10 +121,10 @@ export class MemberListStore {
* @param roomId The room to check if lazy loading is enabled * @param roomId The room to check if lazy loading is enabled
* @returns True if enabled * @returns True if enabled
*/ */
private isLazyLoadingEnabled(roomId: string): boolean { private async isLazyLoadingEnabled(roomId: string): Promise<boolean> {
if (SettingsStore.getValue("feature_sliding_sync")) { if (SettingsStore.getValue("feature_sliding_sync")) {
// only unencrypted rooms use lazy loading // only unencrypted rooms use lazy loading
return !this.stores.client!.isRoomEncrypted(roomId); return !(await this.stores.client?.getCrypto()?.isEncryptionEnabledInRoom(roomId));
} }
return this.stores.client!.hasLazyLoadMembersEnabled(); return this.stores.client!.hasLazyLoadMembersEnabled();
} }

View File

@ -350,6 +350,17 @@ export async function asyncSomeParallel<T>(
} }
} }
/**
* Async version of Array.filter.
* If one of the promises rejects, the whole operation will reject.
* @param values
* @param predicate
*/
export async function asyncFilter<T>(values: Array<T>, predicate: (value: T) => Promise<boolean>): Promise<Array<T>> {
const results = await Promise.all(values.map(predicate));
return values.filter((_, i) => results[i]);
}
export function filterBoolean<T>(values: Array<T | null | undefined>): T[] { export function filterBoolean<T>(values: Array<T | null | undefined>): T[] {
return values.filter(Boolean) as T[]; return values.filter(Boolean) as T[];
} }

View File

@ -27,16 +27,19 @@ describe("RolesRoomSettingsTab", () => {
let cli: MatrixClient; let cli: MatrixClient;
let room: Room; let room: Room;
const renderTab = (propRoom: Room = room): RenderResult => { const renderTab = async (propRoom: Room = room): Promise<RenderResult> => {
return render(<RolesRoomSettingsTab room={propRoom} />, withClientContextRenderOptions(cli)); const renderResult = render(<RolesRoomSettingsTab room={propRoom} />, withClientContextRenderOptions(cli));
// Wait for the tab to be ready
await waitFor(() => expect(screen.getByText("Permissions")).toBeInTheDocument());
return renderResult;
}; };
const getVoiceBroadcastsSelect = (): HTMLElement => { const getVoiceBroadcastsSelect = async (): Promise<Element> => {
return renderTab().container.querySelector("select[label='Voice broadcasts']")!; return (await renderTab()).container.querySelector("select[label='Voice broadcasts']")!;
}; };
const getVoiceBroadcastsSelectedOption = (): HTMLElement => { const getVoiceBroadcastsSelectedOption = async (): Promise<Element> => {
return renderTab().container.querySelector("select[label='Voice broadcasts'] option:checked")!; return (await renderTab()).container.querySelector("select[label='Voice broadcasts'] option:checked")!;
}; };
beforeEach(() => { beforeEach(() => {
@ -45,7 +48,7 @@ describe("RolesRoomSettingsTab", () => {
room = mkStubRoom(roomId, "test room", cli); room = mkStubRoom(roomId, "test room", cli);
}); });
it("should allow an Admin to demote themselves but not others", () => { it("should allow an Admin to demote themselves but not others", async () => {
mocked(cli.getRoom).mockReturnValue(room); mocked(cli.getRoom).mockReturnValue(room);
// @ts-ignore - mocked doesn't support overloads properly // @ts-ignore - mocked doesn't support overloads properly
mocked(room.currentState.getStateEvents).mockImplementation((type, key) => { mocked(room.currentState.getStateEvents).mockImplementation((type, key) => {
@ -67,19 +70,19 @@ describe("RolesRoomSettingsTab", () => {
return null; return null;
}); });
mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true); mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true);
const { container } = renderTab(); const { container } = await renderTab();
expect(container.querySelector(`[placeholder="${cli.getUserId()}"]`)).not.toBeDisabled(); expect(container.querySelector(`[placeholder="${cli.getUserId()}"]`)).not.toBeDisabled();
expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled(); expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled();
}); });
it("should initially show »Moderator« permission for »Voice broadcasts«", () => { it("should initially show »Moderator« permission for »Voice broadcasts«", async () => {
expect(getVoiceBroadcastsSelectedOption().textContent).toBe("Moderator"); expect((await getVoiceBroadcastsSelectedOption()).textContent).toBe("Moderator");
}); });
describe("when setting »Default« permission for »Voice broadcasts«", () => { describe("when setting »Default« permission for »Voice broadcasts«", () => {
beforeEach(() => { beforeEach(async () => {
fireEvent.change(getVoiceBroadcastsSelect(), { fireEvent.change(await getVoiceBroadcastsSelect(), {
target: { value: 0 }, target: { value: 0 },
}); });
}); });
@ -122,12 +125,12 @@ describe("RolesRoomSettingsTab", () => {
}); });
describe("Join Element calls", () => { describe("Join Element calls", () => {
it("defaults to moderator for joining calls", () => { it("defaults to moderator for joining calls", async () => {
expect(getJoinCallSelectedOption(renderTab())?.textContent).toBe("Moderator"); expect(getJoinCallSelectedOption(await renderTab())?.textContent).toBe("Moderator");
}); });
it("can change joining calls power level", () => { it("can change joining calls power level", async () => {
const tab = renderTab(); const tab = await renderTab();
fireEvent.change(getJoinCallSelect(tab), { fireEvent.change(getJoinCallSelect(tab), {
target: { value: 0 }, target: { value: 0 },
@ -143,12 +146,12 @@ describe("RolesRoomSettingsTab", () => {
}); });
describe("Start Element calls", () => { describe("Start Element calls", () => {
it("defaults to moderator for starting calls", () => { it("defaults to moderator for starting calls", async () => {
expect(getStartCallSelectedOption(renderTab())?.textContent).toBe("Moderator"); expect(getStartCallSelectedOption(await renderTab())?.textContent).toBe("Moderator");
}); });
it("can change starting calls power level", () => { it("can change starting calls power level", async () => {
const tab = renderTab(); const tab = await renderTab();
fireEvent.change(getStartCallSelect(tab), { fireEvent.change(getStartCallSelect(tab), {
target: { value: 0 }, target: { value: 0 },
@ -164,10 +167,10 @@ describe("RolesRoomSettingsTab", () => {
}); });
}); });
it("hides when group calls disabled", () => { it("hides when group calls disabled", async () => {
setGroupCallsEnabled(false); setGroupCallsEnabled(false);
const tab = renderTab(); const tab = await renderTab();
expect(getStartCallSelect(tab)).toBeFalsy(); expect(getStartCallSelect(tab)).toBeFalsy();
expect(getStartCallSelectedOption(tab)).toBeFalsy(); expect(getStartCallSelectedOption(tab)).toBeFalsy();
@ -250,7 +253,7 @@ describe("RolesRoomSettingsTab", () => {
return null; return null;
}); });
mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true); mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true);
const { container } = renderTab(); const { container } = await renderTab();
const selector = container.querySelector(`[placeholder="${cli.getUserId()}"]`)!; const selector = container.querySelector(`[placeholder="${cli.getUserId()}"]`)!;
fireEvent.change(selector, { target: { value: "50" } }); fireEvent.change(selector, { target: { value: "50" } });

View File

@ -189,8 +189,7 @@ describe("MemberListStore", () => {
}); });
it("does not use lazy loading on encrypted rooms", async () => { it("does not use lazy loading on encrypted rooms", async () => {
client.isRoomEncrypted = jest.fn(); jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
mocked(client.isRoomEncrypted).mockReturnValue(true);
const { joined } = await store.loadMemberList(roomId); const { joined } = await store.loadMemberList(roomId);
expect(joined).toEqual([room.getMember(alice)]); expect(joined).toEqual([room.getMember(alice)]);

View File

@ -24,6 +24,7 @@ import {
asyncEvery, asyncEvery,
asyncSome, asyncSome,
asyncSomeParallel, asyncSomeParallel,
asyncFilter,
} from "../../../src/utils/arrays"; } from "../../../src/utils/arrays";
type TestParams = { input: number[]; output: number[] }; type TestParams = { input: number[]; output: number[] };
@ -480,4 +481,15 @@ describe("arrays", () => {
expect(await asyncSomeParallel([1, 2, 3], predicate)).toBe(true); expect(await asyncSomeParallel([1, 2, 3], predicate)).toBe(true);
}); });
}); });
describe("asyncFilter", () => {
it("when called with an empty array, it should return an empty array", async () => {
expect(await asyncFilter([], jest.fn().mockResolvedValue(true))).toEqual([]);
});
it("should filter the content", async () => {
const predicate = jest.fn().mockImplementation((value) => Promise.resolve(value === 2));
expect(await asyncFilter([1, 2, 3], predicate)).toEqual([2]);
});
});
}); });