Extract start DM logic to a helper file (#8317)

* Extract start DM logic to a helper file

* Fix incorrect import
pull/21833/head
Germain 2022-04-14 16:52:12 +01:00 committed by GitHub
parent 71ea88286d
commit a63449acdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 168 additions and 160 deletions

View File

@ -28,7 +28,8 @@ import InviteDialog from "./components/views/dialogs/InviteDialog";
import BaseAvatar from "./components/views/avatars/BaseAvatar"; import BaseAvatar from "./components/views/avatars/BaseAvatar";
import { mediaFromMxc } from "./customisations/Media"; import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialogTypes"; import { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialogTypes";
import { Member } from "./utils/direct-messages";
export interface IInviteResult { export interface IInviteResult {
states: CompletionStates; states: CompletionStates;

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -19,7 +19,6 @@ import classNames from 'classnames';
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { IInvite3PID } from "matrix-js-sdk/src/@types/requests";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
@ -30,11 +29,8 @@ import SdkConfig from "../../../SdkConfig";
import * as Email from "../../../email"; import * as Email from "../../../email";
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils"; import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils";
import { abbreviateUrl } from "../../../utils/UrlUtils"; import { abbreviateUrl } from "../../../utils/UrlUtils";
import dis from "../../../dispatcher/dispatcher";
import IdentityAuthClient from "../../../IdentityAuthClient"; import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
import { humanizeTime } from "../../../utils/humanize"; import { humanizeTime } from "../../../utils/humanize";
import createRoom, { canEncryptToAllUsers } from "../../../createRoom";
import { import {
IInviteResult, IInviteResult,
inviteMultipleToRoom, inviteMultipleToRoom,
@ -46,7 +42,6 @@ import RoomListStore from "../../../stores/room-list/RoomListStore";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { mediaFromMxc } from "../../../customisations/Media"; import { mediaFromMxc } from "../../../customisations/Media";
import { getAddressType } from "../../../UserAddress";
import BaseAvatar from '../avatars/BaseAvatar'; import BaseAvatar from '../avatars/BaseAvatar';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton'; import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import { compare, selectText } from '../../../utils/strings'; import { compare, selectText } from '../../../utils/strings';
@ -61,12 +56,12 @@ import CallHandler from "../../../CallHandler";
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier'; import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
import CopyableText from "../elements/CopyableText"; import CopyableText from "../elements/CopyableText";
import { ScreenName } from '../../../PosthogTrackers'; import { ScreenName } from '../../../PosthogTrackers';
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { privateShouldBeEncrypted } from "../../../utils/rooms"; import { DirectoryMember, IDMUserTileProps, Member, startDm, ThreepidMember } from "../../../utils/direct-messages";
import { findDMForUser } from "../../../utils/direct-messages"; import { AnyInviteKind, KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE } from './InviteDialogTypes';
import { AnyInviteKind, KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE, Member } from './InviteDialogTypes'; import Modal from '../../../Modal';
import dis from "../../../dispatcher/dispatcher";
// 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 */
@ -85,67 +80,6 @@ enum TabId {
DialPad = 'dialpad', DialPad = 'dialpad',
} }
class DirectoryMember extends Member {
private readonly _userId: string;
private readonly displayName?: string;
private readonly avatarUrl?: string;
// eslint-disable-next-line camelcase
constructor(userDirResult: { user_id: string, display_name?: string, avatar_url?: string }) {
super();
this._userId = userDirResult.user_id;
this.displayName = userDirResult.display_name;
this.avatarUrl = userDirResult.avatar_url;
}
// These next class members are for the Member interface
get name(): string {
return this.displayName || this._userId;
}
get userId(): string {
return this._userId;
}
getMxcAvatarUrl(): string {
return this.avatarUrl;
}
}
class ThreepidMember extends Member {
private readonly id: string;
constructor(id: string) {
super();
this.id = id;
}
// This is a getter that would be falsey on all other implementations. Until we have
// better type support in the react-sdk we can use this trick to determine the kind
// of 3PID we're dealing with, if any.
get isEmail(): boolean {
return this.id.includes('@');
}
// These next class members are for the Member interface
get name(): string {
return this.id;
}
get userId(): string {
return this.id;
}
getMxcAvatarUrl(): string {
return null;
}
}
interface IDMUserTileProps {
member: Member;
onRemove(member: Member): void;
}
class DMUserTile extends React.PureComponent<IDMUserTileProps> { class DMUserTile extends React.PureComponent<IDMUserTileProps> {
private onRemove = (e) => { private onRemove = (e) => {
// Stop the browser from highlighting text // Stop the browser from highlighting text
@ -630,72 +564,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
private startDm = async () => { private startDm = async () => {
this.setState({ busy: true }); this.setState({ busy: true });
const client = MatrixClientPeg.get();
const targets = this.convertFilter();
const targetIds = targets.map(t => t.userId);
// Check if there is already a DM with these people and reuse it if possible.
let existingRoom: Room;
if (targetIds.length === 1) {
existingRoom = findDMForUser(client, targetIds[0]);
} else {
existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds);
}
if (existingRoom) {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: existingRoom.roomId,
should_peek: false,
joining: false,
metricsTrigger: "MessageUser",
});
this.props.onFinished(true);
return;
}
const createRoomOptions = { inlineErrors: true } as any; // XXX: Type out `createRoomOptions`
if (privateShouldBeEncrypted()) {
// Check whether all users have uploaded device keys before.
// If so, enable encryption in the new room.
const has3PidMembers = targets.some(t => t instanceof ThreepidMember);
if (!has3PidMembers) {
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
if (allHaveDeviceKeys) {
createRoomOptions.encryption = true;
}
}
}
// Check if it's a traditional DM and create the room if required.
// TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
try { try {
const isSelf = targetIds.length === 1 && targetIds[0] === client.getUserId(); const cli = MatrixClientPeg.get();
if (targetIds.length === 1 && !isSelf) { const targets = this.convertFilter();
createRoomOptions.dmUserId = targetIds[0]; await startDm(cli, targets);
}
if (targetIds.length > 1) {
createRoomOptions.createOpts = targetIds.reduce(
(roomOptions, address) => {
const type = getAddressType(address);
if (type === 'email') {
const invite: IInvite3PID = {
id_server: client.getIdentityServerUrl(true),
medium: 'email',
address,
};
roomOptions.invite_3pid.push(invite);
} else if (type === 'mx-user-id') {
roomOptions.invite.push(address);
}
return roomOptions;
},
{ invite: [], invite_3pid: [] },
);
}
await createRoom(createRoomOptions);
this.props.onFinished(true); this.props.onFinished(true);
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);
@ -703,6 +575,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
busy: false, busy: false,
errorText: _t("We couldn't create your DM."), errorText: _t("We couldn't create your DM."),
}); });
} finally {
this.setState({ busy: false });
} }
}; };

