InviteDialog: remove broken checks for unknown users (#33026)
* InviteDialog: remove broken checks for unknown users Per https://github.com/element-hq/element-web/issues/33020, this doesn't actually work, and if we were to bring it back, we'd design it differently. * Remove now-unused `UserProfilesStore.getProfileLookupError` This was only used in `InviteDialog.checkProfileAndStartDm`, which has now been removed.
This commit is contained in:
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef, type JSX, type ReactNode, type SyntheticEvent } from "react";
|
import React, { createRef, type JSX, type ReactNode, type SyntheticEvent } from "react";
|
||||||
import { EventType, MatrixError, type Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
import { EventType, type Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||||
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
@@ -60,8 +60,6 @@ import Modal from "../../../Modal";
|
|||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||||
import { type NonEmptyArray } from "../../../@types/common";
|
import { type NonEmptyArray } from "../../../@types/common";
|
||||||
import { UNKNOWN_PROFILE_ERRORS } from "../../../utils/MultiInviter";
|
|
||||||
import AskInviteAnywayDialog, { type UnknownProfiles } from "./AskInviteAnywayDialog";
|
|
||||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||||
import { type UserProfilesStore } from "../../../stores/UserProfilesStore";
|
import { type UserProfilesStore } from "../../../stores/UserProfilesStore";
|
||||||
import InviteProgressBody from "./InviteProgressBody.tsx";
|
import InviteProgressBody from "./InviteProgressBody.tsx";
|
||||||
@@ -69,30 +67,6 @@ import InviteProgressBody from "./InviteProgressBody.tsx";
|
|||||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
const extractTargetUnknownProfiles = async (
|
|
||||||
targets: Member[],
|
|
||||||
profilesStores: UserProfilesStore,
|
|
||||||
): Promise<UnknownProfiles> => {
|
|
||||||
const directoryMembers = targets.filter((t): t is DirectoryMember => t instanceof DirectoryMember);
|
|
||||||
await Promise.all(directoryMembers.map((t) => profilesStores.getOrFetchProfile(t.userId)));
|
|
||||||
return directoryMembers.reduce<UnknownProfiles>((unknownProfiles: UnknownProfiles, target: DirectoryMember) => {
|
|
||||||
const lookupError = profilesStores.getProfileLookupError(target.userId);
|
|
||||||
|
|
||||||
if (
|
|
||||||
lookupError instanceof MatrixError &&
|
|
||||||
lookupError.errcode &&
|
|
||||||
UNKNOWN_PROFILE_ERRORS.includes(lookupError.errcode)
|
|
||||||
) {
|
|
||||||
unknownProfiles.push({
|
|
||||||
userId: target.userId,
|
|
||||||
errorText: lookupError.data.error || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return unknownProfiles;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Result {
|
interface Result {
|
||||||
userId: string;
|
userId: string;
|
||||||
user: Member;
|
user: Member;
|
||||||
@@ -467,26 +441,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
|||||||
return newTargets;
|
return newTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there are unknown profiles if promptBeforeInviteUnknownUsers setting is enabled.
|
|
||||||
* If so show the "invite anyway?" dialog. Otherwise directly create the DM local room.
|
|
||||||
*/
|
|
||||||
private checkProfileAndStartDm = async (): Promise<void> => {
|
|
||||||
this.setBusy(true);
|
|
||||||
const targets = this.convertFilter();
|
|
||||||
|
|
||||||
if (SettingsStore.getValue("promptBeforeInviteUnknownUsers")) {
|
|
||||||
const unknownProfileUsers = await extractTargetUnknownProfiles(targets, this.profilesStore);
|
|
||||||
|
|
||||||
if (unknownProfileUsers.length) {
|
|
||||||
this.showAskInviteAnywayDialog(unknownProfileUsers);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.startDm();
|
|
||||||
};
|
|
||||||
|
|
||||||
private startDm = async (): Promise<void> => {
|
private startDm = async (): Promise<void> => {
|
||||||
this.setBusy(true);
|
this.setBusy(true);
|
||||||
|
|
||||||
@@ -510,19 +464,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private showAskInviteAnywayDialog(unknownProfileUsers: { userId: string; errorText: string }[]): void {
|
|
||||||
Modal.createDialog(AskInviteAnywayDialog, {
|
|
||||||
unknownProfileUsers,
|
|
||||||
onInviteAnyways: () => this.startDm(),
|
|
||||||
onGiveUp: () => {
|
|
||||||
this.setBusy(false);
|
|
||||||
},
|
|
||||||
description: _t("invite|ask_anyway_description"),
|
|
||||||
inviteNeverWarnLabel: _t("invite|ask_anyway_never_warn_label"),
|
|
||||||
inviteLabel: _t("invite|ask_anyway_label"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private inviteUsers = async (): Promise<void> => {
|
private inviteUsers = async (): Promise<void> => {
|
||||||
if (this.props.kind !== InviteKind.Invite) return;
|
if (this.props.kind !== InviteKind.Invite) return;
|
||||||
this.setState({ busy: true });
|
this.setState({ busy: true });
|
||||||
@@ -1283,7 +1224,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
|||||||
}
|
}
|
||||||
|
|
||||||
buttonText = _t("action|go");
|
buttonText = _t("action|go");
|
||||||
goButtonFn = this.checkProfileAndStartDm;
|
goButtonFn = this.startDm;
|
||||||
} else if (this.props.kind === InviteKind.Invite) {
|
} else if (this.props.kind === InviteKind.Invite) {
|
||||||
const roomId = this.props.roomId;
|
const roomId = this.props.roomId;
|
||||||
const room = MatrixClientPeg.get()?.getRoom(roomId);
|
const room = MatrixClientPeg.get()?.getRoom(roomId);
|
||||||
|
|||||||
@@ -1362,9 +1362,6 @@
|
|||||||
"impossible_dialog_title": "Integrations not allowed"
|
"impossible_dialog_title": "Integrations not allowed"
|
||||||
},
|
},
|
||||||
"invite": {
|
"invite": {
|
||||||
"ask_anyway_description": "Unable to find profiles for the Matrix IDs listed below - would you like to start a DM anyway?",
|
|
||||||
"ask_anyway_label": "Start DM anyway",
|
|
||||||
"ask_anyway_never_warn_label": "Start DM anyway and never warn me again",
|
|
||||||
"email_caption": "Invite by email",
|
"email_caption": "Invite by email",
|
||||||
"email_limit_one": "Invites by email can only be sent one at a time",
|
"email_limit_one": "Invites by email can only be sent one at a time",
|
||||||
"email_use_default_is": "Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.",
|
"email_use_default_is": "Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||||||
import {
|
import {
|
||||||
type IMatrixProfile,
|
type IMatrixProfile,
|
||||||
type MatrixClient,
|
type MatrixClient,
|
||||||
MatrixError,
|
|
||||||
type MatrixEvent,
|
type MatrixEvent,
|
||||||
type RoomMember,
|
type RoomMember,
|
||||||
RoomMemberEvent,
|
RoomMemberEvent,
|
||||||
@@ -33,7 +32,6 @@ interface GetOptions {
|
|||||||
*/
|
*/
|
||||||
export class UserProfilesStore {
|
export class UserProfilesStore {
|
||||||
private profiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
private profiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||||
private profileLookupErrors = new LruCache<string, MatrixError>(cacheSize);
|
|
||||||
private knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
private knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||||
|
|
||||||
public constructor(private client: MatrixClient) {
|
public constructor(private client: MatrixClient) {
|
||||||
@@ -69,16 +67,6 @@ export class UserProfilesStore {
|
|||||||
return this.fetchProfile(userId, options);
|
return this.fetchProfile(userId, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a profile lookup error.
|
|
||||||
*
|
|
||||||
* @param userId - User Id for which to get the lookup error
|
|
||||||
* @returns The lookup error or undefined if there was no error or the profile was not fetched.
|
|
||||||
*/
|
|
||||||
public getProfileLookupError(userId: string): MatrixError | undefined {
|
|
||||||
return this.profileLookupErrors.get(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously get a profile from known users from the store cache.
|
* Synchronously get a profile from known users from the store cache.
|
||||||
* Known user means that at least one shared room with the user exists.
|
* Known user means that at least one shared room with the user exists.
|
||||||
@@ -129,7 +117,6 @@ export class UserProfilesStore {
|
|||||||
|
|
||||||
public flush(): void {
|
public flush(): void {
|
||||||
this.profiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
this.profiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||||
this.profileLookupErrors = new LruCache<string, MatrixError>(cacheSize);
|
|
||||||
this.knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
this.knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,18 +127,11 @@ export class UserProfilesStore {
|
|||||||
* @returns The profile information or null on errors
|
* @returns The profile information or null on errors
|
||||||
*/
|
*/
|
||||||
private async fetchProfileFromApi(userId: string, options?: GetOptions): Promise<IMatrixProfile | null> {
|
private async fetchProfileFromApi(userId: string, options?: GetOptions): Promise<IMatrixProfile | null> {
|
||||||
// invalidate cached profile errors
|
|
||||||
this.profileLookupErrors.delete(userId);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return (await this.client.getProfileInfo(userId)) ?? null;
|
return (await this.client.getProfileInfo(userId)) ?? null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`Error retrieving profile for userId ${userId}`, e);
|
logger.warn(`Error retrieving profile for userId ${userId}`, e);
|
||||||
|
|
||||||
if (e instanceof MatrixError) {
|
|
||||||
this.profileLookupErrors.set(userId, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.shouldThrow) {
|
if (options?.shouldThrow) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { type IConfigOptions } from "../../../../../src/IConfigOptions";
|
|||||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||||
import { type IProfileInfo } from "../../../../../src/hooks/useProfileInfo";
|
import { type IProfileInfo } from "../../../../../src/hooks/useProfileInfo";
|
||||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
|
import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
|
||||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
|
||||||
|
|
||||||
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
|
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
|
||||||
jest.mock("../../../../../src/IdentityAuthClient", () =>
|
jest.mock("../../../../../src/IdentityAuthClient", () =>
|
||||||
@@ -120,7 +119,7 @@ describe("InviteDialog", () => {
|
|||||||
if (userId === bobId) return bobProfileInfo;
|
if (userId === bobId) return bobProfileInfo;
|
||||||
|
|
||||||
throw new MatrixError({
|
throw new MatrixError({
|
||||||
errcode: "M_NOT_FOUND",
|
errcode: "M_UNKNOWN",
|
||||||
error: "Profile not found",
|
error: "Profile not found",
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -418,55 +417,8 @@ describe("InviteDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when inviting a user with an unknown profile", () => {
|
describe("when inviting a user with an unknown profile", () => {
|
||||||
beforeEach(async () => {
|
|
||||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
|
||||||
await enterIntoSearchField(carolId);
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
|
||||||
// Wait for the »invite anyway« modal to show up
|
|
||||||
await screen.findByText("The following users may not exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not start the DM", () => {
|
|
||||||
expect(startDmOnFirstMessage).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show the »invite anyway« dialog if the profile is not available", () => {
|
|
||||||
expect(screen.getByText("The following users may not exist")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(`${carolId}: Profile not found`)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when clicking »Start DM anyway«", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: "Start DM anyway" }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should start the DM", () => {
|
|
||||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
|
||||||
new DirectoryMember({
|
|
||||||
user_id: carolId,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when clicking »Close«", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
mocked(startDmOnFirstMessage).mockClear();
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: "Close" }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not start the DM", () => {
|
|
||||||
expect(startDmOnFirstMessage).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when inviting a user with an unknown profile and »promptBeforeInviteUnknownUsers« setting = false", () => {
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mocked(startDmOnFirstMessage).mockClear();
|
mocked(startDmOnFirstMessage).mockClear();
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
|
||||||
(settingName) => settingName !== "promptBeforeInviteUnknownUsers",
|
|
||||||
);
|
|
||||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||||
await enterIntoSearchField(carolId);
|
await enterIntoSearchField(carolId);
|
||||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
||||||
@@ -474,10 +426,6 @@ describe("InviteDialog", () => {
|
|||||||
await sleep(100);
|
await sleep(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show the »invite anyway« dialog", () => {
|
|
||||||
expect(screen.queryByText("The following users may not exist")).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should start the DM directly", () => {
|
it("should start the DM directly", () => {
|
||||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
||||||
new DirectoryMember({
|
new DirectoryMember({
|
||||||
|
|||||||
@@ -86,11 +86,6 @@ describe("UserProfilesStore", () => {
|
|||||||
expect(profile).toBeNull();
|
expect(profile).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should cache the error and result", () => {
|
|
||||||
expect(userProfilesStore.getProfile(userIdDoesNotExist)).toBeNull();
|
|
||||||
expect(userProfilesStore.getProfileLookupError(userIdDoesNotExist)).toBe(userDoesNotExistError);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the profile does not exist and fetching it again", () => {
|
describe("when the profile does not exist and fetching it again", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockClient.getProfileInfo.mockResolvedValue(user1Profile);
|
mockClient.getProfileInfo.mockResolvedValue(user1Profile);
|
||||||
@@ -100,10 +95,6 @@ describe("UserProfilesStore", () => {
|
|||||||
it("should return the profile", () => {
|
it("should return the profile", () => {
|
||||||
expect(profile).toBe(user1Profile);
|
expect(profile).toBe(user1Profile);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should clear the error", () => {
|
|
||||||
expect(userProfilesStore.getProfileLookupError(userIdDoesNotExist)).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -119,22 +110,6 @@ describe("UserProfilesStore", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getProfileLookupError", () => {
|
|
||||||
it("should return undefined if a profile was not fetched", () => {
|
|
||||||
expect(userProfilesStore.getProfileLookupError(user1Id)).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return undefined if a profile was successfully fetched", async () => {
|
|
||||||
await userProfilesStore.fetchProfile(user1Id);
|
|
||||||
expect(userProfilesStore.getProfileLookupError(user1Id)).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the error if there was one", async () => {
|
|
||||||
await userProfilesStore.fetchProfile(userIdDoesNotExist);
|
|
||||||
expect(userProfilesStore.getProfileLookupError(userIdDoesNotExist)).toBe(userDoesNotExistError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getOnlyKnownProfile should return undefined if the profile was not fetched", () => {
|
it("getOnlyKnownProfile should return undefined if the profile was not fetched", () => {
|
||||||
expect(userProfilesStore.getOnlyKnownProfile(user1Id)).toBeUndefined();
|
expect(userProfilesStore.getOnlyKnownProfile(user1Id)).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -195,7 +170,6 @@ describe("UserProfilesStore", () => {
|
|||||||
|
|
||||||
expect(userProfilesStore.getProfile(user1Id)).toBeUndefined();
|
expect(userProfilesStore.getProfile(user1Id)).toBeUndefined();
|
||||||
expect(userProfilesStore.getOnlyKnownProfile(user1Id)).toBeUndefined();
|
expect(userProfilesStore.getOnlyKnownProfile(user1Id)).toBeUndefined();
|
||||||
expect(userProfilesStore.getProfileLookupError(userIdDoesNotExist)).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user