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 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;
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue