MSC4380: Invite blocking (#31268)
* Initial implementation of MSC4380 * fix lint * Update InviteRulesAccountSetting-test * add some docs * `block_all` -> `default_action` * Add a unit test for BlockInvitesConfigController
This commit is contained in:
committed by
GitHub
parent
5869c519ed
commit
1c684489da
@@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export const INVITE_RULES_ACCOUNT_DATA_TYPE = "org.matrix.msc4155.invite_permission_config";
|
||||
export const MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE = "org.matrix.msc4380.invite_permission_config";
|
||||
|
||||
export interface InviteConfigAccountData {
|
||||
allowed_users?: string[];
|
||||
|
||||
9
src/@types/matrix-js-sdk.d.ts
vendored
9
src/@types/matrix-js-sdk.d.ts
vendored
@@ -15,7 +15,11 @@ import type { EmptyObject } from "matrix-js-sdk/src/matrix";
|
||||
import type { DeviceClientInformation } from "../utils/device/types.ts";
|
||||
import type { UserWidget } from "../utils/WidgetUtils-types.ts";
|
||||
import { type MediaPreviewConfig } from "./media_preview.ts";
|
||||
import { type INVITE_RULES_ACCOUNT_DATA_TYPE, type InviteConfigAccountData } from "./invite-rules.ts";
|
||||
import {
|
||||
type INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
type InviteConfigAccountData,
|
||||
type MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE,
|
||||
} from "./invite-rules.ts";
|
||||
|
||||
// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
|
||||
declare module "matrix-js-sdk/src/types" {
|
||||
@@ -91,6 +95,9 @@ declare module "matrix-js-sdk/src/types" {
|
||||
|
||||
// MSC4155: Invite filtering
|
||||
[INVITE_RULES_ACCOUNT_DATA_TYPE]: InviteConfigAccountData;
|
||||
|
||||
[MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE]: { default_action?: "allow" | "block" };
|
||||
|
||||
"io.element.msc4278.media_preview_config": MediaPreviewConfig;
|
||||
|
||||
// Indicate whether recovery is enabled or disabled
|
||||
|
||||
@@ -15,32 +15,57 @@ import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
|
||||
/**
|
||||
* A settings component which allows the user to enable/disable invite blocking.
|
||||
*
|
||||
* Uses whichever of MSC4155 and MSC4380 is available on the server; if neither is available, the toggle is disabled. If
|
||||
* both are available, the toggle will use MSC4380 to block invites.
|
||||
*/
|
||||
export const InviteRulesAccountSetting: FC = () => {
|
||||
const rules = useSettingValue("inviteRules");
|
||||
const settingsDisabled = SettingsStore.disabledMessage("inviteRules");
|
||||
const msc4155Rules = useSettingValue("inviteRules");
|
||||
const msc4380BlockInvites = useSettingValue("blockInvites");
|
||||
|
||||
const msc4155Disabled = SettingsStore.disabledMessage("inviteRules");
|
||||
const msc4380Disabled = SettingsStore.disabledMessage("blockInvites");
|
||||
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const onChange = useCallback(async (checked: boolean) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, {
|
||||
allBlocked: !checked,
|
||||
});
|
||||
} catch (ex) {
|
||||
logger.error(`Unable to set invite rules`, ex);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}, []);
|
||||
const onChange = useCallback(
|
||||
async (allowInvites: boolean) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
if (allowInvites) {
|
||||
// When allowing invites, clear the block setting on both bits of account data.
|
||||
await SettingsStore.setValue("blockInvites", null, SettingLevel.ACCOUNT, false);
|
||||
await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, { allBlocked: false });
|
||||
} else {
|
||||
// When blocking invites, prefer MSC4380 over MSC4155.
|
||||
if (!msc4380Disabled) {
|
||||
await SettingsStore.setValue("blockInvites", null, SettingLevel.ACCOUNT, true);
|
||||
} else if (!msc4155Disabled) {
|
||||
await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, { allBlocked: true });
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.error(`Unable to set invite rules`, ex);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[msc4155Disabled, msc4380Disabled, setBusy],
|
||||
);
|
||||
|
||||
const disabledMessage = msc4155Disabled && msc4380Disabled;
|
||||
const invitesBlocked = (!msc4155Disabled && msc4155Rules.allBlocked) || (!msc4380Disabled && msc4380BlockInvites);
|
||||
return (
|
||||
<Root className="mx_MediaPreviewAccountSetting_Form">
|
||||
<LabelledToggleSwitch
|
||||
className="mx_MediaPreviewAccountSetting_ToggleSwitch"
|
||||
label={_t("settings|invite_controls|default_label")}
|
||||
value={!rules.allBlocked}
|
||||
value={!invitesBlocked}
|
||||
onChange={onChange}
|
||||
tooltip={settingsDisabled}
|
||||
disabled={!!settingsDisabled || busy}
|
||||
tooltip={disabledMessage}
|
||||
disabled={!!disabledMessage || busy}
|
||||
/>
|
||||
</Root>
|
||||
);
|
||||
|
||||
@@ -50,6 +50,7 @@ import { SortingAlgorithm } from "../stores/room-list-v3/skip-list/sorters/index
|
||||
import MediaPreviewConfigController from "./controllers/MediaPreviewConfigController.ts";
|
||||
import InviteRulesConfigController from "./controllers/InviteRulesConfigController.ts";
|
||||
import { type ComputedInviteConfig } from "../@types/invite-rules.ts";
|
||||
import BlockInvitesConfigController from "./controllers/BlockInvitesConfigController.ts";
|
||||
|
||||
export const defaultWatchManager = new WatchManager();
|
||||
|
||||
@@ -368,6 +369,7 @@ export interface Settings {
|
||||
"Electron.enableContentProtection": IBaseSetting<boolean>;
|
||||
"mediaPreviewConfig": IBaseSetting<MediaPreviewConfig>;
|
||||
"inviteRules": IBaseSetting<ComputedInviteConfig>;
|
||||
"blockInvites": IBaseSetting<boolean>;
|
||||
"Developer.elementCallUrl": IBaseSetting<string>;
|
||||
}
|
||||
|
||||
@@ -458,6 +460,11 @@ export const SETTINGS: Settings = {
|
||||
// Contains server names
|
||||
shouldExportToRageshake: false,
|
||||
},
|
||||
"blockInvites": {
|
||||
controller: new BlockInvitesConfigController("blockInvites"),
|
||||
supportedLevels: [SettingLevel.ACCOUNT],
|
||||
default: false,
|
||||
},
|
||||
"feature_report_to_moderators": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Moderation,
|
||||
|
||||
36
src/settings/controllers/BlockInvitesConfigController.ts
Normal file
36
src/settings/controllers/BlockInvitesConfigController.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 { type SettingLevel } from "../SettingLevel.ts";
|
||||
import { MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE } from "../../@types/invite-rules.ts";
|
||||
import { _td } from "../../languageHandler.tsx";
|
||||
import ServerSupportUnstableFeatureController from "./ServerSupportUnstableFeatureController.ts";
|
||||
import { defaultWatchManager, type SettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Handles invite filtering rules provided by MSC4380.
|
||||
* This handler does not make use of the roomId parameter.
|
||||
*/
|
||||
export default class BlockInvitesConfigController extends ServerSupportUnstableFeatureController {
|
||||
public constructor(settingName: SettingKey) {
|
||||
super(settingName, defaultWatchManager, [["org.matrix.msc4380"]], undefined, _td("settings|not_supported"));
|
||||
}
|
||||
|
||||
public getValueOverride(_level: SettingLevel): boolean {
|
||||
const accountData = this.client?.getAccountData(MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE)?.getContent();
|
||||
return accountData?.default_action == "block";
|
||||
}
|
||||
|
||||
public async beforeChange(_level: SettingLevel, _roomId: string | null, newValue: boolean): Promise<boolean> {
|
||||
if (!this.client) {
|
||||
return false;
|
||||
}
|
||||
const newDefault = newValue ? "block" : "allow";
|
||||
await this.client.setAccountData(MSC4380_INVITE_RULES_ACCOUNT_DATA_TYPE, { default_action: newDefault });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user