Prepare utils for local rooms (#9084)

* Prepare utils for local rooms

* Split up direct-messages module
This commit is contained in:
Michael Weimann 2022-07-25 10:17:40 +02:00 committed by GitHub
parent 0a8adb6bfe
commit c5eaeafe8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1016 additions and 220 deletions

View file

@ -58,9 +58,9 @@ import IncomingCallToast, { getIncomingCallToastKey } from './toasts/IncomingCal
import ToastStore from './stores/ToastStore';
import Resend from './Resend';
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { findDMForUser } from "./utils/direct-messages";
import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes";
import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
import { findDMForUser } from './utils/dm/findDMForUser';
export const PROTOCOL_PSTN = 'm.protocol.pstn';
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';

View file

@ -23,7 +23,7 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import DMRoomMap from "./utils/DMRoomMap";
import CallHandler from './CallHandler';
import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
import { findDMForUser } from "./utils/direct-messages";
import { findDMForUser } from './utils/dm/findDMForUser';
// Functions for mapping virtual users & rooms. Currently the only lookup
// is sip virtual: there could be others in the future.

View file

@ -62,10 +62,11 @@ import CopyableText from "../elements/CopyableText";
import { ScreenName } from '../../../PosthogTrackers';
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { DirectoryMember, IDMUserTileProps, Member, startDm, ThreepidMember } from "../../../utils/direct-messages";
import { DirectoryMember, IDMUserTileProps, Member, 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";
import { startDm } from '../../../utils/dm/startDm';
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */

View file

@ -70,7 +70,7 @@ import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sor
import { RoomViewStore } from "../../../../stores/RoomViewStore";
import { getMetaSpaceName } from "../../../../stores/spaces";
import SpaceStore from "../../../../stores/spaces/SpaceStore";
import { DirectoryMember, Member, startDm } from "../../../../utils/direct-messages";
import { DirectoryMember, Member } from "../../../../utils/direct-messages";
import DMRoomMap from "../../../../utils/DMRoomMap";
import { makeUserPermalink } from "../../../../utils/permalinks/Permalinks";
import { buildActivityScores, buildMemberScores, compareMembers } from "../../../../utils/SortMembers";
@ -92,6 +92,7 @@ import { RoomResultContextMenus } from "./RoomResultContextMenus";
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
import { TooltipOption } from "./TooltipOption";
import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom";
import { startDm } from "../../../../utils/dm/startDm";
const MAX_RECENT_SEARCHES = 10;
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons

View file

@ -78,8 +78,8 @@ import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStor
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
import PosthogTrackers from "../../../PosthogTrackers";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { findDMForUser } from "../../../utils/direct-messages";
import { privateShouldBeEncrypted } from "../../../utils/rooms";
import { findDMForUser } from '../../../utils/dm/findDMForUser';
export interface IDevice {
deviceId: string;

View file

@ -42,7 +42,7 @@ import { Action } from "./dispatcher/actions";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import Spinner from "./components/views/elements/Spinner";
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { findDMForUser } from "./utils/direct-messages";
import { findDMForUser } from "./utils/dm/findDMForUser";
import { privateShouldBeEncrypted } from "./utils/rooms";
import { waitForMember } from "./utils/membership";
import { PreferredRoomVersions } from "./utils/PreferredRoomVersions";

View file

@ -14,54 +14,26 @@ 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 { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";
import createRoom, { canEncryptToAllUsers } from "../createRoom";
import { 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 { LocalRoom, LocalRoomState } from "../models/LocalRoom";
import { waitForRoomReadyAndApplyAfterCreateCallbacks } from "./local-room";
import { findDMRoom } from "./dm/findDMRoom";
import { privateShouldBeEncrypted } from "./rooms";
import { createDmLocalRoom } from "./dm/createDmLocalRoom";
import { startDm } from "./dm/startDm";
export function findDMForUser(client: MatrixClient, userId: string): Room {
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
const rooms = roomIds.map(id => client.getRoom(id));
const suitableDMRooms = rooms.filter(r => {
// Validate that we are joined and the other person is also joined. We'll also make sure
// that the room also looks like a DM (until we have canonical DMs to tell us). For now,
// a DM is a room of two people that contains those two people exactly. This does mean
// that bots, assistants, etc will ruin a room's DM-ness, though this is a problem for
// canonical DMs to solve.
if (r && r.getMyMembership() === "join") {
const members = r.currentState.getMembers();
const joinedMembers = members.filter(m => isJoinedOrNearlyJoined(m.membership));
const otherMember = joinedMembers.find(m => m.userId === userId);
return otherMember && joinedMembers.length === 2;
}
return false;
}).sort((r1, r2) => {
return r2.getLastActiveTimestamp() -
r1.getLastActiveTimestamp();
});
if (suitableDMRooms.length) {
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);
}
export async function startDmOnFirstMessage(
client: MatrixClient,
targets: Member[],
): Promise<Room> {
const existingRoom = findDMRoom(client, targets);
if (existingRoom) {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
@ -70,51 +42,47 @@ export async function startDm(client: MatrixClient, targets: Member[]): Promise<
joining: false,
metricsTrigger: "MessageUser",
});
return existingRoom;
}
const room = await createDmLocalRoom(client, targets);
dis.dispatch({
action: Action.ViewRoom,
room_id: room.roomId,
joining: false,
targets,
});
return room;
}
/**
* Starts a DM based on a local room.
*
* @async
* @param {MatrixClient} client
* @param {LocalRoom} localRoom
* @returns {Promise<string | void>} Resolves to the created room id
*/
export async function createRoomFromLocalRoom(client: MatrixClient, localRoom: LocalRoom): Promise<string | void> {
if (!localRoom.isNew) {
// This action only makes sense for new local rooms.
return;
}
const createRoomOptions = { inlineErrors: true } as any; // XXX: Type out `createRoomOptions`
localRoom.state = LocalRoomState.CREATING;
client.emit(ClientEvent.Room, localRoom);
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);
return startDm(client, localRoom.targets, false).then(
(roomId) => {
localRoom.actualRoomId = roomId;
return waitForRoomReadyAndApplyAfterCreateCallbacks(client, localRoom);
},
() => {
logger.warn(`Error creating DM for local room ${localRoom.roomId}`);
localRoom.state = LocalRoomState.ERROR;
client.emit(ClientEvent.Room, localRoom);
},
);
}
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
@ -200,3 +168,28 @@ export interface IDMUserTileProps {
member: Member;
onRemove(member: Member): void;
}
/**
* Detects whether a room should be encrypted.
*
* @async
* @param {MatrixClient} client
* @param {Member[]} targets The members to which run the check against
* @returns {Promise<boolean>}
*/
export async function determineCreateRoomEncryptionOption(client: MatrixClient, targets: Member[]): Promise<boolean> {
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 targetIds = targets.map(t => t.userId);
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
if (allHaveDeviceKeys) {
return true;
}
}
}
return false;
}

