mirror of https://github.com/vector-im/riot-web
Extract start DM logic to a helper file (#8317)
* Extract start DM logic to a helper file * Fix incorrect importpull/21833/head
parent
71ea88286d
commit
a63449acdd
|
@ -28,7 +28,8 @@ import InviteDialog from "./components/views/dialogs/InviteDialog";
|
|||
import BaseAvatar from "./components/views/avatars/BaseAvatar";
|
||||
import { mediaFromMxc } from "./customisations/Media";
|
||||
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 {
|
||||
states: CompletionStates;
|
||||
|
|
|
@ -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");
|
||||
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 { Room } from "matrix-js-sdk/src/models/room";
|
||||
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 { _t, _td } from "../../../languageHandler";
|
||||
|
@ -30,11 +29,8 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import * as Email from "../../../email";
|
||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils";
|
||||
import { abbreviateUrl } from "../../../utils/UrlUtils";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import Modal from "../../../Modal";
|
||||
import { humanizeTime } from "../../../utils/humanize";
|
||||
import createRoom, { canEncryptToAllUsers } from "../../../createRoom";
|
||||
import {
|
||||
IInviteResult,
|
||||
inviteMultipleToRoom,
|
||||
|
@ -46,7 +42,6 @@ import RoomListStore from "../../../stores/room-list/RoomListStore";
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { getAddressType } from "../../../UserAddress";
|
||||
import BaseAvatar from '../avatars/BaseAvatar';
|
||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import { compare, selectText } from '../../../utils/strings';
|
||||
|
@ -61,12 +56,12 @@ import CallHandler from "../../../CallHandler";
|
|||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
import CopyableText from "../elements/CopyableText";
|
||||
import { ScreenName } from '../../../PosthogTrackers';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
import { findDMForUser } from "../../../utils/direct-messages";
|
||||
import { AnyInviteKind, KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE, Member } from './InviteDialogTypes';
|
||||
import { DirectoryMember, IDMUserTileProps, Member, startDm, ThreepidMember } from "../../../utils/direct-messages";
|
||||
import { AnyInviteKind, KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE } 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.
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -85,67 +80,6 @@ enum TabId {
|
|||
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> {
|
||||
private onRemove = (e) => {
|
||||
// Stop the browser from highlighting text
|
||||
|
@ -630,72 +564,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
|
||||
private startDm = async () => {
|
||||
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 {
|
||||
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);
|
||||
const cli = MatrixClientPeg.get();
|
||||
const targets = this.convertFilter();
|
||||
await startDm(cli, targets);
|
||||
this.props.onFinished(true);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
@ -703,6 +575,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
busy: false,
|
||||
errorText: _t("We couldn't create your DM."),
|
||||
});
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -22,26 +22,3 @@ export const KIND_INVITE = "invite";
|
|||
export const KIND_CALL_TRANSFER = "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;
|
||||
}
|
||||
|
|
|
@ -14,11 +14,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IInvite3PID } from "matrix-js-sdk/src/@types/requests";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
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 { isJoinedOrNearlyJoined } from "./membership";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { privateShouldBeEncrypted } from "./rooms";
|
||||
|
||||
export function findDMForUser(client: MatrixClient, userId: string): Room {
|
||||
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
|
@ -44,3 +51,152 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
|
|||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue