-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Implement MSC4155: Invite filtering #29603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
a1d72ea
Add settings for MSC4155
Half-Shot 794695a
copyright
Half-Shot d213a22
Tweak to not use js-sdk
Half-Shot c0b881e
Merge remote-tracking branch 'origin/develop' into hs/invite-filterin…
Half-Shot acafd03
Update for latest MSC
Half-Shot 287b8b6
Merge remote-tracking branch 'origin/develop' into hs/invite-filterin…
Half-Shot 44a5fca
Various tidyups
Half-Shot ff7551a
Move tab
Half-Shot 05066d5
i18n
Half-Shot d67bdac
update .snap
Half-Shot caa29f3
mvvm
Half-Shot 0297a40
lint
Half-Shot 1d9d17c
add header
Half-Shot 5a86c7c
Merge remote-tracking branch 'origin/develop' into hs/invite-filterin…
Half-Shot 004fc1a
Remove capability check
Half-Shot 86d81e1
fix
Half-Shot f5c3d3d
Rewrite to use Settings
Half-Shot f29c03d
lint
Half-Shot f26ddaa
Merge remote-tracking branch 'origin/develop' into hs/invite-filterin…
Half-Shot d614893
lint
Half-Shot 134c7fc
fix test
Half-Shot fcb1e30
Tweaks
Half-Shot c01ca6c
lint
Half-Shot 2e895ee
Merge branch 'develop' into hs/invite-filtering-settings
Half-Shot e805aae
revert copyright
Half-Shot 79ba365
update screenshot
Half-Shot 793fe53
cleanup
Half-Shot 30e2475
Merge branch 'develop' into hs/invite-filtering-settings
Half-Shot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| /* | ||
| Copyright 2025 New Vector Ltd. | ||
|
|
||
| SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import React, { type ChangeEventHandler, type FC, useCallback, useEffect, useMemo, useState } from "react"; | ||
| import { type AccountDataEvents } from "matrix-js-sdk/src/types"; | ||
| import { ErrorMessage, InlineField, Label, Root, ToggleInput, Tooltip } from "@vector-im/compound-web"; | ||
| import { logger } from "matrix-js-sdk/src/logger"; | ||
|
|
||
| import { SettingsSubsection } from "./shared/SettingsSubsection"; | ||
| import { _t } from "../../../languageHandler"; | ||
| import { useAccountData } from "../../../hooks/useAccountData"; | ||
| import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; | ||
|
|
||
| export const InviteControlsPanel: FC = () => { | ||
| const client = useMatrixClientContext(); | ||
| const [hasError, setHasError] = useState(false); | ||
| const [busy, setBusy] = useState(false); | ||
| const [canUse, setCanUse] = useState<boolean>(); | ||
| const inviteConfig = useAccountData<AccountDataEvents["org.matrix.msc4155.invite_permission_config"]>( | ||
| client, | ||
| "org.matrix.msc4155.invite_permission_config", | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| (async () => { | ||
| setCanUse(await client.doesServerSupportUnstableFeature("org.matrix.msc4155")); | ||
| })(); | ||
| }, [client]); | ||
|
Half-Shot marked this conversation as resolved.
Outdated
|
||
|
|
||
| // This implements a very basic version of MSC4155 that simply allows | ||
| // or disallows all invites by setting a simple glob. | ||
| // Keep in mind that users may configure more powerful rules on other | ||
| // clients and we should keep those intact. | ||
| const isBlockingAll = useMemo(() => { | ||
| if (!inviteConfig) { | ||
| return false; | ||
| } | ||
| return inviteConfig["blocked_users"]?.includes("*") === true; | ||
| }, [inviteConfig]); | ||
|
|
||
| const setValue = useCallback<ChangeEventHandler<HTMLInputElement>>( | ||
| async (e) => { | ||
| e.preventDefault(); | ||
| setHasError(false); | ||
| setBusy(true); | ||
| const newConfig = { ...inviteConfig }; | ||
| if (newConfig["blocked_users"]?.includes("*")) { | ||
| newConfig.blocked_users = newConfig.blocked_users.filter((u) => u !== "*"); | ||
| } else { | ||
| newConfig.blocked_users = [...new Set([...(newConfig.blocked_users ?? []), "*"])]; | ||
| } | ||
| try { | ||
| await client.setAccountData("org.matrix.msc4155.invite_permission_config", newConfig); | ||
| } catch (ex) { | ||
| logger.error("Could not change input config", ex); | ||
| setHasError(true); | ||
| } finally { | ||
| setBusy(false); | ||
| } | ||
| }, | ||
| [client, inviteConfig], | ||
| ); | ||
|
|
||
| let content; | ||
| if (canUse) { | ||
| content = ( | ||
| <> | ||
| <InlineField | ||
| name="default" | ||
| control={ | ||
| <ToggleInput | ||
| id="mx_invite_controls_default" | ||
| disabled={busy || !canUse} | ||
| onChange={setValue} | ||
| checked={!isBlockingAll} | ||
| /> | ||
| } | ||
| > | ||
| <Label htmlFor="mx_invite_controls_default">{_t("settings|invite_controls|default_label")}</Label> | ||
| {hasError && <ErrorMessage>{_t("settings|invite_controls|error_message")}</ErrorMessage>} | ||
| </InlineField> | ||
| </> | ||
| ); | ||
| } else if (canUse === false) { | ||
| content = ( | ||
| <Tooltip description={_t("settings|invite_controls|not_supported")}> | ||
| <InlineField | ||
| name="default" | ||
| control={<ToggleInput id="mx_invite_controls_default" disabled={true} checked={!isBlockingAll} />} | ||
| > | ||
| <Label htmlFor="mx_invite_controls_default">{_t("settings|invite_controls|default_label")}</Label> | ||
| </InlineField> | ||
| </Tooltip> | ||
| ); | ||
| } else { | ||
| return; | ||
| } | ||
|
|
||
| return ( | ||
| <SettingsSubsection heading={_t("settings|invite_controls|title")}> | ||
| <Root>{content}</Root> | ||
| </SettingsSubsection> | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
test/unit-tests/components/views/settings/InviteControlsPanel-test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| /* | ||
| Copyright 2025 New Vector Ltd. | ||
|
|
||
| SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| Please see LICENSE files in the repository root for full details. | ||
| */ | ||
| import React from "react"; | ||
| import { render } from "jest-matrix-react"; | ||
| import { type AccountDataEvents } from "matrix-js-sdk/src/types"; | ||
| import { ClientEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; | ||
| import userEvent from "@testing-library/user-event"; | ||
|
|
||
| import { stubClient } from "../../../../test-utils"; | ||
| import { InviteControlsPanel } from "../../../../../src/components/views/settings/InviteControlsPanel"; | ||
| import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; | ||
|
|
||
| describe("InviteControlsPanel", () => { | ||
| it("does not render if not supported", async () => { | ||
| const client = stubClient(); | ||
| client.getAccountData = jest.fn().mockReturnValue(undefined); | ||
| client.doesServerSupportUnstableFeature = jest.fn().mockResolvedValue(false); | ||
| const { findByText, findByLabelText } = render( | ||
| <MatrixClientContext.Provider value={client}> | ||
| <InviteControlsPanel /> | ||
| </MatrixClientContext.Provider>, | ||
| ); | ||
| const input = await findByLabelText("Allow users to invite you to rooms"); | ||
| await userEvent.hover(input); | ||
| const result = await findByText("Your server does not implement this feature."); | ||
| expect(result).toBeInTheDocument(); | ||
| }); | ||
| it("renders correct state when no value is present", async () => { | ||
| const client = stubClient(); | ||
| client.getAccountData = jest.fn().mockReturnValue(undefined); | ||
| client.doesServerSupportUnstableFeature = jest.fn().mockImplementation(async (v) => v === "org.matrix.msc4155"); | ||
| const { findByLabelText } = render( | ||
| <MatrixClientContext.Provider value={client}> | ||
| <InviteControlsPanel /> | ||
| </MatrixClientContext.Provider>, | ||
| ); | ||
| const result = await findByLabelText("Allow users to invite you to rooms"); | ||
| expect((result as HTMLInputElement).checked).toEqual(true); | ||
| }); | ||
| it.each([{}, { blocked_users: ["some"] }, { blocked_users: [" *"] }])( | ||
| "renders correct state when permissive values are present", | ||
| async (eventData: AccountDataEvents["org.matrix.msc4155.invite_permission_config"]) => { | ||
| const client = stubClient(); | ||
| client.getAccountData = jest | ||
| .fn() | ||
| .mockImplementation((v) => | ||
| v === "org.matrix.msc4155.invite_permission_config" | ||
| ? new MatrixEvent({ content: eventData }) | ||
| : undefined, | ||
| ); | ||
| client.doesServerSupportUnstableFeature = jest | ||
| .fn() | ||
| .mockImplementation(async (v) => v === "org.matrix.msc4155"); | ||
| const { findByLabelText } = render( | ||
| <MatrixClientContext.Provider value={client}> | ||
| <InviteControlsPanel /> | ||
| </MatrixClientContext.Provider>, | ||
| ); | ||
| const result = await findByLabelText("Allow users to invite you to rooms"); | ||
| expect((result as HTMLInputElement).checked).toEqual(true); | ||
| }, | ||
| ); | ||
| it("renders correct state when invites are blocked", async () => { | ||
| const client = stubClient(); | ||
| client.getAccountData = jest | ||
| .fn() | ||
| .mockImplementation((v) => | ||
| v === "org.matrix.msc4155.invite_permission_config" | ||
| ? new MatrixEvent({ content: { blocked_users: "*" } }) | ||
| : undefined, | ||
| ); | ||
| client.doesServerSupportUnstableFeature = jest.fn().mockImplementation(async (v) => v === "org.matrix.msc4155"); | ||
| const { findByLabelText } = render( | ||
| <MatrixClientContext.Provider value={client}> | ||
| <InviteControlsPanel /> | ||
| </MatrixClientContext.Provider>, | ||
| ); | ||
| const result = await findByLabelText("Allow users to invite you to rooms"); | ||
| expect((result as HTMLInputElement).checked).toEqual(false); | ||
| }); | ||
| it("handles disabling all invites", async () => { | ||
| const client = stubClient(); | ||
| const setAccountData = (client.setAccountData = jest.fn().mockImplementation((type, content) => { | ||
| client.emit(ClientEvent.AccountData, new MatrixEvent({ type, content })); | ||
| })); | ||
| client.getAccountData = jest | ||
| .fn() | ||
| .mockImplementation((v) => | ||
| v === "org.matrix.msc4155.invite_permission_config" | ||
| ? new MatrixEvent({ content: { blocked_users: ["other_rules"], foo_bar: true } }) | ||
| : undefined, | ||
| ); | ||
| client.doesServerSupportUnstableFeature = jest.fn().mockImplementation(async (v) => v === "org.matrix.msc4155"); | ||
| const { findByLabelText } = render( | ||
| <MatrixClientContext.Provider value={client}> | ||
| <InviteControlsPanel /> | ||
| </MatrixClientContext.Provider>, | ||
| ); | ||
| const result = await findByLabelText("Allow users to invite you to rooms"); | ||
| await userEvent.click(result); | ||
| // Preserves other rules that might already be configured. | ||
| expect(setAccountData).toHaveBeenCalledWith("org.matrix.msc4155.invite_permission_config", { | ||
| blocked_users: ["other_rules", "*"], | ||
| foo_bar: true, | ||
| }); | ||
| expect((result as HTMLInputElement).checked).toEqual(false); | ||
| }); | ||
| it("handles enabling invites", async () => { | ||
| const client = stubClient(); | ||
| const setAccountData = (client.setAccountData = jest.fn().mockImplementation((type, content) => { | ||
| client.emit(ClientEvent.AccountData, new MatrixEvent({ type, content })); | ||
| })); | ||
| client.getAccountData = jest | ||
| .fn() | ||
| .mockImplementation((v) => | ||
| v === "org.matrix.msc4155.invite_permission_config" | ||
| ? new MatrixEvent({ content: { blocked_users: ["*", "other_rules"], foo_bar: true } }) | ||
| : undefined, | ||
| ); | ||
| client.doesServerSupportUnstableFeature = jest.fn().mockImplementation(async (v) => v === "org.matrix.msc4155"); | ||
| const { findByLabelText } = render( | ||
| <MatrixClientContext.Provider value={client}> | ||
| <InviteControlsPanel /> | ||
| </MatrixClientContext.Provider>, | ||
| ); | ||
| const result = await findByLabelText("Allow users to invite you to rooms"); | ||
| await userEvent.click(result); | ||
| expect(setAccountData).toHaveBeenCalledWith("org.matrix.msc4155.invite_permission_config", { | ||
| blocked_users: ["other_rules"], | ||
| foo_bar: true, | ||
| }); | ||
| expect((result as HTMLInputElement).checked).toEqual(true); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.