Step 8.5: Move various room utilities out of `createRoom`

pull/21833/head
Travis Ralston 2022-03-24 15:32:22 -06:00
parent 211e00539a
commit 888d470c56
14 changed files with 176 additions and 117 deletions

View File

@ -54,12 +54,13 @@ import { Action } from './dispatcher/actions';
import VoipUserMapper from './VoipUserMapper';
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
import SdkConfig from './SdkConfig';
import { ensureDMExists, findDMForUser } from './createRoom';
import { ensureDMExists } from './createRoom';
import { Container, WidgetLayoutStore } from './stores/widgets/WidgetLayoutStore';
import IncomingCallToast, { getIncomingCallToastKey } from './toasts/IncomingCallToast';
import ToastStore from './stores/ToastStore';
import Resend from './Resend';
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { findDMForUser } from "./utils/direct-messages";
export const PROTOCOL_PSTN = 'm.protocol.pstn';
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';

View File

@ -18,11 +18,12 @@ import { Room } from 'matrix-js-sdk/src/models/room';
import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from 'matrix-js-sdk/src/@types/event';
import { ensureVirtualRoomExists, findDMForUser } from './createRoom';
import { ensureVirtualRoomExists } from './createRoom';
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";
// Functions for mapping virtual users & rooms. Currently the only lookup
// is sip virtual: there could be others in the future.

View File

@ -25,7 +25,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import withValidation, { IFieldState } from '../elements/Validation';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { IOpts, privateShouldBeEncrypted } from "../../../createRoom";
import { IOpts } from "../../../createRoom";
import Heading from "../typography/Heading";
import Field from "../elements/Field";
import StyledRadioGroup from "../elements/StyledRadioGroup";
@ -37,6 +37,7 @@ import SpaceStore from "../../../stores/spaces/SpaceStore";
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { privateShouldBeEncrypted } from "../../../utils/rooms";
interface IProps {
defaultPublic?: boolean;

View File

@ -34,11 +34,7 @@ import dis from "../../../dispatcher/dispatcher";
import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
import { humanizeTime } from "../../../utils/humanize";
import createRoom, {
canEncryptToAllUsers,
findDMForUser,
privateShouldBeEncrypted,
} from "../../../createRoom";
import createRoom, { canEncryptToAllUsers } from "../../../createRoom";
import {
IInviteResult,
inviteMultipleToRoom,
@ -68,6 +64,8 @@ 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";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */

View File

@ -33,7 +33,7 @@ import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import dis from '../../../dispatcher/dispatcher';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import createRoom, { findDMForUser, privateShouldBeEncrypted } from '../../../createRoom';
import createRoom from '../../../createRoom';
import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import SdkConfig from '../../../SdkConfig';
@ -79,6 +79,8 @@ import { useUserStatusMessage } from "../../../hooks/useUserStatusMessage";
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";
export interface IDevice {
deviceId: string;

View File

@ -33,12 +33,12 @@ import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
import { Action } from "../../../dispatcher/actions";
import SpaceStore from "../../../stores/spaces/SpaceStore";
import { showSpaceInvite } from "../../../utils/space";
import { privateShouldBeEncrypted } from "../../../createRoom";
import EventTileBubble from "../messages/EventTileBubble";
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../settings/UIFeature";
import { privateShouldBeEncrypted } from "../../../utils/rooms";
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);

View File

@ -24,7 +24,6 @@ import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
import Analytics from "../../../../../Analytics";
import dis from "../../../../../dispatcher/dispatcher";
import { privateShouldBeEncrypted } from "../../../../../createRoom";
import { SettingLevel } from "../../../../../settings/SettingLevel";
import SecureBackupPanel from "../../SecureBackupPanel";
import SettingsStore from "../../../../../settings/SettingsStore";
@ -39,6 +38,7 @@ import EventIndexPanel from "../../EventIndexPanel";
import InlineSpinner from "../../../elements/InlineSpinner";
import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
import { showDialog as showAnalyticsLearnMoreDialog } from "../../../dialogs/AnalyticsLearnMoreDialog";
import { privateShouldBeEncrypted } from "../../../../../utils/rooms";
interface IIgnoredUserProps {
userId: string;

View File

@ -17,7 +17,6 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { EventType, RoomCreateTypeField, RoomType } from "matrix-js-sdk/src/@types/event";
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
import {
@ -28,17 +27,13 @@ import {
Visibility,
} from "matrix-js-sdk/src/@types/partials";
import { logger } from "matrix-js-sdk/src/logger";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { MatrixClientPeg } from './MatrixClientPeg';
import Modal from './Modal';
import { _t } from './languageHandler';
import dis from "./dispatcher/dispatcher";
import * as Rooms from "./Rooms";
import DMRoomMap from "./utils/DMRoomMap";
import { getAddressType } from "./UserAddress";
import { getE2EEWellKnown } from "./utils/WellKnownUtils";
import { isJoinedOrNearlyJoined } from "./utils/membership";
import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
import SpaceStore from "./stores/spaces/SpaceStore";
import { makeSpaceParentEvent } from "./utils/space";
@ -47,6 +42,9 @@ 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 { privateShouldBeEncrypted } from "./utils/rooms";
import { waitForMember } from "./utils/membership";
// we define a number of interfaces which take their names from the js-sdk
/* eslint-disable camelcase */
@ -313,55 +311,6 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
});
}
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];
}
}
/*
* Try to ensure the user is already in the megolm session before continuing
* NOTE: this assumes you've just created the room and there's not been an opportunity
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
*/
export async function waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
const { timeout } = opts;
let handler;
return new Promise((resolve) => {
handler = function(_, __, member: RoomMember) { // eslint-disable-line @typescript-eslint/naming-convention
if (member.userId !== userId) return;
if (member.roomId !== roomId) return;
resolve(true);
};
client.on(RoomStateEvent.NewMember, handler);
/* We don't want to hang if this goes wrong, so we proceed and hope the other
user is already in the megolm session */
setTimeout(resolve, timeout, false);
}).finally(() => {
client.removeListener(RoomStateEvent.NewMember, handler);
});
}
/*
* Ensure that for every user in a room, there is at least one device that we
* can encrypt to.
@ -424,12 +373,3 @@ export async function ensureDMExists(client: MatrixClient, userId: string): Prom
}
return roomId;
}
export function privateShouldBeEncrypted(): boolean {
const e2eeWellKnown = getE2EEWellKnown();
if (e2eeWellKnown) {
const defaultDisabled = e2eeWellKnown["default"] === false;
return !defaultDisabled;
}
return true;
}

View File

@ -0,0 +1,46 @@
/*
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 } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import DMRoomMap from "./DMRoomMap";
import { isJoinedOrNearlyJoined } from "./membership";
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];
}
}

View File

@ -15,6 +15,9 @@ limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
/**
* Approximation of a membership status for a given room.
@ -75,3 +78,27 @@ export function isJoinedOrNearlyJoined(membership: string): boolean {
const effective = getEffectiveMembership(membership);
return effective === EffectiveMembership.Join || effective === EffectiveMembership.Invite;
}
/**
* Try to ensure the user is already in the megolm session before continuing
* NOTE: this assumes you've just created the room and there's not been an opportunity
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
*/
export async function waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
const { timeout } = opts;
let handler;
return new Promise((resolve) => {
handler = function(_, __, member: RoomMember) { // eslint-disable-line @typescript-eslint/naming-convention
if (member.userId !== userId) return;
if (member.roomId !== roomId) return;
resolve(true);
};
client.on(RoomStateEvent.NewMember, handler);
/* We don't want to hang if this goes wrong, so we proceed and hope the other
user is already in the megolm session */
setTimeout(resolve, timeout, false);
}).finally(() => {
client.removeListener(RoomStateEvent.NewMember, handler);
});
}