View File

@ -22,26 +22,3 @@ export const KIND_INVITE = "invite";
export const KIND_CALL_TRANSFER = "call_transfer"; export const KIND_CALL_TRANSFER = "call_transfer";
export type AnyInviteKind = typeof KIND_INVITE | typeof KIND_DM | typeof KIND_CALL_TRANSFER; export type AnyInviteKind = typeof KIND_INVITE | typeof KIND_DM | typeof KIND_CALL_TRANSFER;
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
export abstract class Member {
/**
* The display name of this Member. For users this should be their profile's display
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
*/
public abstract get name(): string;
/**
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
* be the 3PID address (email).
*/
public abstract get userId(): string;
/**
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
*/
public abstract getMxcAvatarUrl(): string;
}

View File

@ -14,11 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IInvite3PID } from "matrix-js-sdk/src/@types/requests";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import createRoom, { canEncryptToAllUsers } from "../createRoom";
import { Action } from "../dispatcher/actions";
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
import { getAddressType } from "../UserAddress";
import DMRoomMap from "./DMRoomMap"; import DMRoomMap from "./DMRoomMap";
import { isJoinedOrNearlyJoined } from "./membership"; import { isJoinedOrNearlyJoined } from "./membership";
import dis from "../dispatcher/dispatcher";
import { privateShouldBeEncrypted } from "./rooms";
export function findDMForUser(client: MatrixClient, userId: string): Room { export function findDMForUser(client: MatrixClient, userId: string): Room {
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId); const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
@ -44,3 +51,152 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
return suitableDMRooms[0]; return suitableDMRooms[0];
} }
} }
export async function startDm(client: MatrixClient, targets: Member[]): Promise<void> {
const targetIds = targets.map(t => t.userId);
// Check if there is already a DM with these people and reuse it if possible.
let existingRoom: Room;
if (targetIds.length === 1) {
existingRoom = findDMForUser(client, targetIds[0]);
} else {
existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds);
}
if (existingRoom) {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: existingRoom.roomId,
should_peek: false,
joining: false,
metricsTrigger: "MessageUser",
});
return;
}
const createRoomOptions = { inlineErrors: true } as any; // XXX: Type out `createRoomOptions`
if (privateShouldBeEncrypted()) {
// Check whether all users have uploaded device keys before.
// If so, enable encryption in the new room.
const has3PidMembers = targets.some(t => t instanceof ThreepidMember);
if (!has3PidMembers) {
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
if (allHaveDeviceKeys) {
createRoomOptions.encryption = true;
}
}
}
// Check if it's a traditional DM and create the room if required.
// TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
const isSelf = targetIds.length === 1 && targetIds[0] === client.getUserId();
if (targetIds.length === 1 && !isSelf) {
createRoomOptions.dmUserId = targetIds[0];
}
if (targetIds.length > 1) {
createRoomOptions.createOpts = targetIds.reduce(
(roomOptions, address) => {
const type = getAddressType(address);
if (type === 'email') {
const invite: IInvite3PID = {
id_server: client.getIdentityServerUrl(true),
medium: 'email',
address,
};
roomOptions.invite_3pid.push(invite);
} else if (type === 'mx-user-id') {
roomOptions.invite.push(address);
}
return roomOptions;
},
{ invite: [], invite_3pid: [] },
);
}
await createRoom(createRoomOptions);
}
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
export abstract class Member {
/**
* The display name of this Member. For users this should be their profile's display
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
*/
public abstract get name(): string;
/**
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
* be the 3PID address (email).
*/
public abstract get userId(): string;
/**
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
*/
public abstract getMxcAvatarUrl(): string;
}
export class DirectoryMember extends Member {
private readonly _userId: string;
private readonly displayName?: string;
private readonly avatarUrl?: string;
// eslint-disable-next-line camelcase
constructor(userDirResult: { user_id: string, display_name?: string, avatar_url?: string }) {
super();
this._userId = userDirResult.user_id;
this.displayName = userDirResult.display_name;
this.avatarUrl = userDirResult.avatar_url;
}
// These next class members are for the Member interface
get name(): string {
return this.displayName || this._userId;
}
get userId(): string {
return this._userId;
}
getMxcAvatarUrl(): string {
return this.avatarUrl;
}
}
export class ThreepidMember extends Member {
private readonly id: string;
constructor(id: string) {
super();
this.id = id;
}
// This is a getter that would be falsey on all other implementations. Until we have
// better type support in the react-sdk we can use this trick to determine the kind
// of 3PID we're dealing with, if any.
get isEmail(): boolean {
return this.id.includes('@');
}
// These next class members are for the Member interface
get name(): string {
return this.id;
}
get userId(): string {
return this.id;
}
getMxcAvatarUrl(): string {
return null;
}
}
export interface IDMUserTileProps {
member: Member;
onRemove(member: Member): void;
}