View file

@ -0,0 +1,123 @@
/*
Copyright 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MEGOLM_ALGORITHM } from "matrix-js-sdk/src/crypto/olmlib";
import { EventType, KNOWN_SAFE_ROOM_VERSION, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../src/models/LocalRoom";
import { determineCreateRoomEncryptionOption, Member } from "../../../src/utils/direct-messages";
/**
* Create a DM local room. This room will not be send to the server and only exists inside the client.
* It sets up the local room with some artificial state events
* so that can be used in most components instead of a real room.
*
* @async
* @param {MatrixClient} client
* @param {Member[]} targets DM partners
* @returns {Promise<LocalRoom>} Resolves to the new local room
*/
export async function createDmLocalRoom(
client: MatrixClient,
targets: Member[],
): Promise<LocalRoom> {
const userId = client.getUserId();
const localRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + client.makeTxnId(), client, userId);
const events = [];
events.push(new MatrixEvent({
event_id: `~${localRoom.roomId}:${client.makeTxnId()}`,
type: EventType.RoomCreate,
content: {
creator: userId,
room_version: KNOWN_SAFE_ROOM_VERSION,
},
state_key: "",
user_id: userId,
sender: userId,
room_id: localRoom.roomId,
origin_server_ts: Date.now(),
}));
if (await determineCreateRoomEncryptionOption(client, targets)) {
localRoom.encrypted = true;
events.push(
new MatrixEvent({
event_id: `~${localRoom.roomId}:${client.makeTxnId()}`,
type: EventType.RoomEncryption,
content: {
algorithm: MEGOLM_ALGORITHM,
},
user_id: userId,
sender: userId,
state_key: "",
room_id: localRoom.roomId,
origin_server_ts: Date.now(),
}),
);
}
events.push(new MatrixEvent({
event_id: `~${localRoom.roomId}:${client.makeTxnId()}`,
type: EventType.RoomMember,
content: {
displayname: userId,
membership: "join",
},
state_key: userId,
user_id: userId,
sender: userId,
room_id: localRoom.roomId,
}));
targets.forEach((target: Member) => {
events.push(new MatrixEvent({
event_id: `~${localRoom.roomId}:${client.makeTxnId()}`,
type: EventType.RoomMember,
content: {
displayname: target.name,
avatar_url: target.getMxcAvatarUrl(),
membership: "invite",
isDirect: true,
},
state_key: target.userId,
sender: userId,
room_id: localRoom.roomId,
}));
events.push(new MatrixEvent({
event_id: `~${localRoom.roomId}:${client.makeTxnId()}`,
type: EventType.RoomMember,
content: {
displayname: target.name,
avatar_url: target.getMxcAvatarUrl(),
membership: "join",
},
state_key: target.userId,
sender: target.userId,
room_id: localRoom.roomId,
}));
});
localRoom.targets = targets;
localRoom.updateMyMembership("join");
localRoom.addLiveEvents(events);
localRoom.currentState.setStateEvents(events);
localRoom.name = localRoom.getDefaultRoomName(client.getUserId());
client.store.storeRoom(localRoom);
return localRoom;
}

