Prepare for Element Call integration (#9224)

* Improve accessibility and testability of Tooltip

Adding a role to Tooltip was motivated by React Testing Library's
reliance on accessibility-related attributes to locate elements.

* Make the ReadyWatchingStore constructor safer

The ReadyWatchingStore constructor previously had a chance to
immediately call onReady, which was dangerous because it was potentially
calling the derived class's onReady at a point when the derived class
hadn't even finished construction yet. In normal usage, I guess this
never was a problem, but it was causing some of the tests I was writing
to crash. This is solved by separating out the onReady call into a start
method.

* Rename 1:1 call components to 'LegacyCall'

to reflect the fact that they're slated for removal, and to not clash
with the new Call code.

* Refactor VideoChannelStore into Call and CallStore

Call is an abstract class that currently only has a Jitsi
implementation, but this will make it easy to later add an Element Call
implementation.

* Remove WidgetReady, ClientReady, and ForceHangupCall hacks

These are no longer used by the new Jitsi call implementation, and can
be removed.

* yarn i18n

* Delete call map entries instead of inserting nulls

* Allow multiple active calls and consolidate call listeners

* Fix a race condition when creating a video room

* Un-hardcode the media device fallback labels

* Apply misc code review fixes

* yarn i18n

* Disconnect from calls more politely on logout

* Fix some strict mode errors

* Fix another updateRoom race condition
This commit is contained in:
Robin
2022-08-30 15:13:39 -04:00
committed by GitHub
parent 50f6986f6c
commit 0d6a550c33
107 changed files with 2573 additions and 2157 deletions
+32 -20
View File
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixClient, ClientEvent } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventType, RoomCreateTypeField, RoomType } from "matrix-js-sdk/src/@types/event";
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
@@ -37,7 +37,7 @@ import { getAddressType } from "./UserAddress";
import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
import SpaceStore from "./stores/spaces/SpaceStore";
import { makeSpaceParentEvent } from "./utils/space";
import { VIDEO_CHANNEL_MEMBER, addVideoChannel } from "./utils/VideoChannelUtils";
import { JitsiCall } from "./models/Call";
import { Action } from "./dispatcher/actions";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import Spinner from "./components/views/elements/Spinner";
@@ -131,8 +131,8 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
if (opts.roomType === RoomType.ElementVideo) {
createOpts.power_level_content_override = {
events: {
// Allow all users to send video member updates
[VIDEO_CHANNEL_MEMBER]: 0,
// Allow all users to send call membership updates
[JitsiCall.MEMBER_EVENT_TYPE]: 0,
// Make widgets immutable, even to admins
"im.vector.modular.widgets": 200,
// Annoyingly, we have to reiterate all the defaults here
@@ -239,7 +239,8 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
let modal;
if (opts.spinner) modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
let roomId;
let roomId: string;
let room: Promise<Room>;
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,
@@ -254,32 +255,43 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
}
}).finally(function() {
if (modal) modal.close();
}).then(function(res) {
}).then(async res => {
roomId = res.room_id;
if (opts.dmUserId) {
return Rooms.setDMRoom(roomId, opts.dmUserId);
} else {
return Promise.resolve();
}
room = new Promise(resolve => {
const storedRoom = client.getRoom(roomId);
if (storedRoom) {
resolve(storedRoom);
} else {
// The room hasn't arrived down sync yet
const onRoom = (emittedRoom: Room) => {
if (emittedRoom.roomId === roomId) {
resolve(emittedRoom);
client.off(ClientEvent.Room, onRoom);
}
};
client.on(ClientEvent.Room, onRoom);
}
});
if (opts.dmUserId) await Rooms.setDMRoom(roomId, opts.dmUserId);
}).then(() => {
if (opts.parentSpace) {
return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], opts.suggested);
}
}).then(async () => {
if (opts.roomType === RoomType.ElementVideo) {
// Set up video rooms with a Jitsi widget
await addVideoChannel(roomId, createOpts.name);
// Set up video rooms with a Jitsi call
await JitsiCall.create(await room);
// Reset our power level back to admin so that the widget becomes immutable
const room = client.getRoom(roomId);
const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
await client.setPowerLevel(roomId, client.getUserId(), 100, plEvent);
const plEvent = (await room)?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
await client.setPowerLevel(roomId, client.getUserId()!, 100, plEvent);
}
}).then(function() {
// NB createRoom doesn't block on the client seeing the echo that the
// room has been created, so we race here with the client knowing that
// the room exists, causing things like
// https://github.com/vector-im/vector-web/issues/1813
// 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.