26
src/utils/rooms.ts Normal file
View File

@ -0,0 +1,26 @@
/*
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 { getE2EEWellKnown } from "./WellKnownUtils";
export function privateShouldBeEncrypted(): boolean {
const e2eeWellKnown = getE2EEWellKnown();
if (e2eeWellKnown) {
const defaultDisabled = e2eeWellKnown["default"] === false;
return !defaultDisabled;
}
return true;
}

View File

@ -22,13 +22,13 @@ import { MatrixClientPeg } from './MatrixClientPeg';
import dis from "./dispatcher/dispatcher";
import Modal from './Modal';
import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import { findDMForUser } from './createRoom';
import { accessSecretStorage } from './SecurityManager';
import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog";
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";
async function enable4SIfNeeded() {
const cli = MatrixClientPeg.get();

View File

@ -1,45 +1,4 @@
import { EventEmitter } from 'events';
import { waitForMember, canEncryptToAllUsers } from '../src/createRoom';
/* Shorter timeout, we've got tests to run */
const timeout = 30;
describe("waitForMember", () => {
let client;
beforeEach(() => {
client = new EventEmitter();
});
it("resolves with false if the timeout is reached", (done) => {
waitForMember(client, "", "", { timeout: 0 }).then((r) => {
expect(r).toBe(false);
done();
});
});
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
const roomId = "!roomId:domain";
const userId = "@clientId:domain";
waitForMember(client, roomId, userId, { timeout }).then((r) => {
expect(r).toBe(false);
done();
});
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId: "@anotherClient:domain" });
});
it("resolves with true if RoomState.newMember fires", (done) => {
const roomId = "!roomId:domain";
const userId = "@clientId:domain";
waitForMember(client, roomId, userId, { timeout }).then((r) => {
expect(r).toBe(true);
expect(client.listeners("RoomState.newMember").length).toBe(0);
done();
});
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId });
});
});
import { canEncryptToAllUsers } from '../src/createRoom';
describe("canEncryptToAllUsers", () => {
const trueUser = {

View File

@ -0,0 +1,58 @@
/*
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 { EventEmitter } from "events";
import { waitForMember } from "../../src/utils/membership";
/* Shorter timeout, we've got tests to run */
const timeout = 30;
describe("waitForMember", () => {
let client;
beforeEach(() => {
client = new EventEmitter();
});
it("resolves with false if the timeout is reached", (done) => {
waitForMember(client, "", "", { timeout: 0 }).then((r) => {
expect(r).toBe(false);
done();
});
});
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
const roomId = "!roomId:domain";
const userId = "@clientId:domain";
waitForMember(client, roomId, userId, { timeout }).then((r) => {
expect(r).toBe(false);
done();
});
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId: "@anotherClient:domain" });
});
it("resolves with true if RoomState.newMember fires", (done) => {
const roomId = "!roomId:domain";
const userId = "@clientId:domain";
waitForMember(client, roomId, userId, { timeout }).then((r) => {
expect(r).toBe(true);
expect(client.listeners("RoomState.newMember").length).toBe(0);
done();
});
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId });
});
});