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

* Extract start DM logic to a helper file

* Fix incorrect import
This commit is contained in:
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

@ -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;
}