From 713f5249485809eccce8ea00e942f6a0328d9819 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:41:58 +0100 Subject: [PATCH] Update `MultiInviter` to take an options object (#30541) * Move `inviteUsersToRoom` to `RoomUpgrade` This method is only used in one place, uses only public methods, and is undocumented. Let's move it to the place where it is used, to simplify the API for `RoomInvite`. * Simplify `inviteUsersToRoom` `inviteMultipleToRoom` basically never throws, so this code was effectively unreachable. * Update MultiInviter to take an options object I'm going to add another option, so an options object is going to be more flexible. * Jump through the coverage hoop with another test --- src/RoomInvite.tsx | 33 +++++++----------------------- src/utils/MultiInviter.ts | 16 +++++++++++---- src/utils/RoomUpgrade.ts | 15 ++++++++++++-- test/unit-tests/RoomInvite-test.ts | 33 ++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 test/unit-tests/RoomInvite-test.ts diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx index 70aabed3de..feefdf7244 100644 --- a/src/RoomInvite.tsx +++ b/src/RoomInvite.tsx @@ -7,10 +7,9 @@ Please see LICENSE files in the repository root for full details. */ import React, { type ComponentProps } from "react"; -import { type Room, type MatrixEvent, type MatrixClient, type User, EventType } from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; +import { EventType, type MatrixClient, type MatrixEvent, type Room, type User } from "matrix-js-sdk/src/matrix"; -import MultiInviter, { type CompletionStates } from "./utils/MultiInviter"; +import MultiInviter, { type CompletionStates, type MultiInviterOptions } from "./utils/MultiInviter"; import Modal from "./Modal"; import { _t } from "./languageHandler"; import InviteDialog from "./components/views/dialogs/InviteDialog"; @@ -30,18 +29,20 @@ export interface IInviteResult { * * Simpler interface to {@link MultiInviter}. * + * Any failures are returned via the `states` in the result. + * * @param {string} roomId The ID of the room to invite to * @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids. - * @param {function} progressCallback optional callback, fired after each invite. + * @param options Options object. * @returns {Promise} Promise */ export async function inviteMultipleToRoom( client: MatrixClient, roomId: string, addresses: string[], - progressCallback?: () => void, + options: MultiInviterOptions = {}, ): Promise { - const inviter = new MultiInviter(client, roomId, progressCallback); + const inviter = new MultiInviter(client, roomId, options); return { states: await inviter.invite(addresses), inviter }; } @@ -89,26 +90,6 @@ export function isValid3pidInvite(event: MatrixEvent): boolean { return true; } -export function inviteUsersToRoom( - client: MatrixClient, - roomId: string, - userIds: string[], - progressCallback?: () => void, -): Promise { - return inviteMultipleToRoom(client, roomId, userIds, progressCallback) - .then((result) => { - const room = client.getRoom(roomId)!; - showAnyInviteErrors(result.states, room, result.inviter); - }) - .catch((err) => { - logger.error(err.stack); - Modal.createDialog(ErrorDialog, { - title: _t("invite|failed_title"), - description: err?.message ?? _t("invite|failed_generic"), - }); - }); -} - export function showAnyInviteErrors( states: CompletionStates, room: Room, diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts index 67bebe732d..a41967b5d3 100644 --- a/src/utils/MultiInviter.ts +++ b/src/utils/MultiInviter.ts @@ -40,6 +40,12 @@ const USER_ALREADY_JOINED = "IO.ELEMENT.ALREADY_JOINED"; const USER_ALREADY_INVITED = "IO.ELEMENT.ALREADY_INVITED"; const USER_BANNED = "IO.ELEMENT.BANNED"; +/** Options interface for {@link MultiInviter} */ +export interface MultiInviterOptions { + /** Optional callback, fired after each invite */ + progressCallback?: () => void; +} + /** * Invites multiple addresses to a room, handling rate limiting from the server */ @@ -53,12 +59,12 @@ export default class MultiInviter { /** * @param matrixClient the client of the logged in user * @param {string} roomId The ID of the room to invite to - * @param {function} progressCallback optional callback, fired after each invite. + * @param options Options object */ public constructor( private readonly matrixClient: MatrixClient, private roomId: string, - private readonly progressCallback?: () => void, + private readonly options: MultiInviterOptions = {}, ) {} public get fatal(): boolean { @@ -69,9 +75,11 @@ export default class MultiInviter { * Invite users to this room. This may only be called once per * instance of the class. * + * Any failures are returned via the {@link CompletionStates} in the result. + * * @param {array} addresses Array of addresses to invite * @param {string} reason Reason for inviting (optional) - * @returns {Promise} Resolved when all invitations in the queue are complete + * @returns {Promise} Resolved when all invitations in the queue are complete. */ public async invite(addresses: string[], reason?: string): Promise { if (this.addresses.length > 0) { @@ -230,7 +238,7 @@ export default class MultiInviter { delete this.errors[address]; resolve(); - this.progressCallback?.(); + this.options.progressCallback?.(); }) .catch((err) => { logger.error(err); diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index aa106f1a1a..e4ccfdb7d0 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type Room, EventType, ClientEvent, type MatrixClient } from "matrix-js-sdk/src/matrix"; +import { ClientEvent, EventType, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { logger } from "matrix-js-sdk/src/logger"; -import { inviteUsersToRoom } from "../RoomInvite"; +import { inviteMultipleToRoom, showAnyInviteErrors } from "../RoomInvite"; import Modal, { type IHandle } from "../Modal"; import { _t } from "../languageHandler"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; @@ -145,3 +145,14 @@ export async function upgradeRoom( spinnerModal?.close(); return newRoomId; } + +async function inviteUsersToRoom( + client: MatrixClient, + roomId: string, + userIds: string[], + progressCallback?: () => void, +): Promise { + const result = await inviteMultipleToRoom(client, roomId, userIds, { progressCallback }); + const room = client.getRoom(roomId)!; + showAnyInviteErrors(result.states, room, result.inviter); +} diff --git a/test/unit-tests/RoomInvite-test.ts b/test/unit-tests/RoomInvite-test.ts new file mode 100644 index 0000000000..32ef8dc73f --- /dev/null +++ b/test/unit-tests/RoomInvite-test.ts @@ -0,0 +1,33 @@ +/* +Copyright 2025 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { getMockClientWithEventEmitter } from "../test-utils"; +import { inviteMultipleToRoom } from "../../src/RoomInvite.tsx"; + +afterEach(() => { + jest.restoreAllMocks(); +}); + +describe("inviteMultipleToRoom", () => { + it("can be called wth no `options`", async () => { + const client = getMockClientWithEventEmitter({}); + const { states, inviter } = await inviteMultipleToRoom(client, "!room:id", []); + expect(states).toEqual({}); + + // @ts-ignore reference to private property + expect(inviter.options).toEqual({}); + }); +});