View file

@ -0,0 +1,55 @@
/*
Copyright 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import DMRoomMap from "../DMRoomMap";
import { isLocalRoom } from "../localRoom/isLocalRoom";
import { isJoinedOrNearlyJoined } from "../membership";
/**
* Tries to find a DM room with a specific user.
*
* @param {MatrixClient} client
* @param {string} userId ID of the user to find the DM for
* @returns {Room} Room if found
*/
export function findDMForUser(client: MatrixClient, userId: string): Room {
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
const rooms = roomIds.map(id => client.getRoom(id));
const suitableDMRooms = rooms.filter(r => {
// Validate that we are joined and the other person is also joined. We'll also make sure
// that the room also looks like a DM (until we have canonical DMs to tell us). For now,
// a DM is a room of two people that contains those two people exactly. This does mean
// that bots, assistants, etc will ruin a room's DM-ness, though this is a problem for
// canonical DMs to solve.
if (r && r.getMyMembership() === "join") {
if (isLocalRoom(r)) return false;
const members = r.currentState.getMembers();
const joinedMembers = members.filter(m => isJoinedOrNearlyJoined(m.membership));
const otherMember = joinedMembers.find(m => m.userId === userId);
return otherMember && joinedMembers.length === 2;
}
return false;
}).sort((r1, r2) => {
return r2.getLastActiveTimestamp() -
r1.getLastActiveTimestamp();
});
if (suitableDMRooms.length) {
return suitableDMRooms[0];
}
}

View file

@ -0,0 +1,42 @@
/*
Copyright 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { Member } from "../direct-messages";
import DMRoomMap from "../DMRoomMap";
import { findDMForUser } from "./findDMForUser";
/**
* Tries to find a DM room with some other users.
*
* @param {MatrixClient} client
* @param {Member[]} targets The Members to try to find the room for
* @returns {Room | null} Resolved so the room if found, else null
*/
export function findDMRoom(client: MatrixClient, targets: Member[]): Room | null {
const targetIds = targets.map(t => t.userId);
let existingRoom: Room;
if (targetIds.length === 1) {
existingRoom = findDMForUser(client, targetIds[0]);
} else {
existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds);
}
if (existingRoom) {
return existingRoom;
}
return null;
}

90
src/utils/dm/startDm.ts Normal file
View file

@ -0,0 +1,90 @@
/*
Copyright 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { IInvite3PID, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { Action } from "../../dispatcher/actions";
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import { determineCreateRoomEncryptionOption, Member } from "../direct-messages";
import DMRoomMap from "../DMRoomMap";
import { isLocalRoom } from "../localRoom/isLocalRoom";
import { findDMForUser } from "./findDMForUser";
import dis from "../../dispatcher/dispatcher";
import { getAddressType } from "../../UserAddress";
import createRoom from "../../createRoom";
/**
* Start a DM.
*
* @returns {Promise<string | null} Resolves to the room id.
*/
export async function startDm(client: MatrixClient, targets: Member[], showSpinner = true): Promise<string | null> {
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 && !isLocalRoom(existingRoom)) {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: existingRoom.roomId,
should_peek: false,
joining: false,
metricsTrigger: "MessageUser",
});
return Promise.resolve(existingRoom.roomId);
}
const createRoomOptions = { inlineErrors: true } as any; // XXX: Type out `createRoomOptions`
if (await determineCreateRoomEncryptionOption(client, targets)) {
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: [] },
);
}
createRoomOptions.spinner = showSpinner;
return createRoom(createRoomOptions);
}

View file

