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
This commit is contained in:
Richard van der Hoff 2025-08-12 18:41:58 +01:00 committed by GitHub
parent e880a866ed
commit 713f524948
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 65 additions and 32 deletions

View File

@ -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<IInviteResult> {
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<void> {
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,

View File

@ -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<CompletionStates> {
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);

View File

@ -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<void> {
const result = await inviteMultipleToRoom(client, roomId, userIds, { progressCallback });
const room = client.getRoom(roomId)!;
showAnyInviteErrors(result.states, room, result.inviter);
}

View File

@ -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({});
});
});