2016-06-09 00:03:46 +02:00
|
|
|
/*
|
2024-09-09 15:57:16 +02:00
|
|
|
Copyright 2024 New Vector Ltd.
|
2020-01-23 14:54:28 +01:00
|
|
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
2024-09-09 15:57:16 +02:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2016-06-09 00:03:46 +02:00
|
|
|
|
2024-09-09 15:57:16 +02:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
|
|
Please see LICENSE files in the repository root for full details.
|
2016-06-09 00:03:46 +02:00
|
|
|
*/
|
|
|
|
|
2021-07-23 09:46:20 +02:00
|
|
|
import {
|
2023-08-04 09:36:16 +02:00
|
|
|
MatrixClient,
|
|
|
|
ClientEvent,
|
|
|
|
Room,
|
|
|
|
EventType,
|
|
|
|
RoomCreateTypeField,
|
|
|
|
RoomType,
|
|
|
|
ICreateRoomOpts,
|
2021-07-23 09:46:20 +02:00
|
|
|
HistoryVisibility,
|
|
|
|
JoinRule,
|
|
|
|
Preset,
|
|
|
|
RestrictedAllowType,
|
|
|
|
Visibility,
|
2023-08-04 09:36:16 +02:00
|
|
|
} from "matrix-js-sdk/src/matrix";
|
2021-10-23 00:23:32 +02:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
2020-07-02 23:38:11 +02:00
|
|
|
|
2023-02-13 12:39:16 +01:00
|
|
|
import Modal, { IHandle } from "./Modal";
|
2023-05-11 10:56:56 +02:00
|
|
|
import { _t, UserFriendlyError } from "./languageHandler";
|
2020-05-14 04:41:41 +02:00
|
|
|
import dis from "./dispatcher/dispatcher";
|
2017-07-05 16:00:50 +02:00
|
|
|
import * as Rooms from "./Rooms";
|
2020-11-13 23:19:34 +01:00
|
|
|
import { getAddressType } from "./UserAddress";
|
2022-03-24 22:26:09 +01:00
|
|
|
import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
|
2021-11-11 14:07:41 +01:00
|
|
|
import SpaceStore from "./stores/spaces/SpaceStore";
|
2021-03-01 20:05:50 +01:00
|
|
|
import { makeSpaceParentEvent } from "./utils/space";
|
2022-09-16 17:12:27 +02:00
|
|
|
import { JitsiCall, ElementCall } from "./models/Call";
|
2021-06-29 14:11:58 +02:00
|
|
|
import { Action } from "./dispatcher/actions";
|
2021-07-02 12:12:41 +02:00
|
|
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
|
|
|
import Spinner from "./components/views/elements/Spinner";
|
2022-02-10 15:29:55 +01:00
|
|
|
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
2022-07-25 10:17:40 +02:00
|
|
|
import { findDMForUser } from "./utils/dm/findDMForUser";
|
2022-03-24 22:32:22 +01:00
|
|
|
import { privateShouldBeEncrypted } from "./utils/rooms";
|
2023-06-27 11:42:31 +02:00
|
|
|
import { shouldForceDisableEncryption } from "./utils/crypto/shouldForceDisableEncryption";
|
2022-03-24 22:32:22 +01:00
|
|
|
import { waitForMember } from "./utils/membership";
|
2022-07-08 08:32:38 +02:00
|
|
|
import { PreferredRoomVersions } from "./utils/PreferredRoomVersions";
|
2022-10-07 20:10:17 +02:00
|
|
|
import SettingsStore from "./settings/SettingsStore";
|
2024-07-04 17:50:07 +02:00
|
|
|
import { MEGOLM_ENCRYPTION_ALGORITHM } from "./utils/crypto";
|
2016-06-09 00:03:46 +02:00
|
|
|
|
2020-07-20 21:50:12 +02:00
|
|
|
// we define a number of interfaces which take their names from the js-sdk
|
2020-07-20 21:43:49 +02:00
|
|
|
/* eslint-disable camelcase */
|
|
|
|
|
2021-03-01 18:54:53 +01:00
|
|
|
export interface IOpts {
|
2020-07-02 23:38:11 +02:00
|
|
|
dmUserId?: string;
|
2021-06-14 21:31:58 +02:00
|
|
|
createOpts?: ICreateRoomOpts;
|
2020-07-02 23:38:11 +02:00
|
|
|
spinner?: boolean;
|
|
|
|
guestAccess?: boolean;
|
|
|
|
encryption?: boolean;
|
|
|
|
inlineErrors?: boolean;
|
|
|
|
andView?: boolean;
|
2021-07-23 09:46:20 +02:00
|
|
|
avatar?: File | string; // will upload if given file, else mxcUrl is needed
|
|
|
|
roomType?: RoomType | string;
|
|
|
|
historyVisibility?: HistoryVisibility;
|
2021-03-01 20:05:50 +01:00
|
|
|
parentSpace?: Room;
|
2021-08-31 17:42:21 +02:00
|
|
|
// contextually only makes sense if parentSpace is specified, if true then will be added to parentSpace as suggested
|
|
|
|
suggested?: boolean;
|
2021-07-02 15:51:55 +02:00
|
|
|
joinRule?: JoinRule;
|
2020-07-02 23:38:11 +02:00
|
|
|
}
|
|
|
|
|
2022-09-16 17:12:27 +02:00
|
|
|
const DEFAULT_EVENT_POWER_LEVELS = {
|
|
|
|
[EventType.RoomName]: 50,
|
|
|
|
[EventType.RoomAvatar]: 50,
|
|
|
|
[EventType.RoomPowerLevels]: 100,
|
|
|
|
[EventType.RoomHistoryVisibility]: 100,
|
|
|
|
[EventType.RoomCanonicalAlias]: 50,
|
|
|
|
[EventType.RoomTombstone]: 100,
|
|
|
|
[EventType.RoomServerAcl]: 100,
|
|
|
|
[EventType.RoomEncryption]: 100,
|
|
|
|
};
|
|
|
|
|
2016-06-09 00:03:46 +02:00
|
|
|
/**
|
|
|
|
* Create a new room, and switch to it.
|
|
|
|
*
|
2023-05-30 11:36:34 +02:00
|
|
|
* @param client The Matrix Client instance to create the room with
|
2016-06-09 00:03:46 +02:00
|
|
|
* @param {object=} opts parameters for creating the room
|
2016-09-09 20:25:00 +02:00
|
|
|
* @param {string=} opts.dmUserId If specified, make this a DM room for this user and invite them
|
2016-06-09 00:03:46 +02:00
|
|
|
* @param {object=} opts.createOpts set of options to pass to createRoom call.
|
2019-06-14 18:21:07 +02:00
|
|
|
* @param {bool=} opts.spinner True to show a modal spinner while the room is created.
|
|
|
|
* Default: True
|
2020-01-23 14:54:28 +01:00
|
|
|
* @param {bool=} opts.guestAccess Whether to enable guest access.
|
|
|
|
* Default: True
|
2020-01-23 15:05:38 +01:00
|
|
|
* @param {bool=} opts.encryption Whether to enable encryption.
|
|
|
|
* Default: False
|
2020-02-28 01:10:31 +01:00
|
|
|
* @param {bool=} opts.inlineErrors True to raise errors off the promise instead of resolving to null.
|
|
|
|
* Default: False
|
2020-07-02 23:38:11 +02:00
|
|
|
* @param {bool=} opts.andView True to dispatch an action to view the room once it has been created.
|
2017-07-01 15:40:46 +02:00
|
|
|
*
|
|
|
|
* @returns {Promise} which resolves to the room id, or null if the
|
|
|
|
* action was aborted or failed.
|
2016-06-09 00:03:46 +02:00
|
|
|
*/
|
2023-05-30 11:36:34 +02:00
|
|
|
export default async function createRoom(client: MatrixClient, opts: IOpts): Promise<string | null> {
|
2016-09-09 20:25:00 +02:00
|
|
|
opts = opts || {};
|
2019-06-14 18:21:07 +02:00
|
|
|
if (opts.spinner === undefined) opts.spinner = true;
|
2020-01-23 14:54:28 +01:00
|
|
|
if (opts.guestAccess === undefined) opts.guestAccess = true;
|
2020-01-23 15:05:38 +01:00
|
|
|
if (opts.encryption === undefined) opts.encryption = false;
|
2016-06-09 00:03:46 +02:00
|
|
|
|
|
|
|
if (client.isGuest()) {
|
2022-12-12 12:24:14 +01:00
|
|
|
dis.dispatch({ action: "require_registration" });
|
2021-06-18 13:18:23 +02:00
|
|
|
return null;
|
2016-06-09 00:03:46 +02:00
|
|
|
}
|
|
|
|
|
2020-07-02 23:38:11 +02:00
|
|
|
const defaultPreset = opts.dmUserId ? Preset.TrustedPrivateChat : Preset.PrivateChat;
|
2016-09-09 20:25:00 +02:00
|
|
|
|
2016-06-09 00:03:46 +02:00
|
|
|
// set some defaults for the creation
|
2021-06-14 21:31:58 +02:00
|
|
|
const createOpts: ICreateRoomOpts = opts.createOpts || {};
|
2016-09-09 20:25:00 +02:00
|
|
|
createOpts.preset = createOpts.preset || defaultPreset;
|
2020-07-02 23:38:11 +02:00
|
|
|
createOpts.visibility = createOpts.visibility || Visibility.Private;
|
2023-10-17 11:37:08 +02:00
|
|
|
|
|
|
|
// We allow UX of DMing ourselves as a form of creating a personal room but the server throws
|
|
|
|
// an error when a user tries to invite themselves so we filter it out
|
|
|
|
if (opts.dmUserId && opts.dmUserId !== client.getUserId() && createOpts.invite === undefined) {
|
2018-01-25 10:54:31 +01:00
|
|
|
switch (getAddressType(opts.dmUserId)) {
|
2022-12-12 12:24:14 +01:00
|
|
|
case "mx-user-id":
|
2018-01-25 10:54:31 +01:00
|
|
|
createOpts.invite = [opts.dmUserId];
|
|
|
|
break;
|
2023-05-11 10:56:56 +02:00
|
|
|
case "email": {
|
2023-05-30 11:36:34 +02:00
|
|
|
const isUrl = client.getIdentityServerUrl(true);
|
2023-05-11 10:56:56 +02:00
|
|
|
if (!isUrl) {
|
2023-09-22 17:39:40 +02:00
|
|
|
throw new UserFriendlyError("cannot_invite_without_identity_server");
|
2023-05-11 10:56:56 +02:00
|
|
|
}
|
2022-12-12 12:24:14 +01:00
|
|
|
createOpts.invite_3pid = [
|
|
|
|
{
|
2023-05-11 10:56:56 +02:00
|
|
|
id_server: isUrl,
|
2022-12-12 12:24:14 +01:00
|
|
|
medium: "email",
|
|
|
|
address: opts.dmUserId,
|
|
|
|
},
|
|
|
|
];
|
2023-05-11 10:56:56 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-01-25 10:54:31 +01:00
|
|
|
}
|
2016-09-09 20:25:00 +02:00
|
|
|
}
|
2016-09-12 19:32:44 +02:00
|
|
|
if (opts.dmUserId && createOpts.is_direct === undefined) {
|
|
|
|
createOpts.is_direct = true;
|
|
|
|
}
|
2016-06-09 00:03:46 +02:00
|
|
|
|
2021-07-23 09:46:20 +02:00
|
|
|
if (opts.roomType) {
|
|
|
|
createOpts.creation_content = {
|
|
|
|
...createOpts.creation_content,
|
|
|
|
[RoomCreateTypeField]: opts.roomType,
|
|
|
|
};
|
2022-03-28 15:12:09 +02:00
|
|
|
|
2022-04-07 13:34:07 +02:00
|
|
|
// Video rooms require custom power levels
|
2022-04-04 16:29:40 +02:00
|
|
|
if (opts.roomType === RoomType.ElementVideo) {
|
2022-03-28 15:12:09 +02:00
|
|
|
createOpts.power_level_content_override = {
|
|
|
|
events: {
|
2022-09-16 17:12:27 +02:00
|
|
|
...DEFAULT_EVENT_POWER_LEVELS,
|
2022-08-30 21:13:39 +02:00
|
|
|
// Allow all users to send call membership updates
|
|
|
|
[JitsiCall.MEMBER_EVENT_TYPE]: 0,
|
2022-06-09 15:56:02 +02:00
|
|
|
// Make widgets immutable, even to admins
|
|
|
|
"im.vector.modular.widgets": 200,
|
2022-03-28 15:12:09 +02:00
|
|
|
},
|
2022-06-09 15:56:02 +02:00
|
|
|
users: {
|
|
|
|
// Temporarily give ourselves the power to set up a widget
|
2023-05-30 11:36:34 +02:00
|
|
|
[client.getSafeUserId()]: 200,
|
2022-09-16 17:12:27 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
} else if (opts.roomType === RoomType.UnstableCall) {
|
|
|
|
createOpts.power_level_content_override = {
|
|
|
|
events: {
|
|
|
|
...DEFAULT_EVENT_POWER_LEVELS,
|
|
|
|
// Allow all users to send call membership updates
|
2022-09-30 21:26:08 +02:00
|
|
|
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
2022-09-16 17:12:27 +02:00
|
|
|
// Make calls immutable, even to admins
|
2022-09-30 21:26:08 +02:00
|
|
|
[ElementCall.CALL_EVENT_TYPE.name]: 200,
|
2022-09-16 17:12:27 +02:00
|
|
|
},
|
|
|
|
users: {
|
|
|
|
// Temporarily give ourselves the power to set up a call
|
2023-05-30 11:36:34 +02:00
|
|
|
[client.getSafeUserId()]: 200,
|
2022-06-09 15:56:02 +02:00
|
|
|
},
|
2022-03-28 15:12:09 +02:00
|
|
|
};
|
|
|
|
}
|
2022-10-07 20:10:17 +02:00
|
|
|
} else if (SettingsStore.getValue("feature_group_calls")) {
|
|
|
|
createOpts.power_level_content_override = {
|
|
|
|
events: {
|
|
|
|
...DEFAULT_EVENT_POWER_LEVELS,
|
2023-11-28 13:13:31 +01:00
|
|
|
// It should always (including non video rooms) be possible to join a group call.
|
|
|
|
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
|
|
|
// Make sure only admins can enable it (DEPRECATED)
|
2022-10-07 20:10:17 +02:00
|
|
|
[ElementCall.CALL_EVENT_TYPE.name]: 100,
|
|
|
|
},
|
|
|
|
};
|
2021-07-23 09:46:20 +02:00
|
|
|
}
|
|
|
|
|
2017-05-24 17:56:13 +02:00
|
|
|
// By default, view the room after creating it
|
|
|
|
if (opts.andView === undefined) {
|
|
|
|
opts.andView = true;
|
|
|
|
}
|
|
|
|
|
2020-01-23 15:05:38 +01:00
|
|
|
createOpts.initial_state = createOpts.initial_state || [];
|
|
|
|
|
2016-06-09 00:03:46 +02:00
|
|
|
// Allow guests by default since the room is private and they'd
|
|
|
|
// need an invite. This means clicking on a 3pid invite email can
|
|
|
|
// actually drop you right in to a chat.
|
2020-01-23 14:54:28 +01:00
|
|
|
if (opts.guestAccess) {
|
|
|
|
createOpts.initial_state.push({
|
2022-12-12 12:24:14 +01:00
|
|
|
type: "m.room.guest_access",
|
|
|
|
state_key: "",
|
2016-06-09 00:03:46 +02:00
|
|
|
content: {
|
2022-12-12 12:24:14 +01:00
|
|
|
guest_access: "can_join",
|
2016-06-09 00:03:46 +02:00
|
|
|
},
|
2020-01-23 15:05:38 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts.encryption) {
|
|
|
|
createOpts.initial_state.push({
|
2022-12-12 12:24:14 +01:00
|
|
|
type: "m.room.encryption",
|
|
|
|
state_key: "",
|
2020-01-23 15:05:38 +01:00
|
|
|
content: {
|
2024-07-04 17:50:07 +02:00
|
|
|
algorithm: MEGOLM_ENCRYPTION_ALGORITHM,
|
2020-01-23 15:05:38 +01:00
|
|
|
},
|
2020-01-23 14:54:28 +01:00
|
|
|
});
|
|
|
|
}
|
2016-06-09 00:03:46 +02:00
|
|
|
|
2023-07-10 10:01:03 +02:00
|
|
|
if (opts.joinRule === JoinRule.Knock) {
|
|
|
|
createOpts.room_version = PreferredRoomVersions.KnockRooms;
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:05:50 +01:00
|
|
|
if (opts.parentSpace) {
|
2021-07-06 13:05:30 +02:00
|
|
|
createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
|
2021-07-23 09:46:20 +02:00
|
|
|
if (!opts.historyVisibility) {
|
2022-12-12 12:24:14 +01:00
|
|
|
opts.historyVisibility =
|
|
|
|
createOpts.preset === Preset.PublicChat ? HistoryVisibility.WorldReadable : HistoryVisibility.Invited;
|
2021-07-23 09:46:20 +02:00
|
|
|
}
|
2021-06-18 13:18:23 +02:00
|
|
|
|
2021-07-02 15:51:55 +02:00
|
|
|
if (opts.joinRule === JoinRule.Restricted) {
|
2022-07-08 08:32:38 +02:00
|
|
|
createOpts.room_version = PreferredRoomVersions.RestrictedRooms;
|
|
|
|
|
|
|
|
createOpts.initial_state.push({
|
|
|
|
type: EventType.RoomJoinRules,
|
|
|
|
content: {
|
2022-12-12 12:24:14 +01:00
|
|
|
join_rule: JoinRule.Restricted,
|
|
|
|
allow: [
|
|
|
|
{
|
|
|
|
type: RestrictedAllowType.RoomMembership,
|
|
|
|
room_id: opts.parentSpace.roomId,
|
|
|
|
},
|
|
|
|
],
|
2022-07-08 08:32:38 +02:00
|
|
|
},
|
|
|
|
});
|
2021-06-18 13:18:23 +02:00
|
|
|
}
|
2021-03-01 20:05:50 +01:00
|
|
|
}
|
|
|
|
|
2021-07-30 16:10:08 +02:00
|
|
|
// we handle the restricted join rule in the parentSpace handling block above
|
|
|
|
if (opts.joinRule && opts.joinRule !== JoinRule.Restricted) {
|
2021-07-06 13:05:30 +02:00
|
|
|
createOpts.initial_state.push({
|
2021-07-02 15:51:55 +02:00
|
|
|
type: EventType.RoomJoinRules,
|
|
|
|
content: { join_rule: opts.joinRule },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-23 09:46:20 +02:00
|
|
|
if (opts.avatar) {
|
|
|
|
let url = opts.avatar;
|
|
|
|
if (opts.avatar instanceof File) {
|
2022-10-12 19:59:07 +02:00
|
|
|
({ content_uri: url } = await client.uploadContent(opts.avatar));
|
2021-07-23 09:46:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
createOpts.initial_state.push({
|
|
|
|
type: EventType.RoomAvatar,
|
|
|
|
content: { url },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts.historyVisibility) {
|
|
|
|
createOpts.initial_state.push({
|
|
|
|
type: EventType.RoomHistoryVisibility,
|
|
|
|
content: {
|
2022-12-12 12:24:14 +01:00
|
|
|
history_visibility: opts.historyVisibility,
|
2021-07-23 09:46:20 +02:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-02-13 12:39:16 +01:00
|
|
|
let modal: IHandle<any> | undefined;
|
2023-02-03 16:27:47 +01:00
|
|
|
if (opts.spinner) modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
|
2016-06-09 00:03:46 +02:00
|
|
|
|
2022-08-30 21:13:39 +02:00
|
|
|
let roomId: string;
|
|
|
|
let room: Promise<Room>;
|
2022-12-12 12:24:14 +01:00
|
|
|
return client
|
|
|
|
.createRoom(createOpts)
|
|
|
|
.catch(function (err) {
|
|
|
|
// NB This checks for the Synapse-specific error condition of a room creation
|
|
|
|
// having been denied because the requesting user wanted to publish the room,
|
|
|
|
// but the server denies them that permission (via room_list_publication_rules).
|
|
|
|
// The check below responds by retrying without publishing the room.
|
|
|
|
if (
|
|
|
|
err.httpStatus === 403 &&
|
|
|
|
err.errcode === "M_UNKNOWN" &&
|
|
|
|
err.data.error === "Not allowed to publish room"
|
|
|
|
) {
|
|
|
|
logger.warn("Failed to publish room, try again without publishing it");
|
|
|
|
createOpts.visibility = Visibility.Private;
|
|
|
|
return client.createRoom(createOpts);
|
2022-08-30 21:13:39 +02:00
|
|
|
} else {
|
2022-12-12 12:24:14 +01:00
|
|
|
return Promise.reject(err);
|
2022-08-30 21:13:39 +02:00
|
|
|
}
|
2022-12-12 12:24:14 +01:00
|
|
|
})
|
|
|
|
.finally(function () {
|
|
|
|
if (modal) modal.close();
|
|
|
|
})
|
2023-01-12 14:25:14 +01:00
|
|
|
.then(async (res): Promise<void> => {
|
2022-12-12 12:24:14 +01:00
|
|
|
roomId = res.room_id;
|
|
|
|
|
|
|
|
room = new Promise((resolve) => {
|
|
|
|
const storedRoom = client.getRoom(roomId);
|
|
|
|
if (storedRoom) {
|
|
|
|
resolve(storedRoom);
|
|
|
|
} else {
|
|
|
|
// The room hasn't arrived down sync yet
|
2023-01-12 14:25:14 +01:00
|
|
|
const onRoom = (emittedRoom: Room): void => {
|
2022-12-12 12:24:14 +01:00
|
|
|
if (emittedRoom.roomId === roomId) {
|
|
|
|
resolve(emittedRoom);
|
|
|
|
client.off(ClientEvent.Room, onRoom);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
client.on(ClientEvent.Room, onRoom);
|
|
|
|
}
|
2017-05-24 17:56:13 +02:00
|
|
|
});
|
2022-12-12 12:24:14 +01:00
|
|
|
|
2023-06-01 15:43:24 +02:00
|
|
|
if (opts.dmUserId) await Rooms.setDMRoom(client, roomId, opts.dmUserId);
|
2022-12-12 12:24:14 +01:00
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
if (opts.parentSpace) {
|
|
|
|
return SpaceStore.instance.addRoomToSpace(
|
|
|
|
opts.parentSpace,
|
|
|
|
roomId,
|
2023-04-06 12:10:14 +02:00
|
|
|
[client.getDomain()!],
|
2022-12-12 12:24:14 +01:00
|
|
|
opts.suggested,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})
|
2023-01-12 14:25:14 +01:00
|
|
|
.then(async (): Promise<void> => {
|
2022-12-12 12:24:14 +01:00
|
|
|
if (opts.roomType === RoomType.ElementVideo) {
|
|
|
|
// Set up this video room with a Jitsi call
|
|
|
|
await JitsiCall.create(await room);
|
|
|
|
|
|
|
|
// Reset our power level back to admin so that the widget becomes immutable
|
2024-03-25 13:21:02 +01:00
|
|
|
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
2022-12-12 12:24:14 +01:00
|
|
|
} else if (opts.roomType === RoomType.UnstableCall) {
|
|
|
|
// Set up this video room with an Element call
|
|
|
|
await ElementCall.create(await room);
|
|
|
|
|
|
|
|
// Reset our power level back to admin so that the call becomes immutable
|
2024-03-25 13:21:02 +01:00
|
|
|
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
2022-12-12 12:24:14 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.then(
|
|
|
|
function () {
|
|
|
|
// NB we haven't necessarily blocked on the room promise, so we race
|
|
|
|
// here with the client knowing that the room exists, causing things
|
|
|
|
// like https://github.com/vector-im/vector-web/issues/1813
|
|
|
|
// Even if we were to block on the echo, servers tend to split the room
|
|
|
|
// state over multiple syncs so we can't atomically know when we have the
|
|
|
|
// entire thing.
|
|
|
|
if (opts.andView) {
|
|
|
|
dis.dispatch<ViewRoomPayload>({
|
|
|
|
action: Action.ViewRoom,
|
|
|
|
room_id: roomId,
|
|
|
|
should_peek: false,
|
|
|
|
// Creating a room will have joined us to the room,
|
|
|
|
// so we are expecting the room to come down the sync
|
|
|
|
// stream, if it hasn't already.
|
|
|
|
joining: true,
|
|
|
|
justCreatedOpts: opts,
|
|
|
|
metricsTrigger: "Created",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return roomId;
|
|
|
|
},
|
|
|
|
function (err) {
|
|
|
|
// Raise the error if the caller requested that we do so.
|
|
|
|
if (opts.inlineErrors) throw err;
|
|
|
|
|
|
|
|
// We also failed to join the room (this sets joining to false in RoomViewStore)
|
|
|
|
dis.dispatch({
|
|
|
|
action: Action.JoinRoomError,
|
|
|
|
roomId,
|
|
|
|
});
|
|
|
|
logger.error("Failed to create room " + roomId + " " + err);
|
2023-09-22 17:39:40 +02:00
|
|
|
let description = _t("create_room|generic_error");
|
2022-12-12 12:24:14 +01:00
|
|
|
if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
|
|
|
|
// Technically not possible with the UI as of April 2019 because there's no
|
|
|
|
// options for the user to change this. However, it's not a bad thing to report
|
|
|
|
// the error to the user for if/when the UI is available.
|
2023-09-22 17:39:40 +02:00
|
|
|
description = _t("create_room|unsupported_version");
|
2022-12-12 12:24:14 +01:00
|
|
|
}
|
|
|
|
Modal.createDialog(ErrorDialog, {
|
2023-09-22 17:39:40 +02:00
|
|
|
title: _t("create_room|error_title"),
|
2022-12-12 12:24:14 +01:00
|
|
|
description,
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
);
|
2016-06-09 00:03:46 +02:00
|
|
|
}
|
|
|
|
|
2020-02-18 12:25:19 +01:00
|
|
|
/*
|
|
|
|
* Ensure that for every user in a room, there is at least one device that we
|
|
|
|
* can encrypt to.
|
|
|
|
*/
|
2023-01-12 14:25:14 +01:00
|
|
|
export async function canEncryptToAllUsers(client: MatrixClient, userIds: string[]): Promise<boolean> {
|
2020-10-02 02:58:13 +02:00
|
|
|
try {
|
2023-04-24 16:19:08 +02:00
|
|
|
const usersDeviceMap = await client.getCrypto()?.getUserDeviceInfo(userIds, true);
|
|
|
|
if (!usersDeviceMap) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-31 09:43:47 +02:00
|
|
|
|
|
|
|
for (const devices of usersDeviceMap.values()) {
|
|
|
|
if (devices.size === 0) {
|
2023-04-03 11:28:17 +02:00
|
|
|
// This user does not have any encryption-capable devices.
|
2023-03-31 09:43:47 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2020-10-02 02:58:13 +02:00
|
|
|
} catch (e) {
|
2021-10-15 16:30:53 +02:00
|
|
|
logger.error("Error determining if it's possible to encrypt to all users: ", e);
|
2020-10-02 02:58:13 +02:00
|
|
|
return false; // assume not
|
|
|
|
}
|
2023-03-31 09:43:47 +02:00
|
|
|
|
|
|
|
return true;
|
2020-02-18 12:25:19 +01:00
|
|
|
}
|
|
|
|
|
2021-02-17 19:51:21 +01:00
|
|
|
// Similar to ensureDMExists but also adds creation content
|
|
|
|
// without polluting ensureDMExists with unrelated stuff (also
|
|
|
|
// they're never encrypted).
|
|
|
|
export async function ensureVirtualRoomExists(
|
2022-12-12 12:24:14 +01:00
|
|
|
client: MatrixClient,
|
|
|
|
userId: string,
|
|
|
|
nativeRoomId: string,
|
2023-02-03 16:27:47 +01:00
|
|
|
): Promise<string | null> {
|
2021-02-17 19:51:21 +01:00
|
|
|
const existingDMRoom = findDMForUser(client, userId);
|
2023-02-03 16:27:47 +01:00
|
|
|
let roomId: string | null;
|
2021-02-17 19:51:21 +01:00
|
|
|
if (existingDMRoom) {
|
|
|
|
roomId = existingDMRoom.roomId;
|
|
|
|
} else {
|
2023-05-30 11:36:34 +02:00
|
|
|
roomId = await createRoom(client, {
|
2021-02-17 19:51:21 +01:00
|
|
|
dmUserId: userId,
|
|
|
|
spinner: false,
|
|
|
|
andView: false,
|
|
|
|
createOpts: {
|
|
|
|
creation_content: {
|
|
|
|
// This allows us to recognise that the room is a virtual room
|
|
|
|
// when it comes down our sync stream (we also put the ID of the
|
|
|
|
// respective native room in there because why not?)
|
|
|
|
[VIRTUAL_ROOM_EVENT_TYPE]: nativeRoomId,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return roomId;
|
|
|
|
}
|
|
|
|
|
2023-02-03 16:27:47 +01:00
|
|
|
export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string | null> {
|
2020-02-13 14:34:44 +01:00
|
|
|
const existingDMRoom = findDMForUser(client, userId);
|
2023-02-03 16:27:47 +01:00
|
|
|
let roomId: string | null;
|
2020-02-13 14:34:44 +01:00
|
|
|
if (existingDMRoom) {
|
|
|
|
roomId = existingDMRoom.roomId;
|
2019-12-19 17:43:10 +01:00
|
|
|
} else {
|
2023-02-03 16:27:47 +01:00
|
|
|
let encryption: boolean | undefined;
|
2023-05-23 17:24:12 +02:00
|
|
|
if (privateShouldBeEncrypted(client)) {
|
2020-10-01 16:11:01 +02:00
|
|
|
encryption = await canEncryptToAllUsers(client, [userId]);
|
2020-02-13 15:13:10 +01:00
|
|
|
}
|
2021-02-17 19:51:21 +01:00
|
|
|
|
2023-05-30 11:36:34 +02:00
|
|
|
roomId = await createRoom(client, { encryption, dmUserId: userId, spinner: false, andView: false });
|
2023-03-07 14:19:18 +01:00
|
|
|
if (!roomId) return null;
|
2021-07-19 23:43:11 +02:00
|
|
|
await waitForMember(client, roomId, userId);
|
2019-12-19 17:43:10 +01:00
|
|
|
}
|
|
|
|
return roomId;
|
|
|
|
}
|
2023-06-21 23:50:01 +02:00
|
|
|
|
|
|
|
interface AllowedEncryptionSetting {
|
|
|
|
/**
|
|
|
|
* True when the user is allowed to choose whether encryption is enabled
|
|
|
|
*/
|
|
|
|
allowChange: boolean;
|
|
|
|
/**
|
|
|
|
* Set when user is not allowed to choose encryption setting
|
|
|
|
* True when encryption is forced to enabled
|
|
|
|
*/
|
|
|
|
forcedValue?: boolean;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Check if server configuration supports the user changing encryption for a room
|
|
|
|
* First check if server features force enable encryption for the given room type
|
|
|
|
* If not, check if server .well-known forces encryption to disabled
|
|
|
|
* If either are forced, then do not allow the user to change room's encryption
|
|
|
|
* @param client
|
|
|
|
* @param chatPreset chat type
|
|
|
|
* @returns Promise<boolean>
|
|
|
|
*/
|
|
|
|
export async function checkUserIsAllowedToChangeEncryption(
|
|
|
|
client: MatrixClient,
|
|
|
|
chatPreset: Preset,
|
|
|
|
): Promise<AllowedEncryptionSetting> {
|
|
|
|
const doesServerForceEncryptionForPreset = await client.doesServerForceEncryptionForPreset(chatPreset);
|
|
|
|
const doesWellKnownForceDisableEncryption = shouldForceDisableEncryption(client);
|
|
|
|
|
|
|
|
// server is forcing encryption to ENABLED
|
|
|
|
// while .well-known config is forcing it to DISABLED
|
|
|
|
// server version config overrides wk config
|
|
|
|
if (doesServerForceEncryptionForPreset && doesWellKnownForceDisableEncryption) {
|
|
|
|
console.warn(
|
|
|
|
`Conflicting e2ee settings: server config and .well-known configuration disagree. Using server forced encryption setting for chat type ${chatPreset}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (doesServerForceEncryptionForPreset) {
|
|
|
|
return { allowChange: false, forcedValue: true };
|
|
|
|
}
|
|
|
|
if (doesWellKnownForceDisableEncryption) {
|
|
|
|
return { allowChange: false, forcedValue: false };
|
|
|
|
}
|
|
|
|
|
|
|
|
return { allowChange: true };
|
|
|
|
}
|