@ -15,12 +15,13 @@ limitations under the License.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { ClientEvent, EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
import defaultDispatcher from "../dispatcher/dispatcher";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { LocalRoom, LocalRoomState, LOCAL_ROOM_ID_PREFIX } from "../models/LocalRoom";
import * as thisModule from "./local-room";
import { LocalRoom, LocalRoomState } from "../models/LocalRoom";
import { isLocalRoom } from "./localRoom/isLocalRoom";
import { isRoomReady } from "./localRoom/isRoomReady";
/**
* Does a room action:
@ -40,7 +41,7 @@ export async function doMaybeLocalRoomAction<T>(
fn: (actualRoomId: string) => Promise<T>,
client?: MatrixClient,
): Promise<T> {
if (roomId.startsWith(LOCAL_ROOM_ID_PREFIX)) {
if (isLocalRoom(roomId)) {
client = client ?? MatrixClientPeg.get();
const room = client.getRoom(roomId) as LocalRoom;
@ -62,34 +63,6 @@ export async function doMaybeLocalRoomAction<T>(
return fn(roomId);
}
/**
* Tests whether a room created based on a local room is ready.
*/
export function isRoomReady(
client: MatrixClient,
localRoom: LocalRoom,
): boolean {
// not ready if no actual room id exists
if (!localRoom.actualRoomId) return false;
const room = client.getRoom(localRoom.actualRoomId);
// not ready if the room does not exist
if (!room) return false;
// not ready if not all members joined/invited
if (room.getInvitedAndJoinedMemberCount() !== 1 + localRoom.targets?.length) return false;
const roomHistoryVisibilityEvents = room.currentState.getStateEvents(EventType.RoomHistoryVisibility);
// not ready if the room history has not been configured
if (roomHistoryVisibilityEvents.length === 0) return false;
const roomEncryptionEvents = room.currentState.getStateEvents(EventType.RoomEncryption);
// not ready if encryption has not been configured (applies only to encrypted rooms)
if (localRoom.encrypted === true && roomEncryptionEvents.length === 0) return false;
return true;
}
/**
* Waits until a room is ready and then applies the after-create local room callbacks.
* Also implements a stopgap timeout after that a room is assumed to be ready.
@ -104,7 +77,7 @@ export async function waitForRoomReadyAndApplyAfterCreateCallbacks(
client: MatrixClient,
localRoom: LocalRoom,
): Promise<string> {
if (thisModule.isRoomReady(client, localRoom)) {
if (isRoomReady(client, localRoom)) {
return applyAfterCreateCallbacks(localRoom, localRoom.actualRoomId).then(() => {
localRoom.state = LocalRoomState.CREATED;
client.emit(ClientEvent.Room, localRoom);
@ -130,7 +103,7 @@ export async function waitForRoomReadyAndApplyAfterCreateCallbacks(
};
const checkRoomStateIntervalHandle = setInterval(() => {
if (thisModule.isRoomReady(client, localRoom)) finish();
if (isRoomReady(client, localRoom)) finish();
}, 500);
const stopgapTimeoutHandle = setTimeout(stopgapFinish, 5000);
});

View file

@ -0,0 +1,47 @@
/*
Copyright 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
import { LocalRoom } from "../../models/LocalRoom";
/**
* Tests whether a room created based on a local room is ready.
*/
export function isRoomReady(
client: MatrixClient,
localRoom: LocalRoom,
): boolean {
// not ready if no actual room id exists
if (!localRoom.actualRoomId) return false;
const room = client.getRoom(localRoom.actualRoomId);
// not ready if the room does not exist
if (!room) return false;
// not ready if not all members joined/invited
if (room.getInvitedAndJoinedMemberCount() !== 1 + localRoom.targets?.length) return false;
const roomHistoryVisibilityEvents = room.currentState.getStateEvents(EventType.RoomHistoryVisibility);
// not ready if the room history has not been configured
if (roomHistoryVisibilityEvents.length === 0) return false;
const roomEncryptionEvents = room.currentState.getStateEvents(EventType.RoomEncryption);
// not ready if encryption has not been configured (applies only to encrypted rooms)
if (localRoom.encrypted === true && roomEncryptionEvents.length === 0) return false;
return true;
}

View file

@ -28,7 +28,7 @@ import { IDevice } from "./components/views/right_panel/UserInfo";
import ManualDeviceKeyVerificationDialog from "./components/views/dialogs/ManualDeviceKeyVerificationDialog";
import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { IRightPanelCardState } from "./stores/right-panel/RightPanelStoreIPanelState";
import { findDMForUser } from "./utils/direct-messages";
import { findDMForUser } from "./utils/dm/findDMForUser";
async function enable4SIfNeeded() {
const cli = MatrixClientPeg.get();