From 5bab9b0858777ef8e29d13b957f22641f2d39ee8 Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 2 Apr 2024 23:57:50 +0200 Subject: [PATCH] Update recent list. - Don't use deprecated `groupCallEventHander` anymore (it used the old `m.call` state event.) - make the recent list reactive (getting removed from a call removes the item from the list) - support having rooms without shared secret but actual matrix encryption in the recent list - change the share link creation button so that we create a link with pwd for sharedKey rooms and with `perParticipantE2EE=true` for matrix encrypted rooms. Signed-off-by: Timo K --- src/UrlParams.ts | 3 +- src/home/CallList.tsx | 18 +++------ src/home/useGroupCallRooms.ts | 75 +++++++++++++++++++++-------------- src/matrix-utils.ts | 60 +++++++++++++++++----------- 4 files changed, 89 insertions(+), 67 deletions(-) diff --git a/src/UrlParams.ts b/src/UrlParams.ts index 78735a8e..547393bf 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -20,7 +20,8 @@ import { useLocation } from "react-router-dom"; import { Config } from "./config/Config"; export const PASSWORD_STRING = "password="; - +export const PER_PARTICIPANT_STRING = "perParticipantE2EE="; +export const VIA_SERVERS_STRING = "viaServers="; interface RoomIdentifier { roomAlias: string | null; roomId: string | null; diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index 187228b9..bb22d4e8 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -26,7 +26,7 @@ import styles from "./CallList.module.css"; import { getAbsoluteRoomUrl, getRelativeRoomUrl } from "../matrix-utils"; import { Body } from "../typography/Typography"; import { GroupCallRoom } from "./useGroupCallRooms"; -import { useRoomSharedKey } from "../e2ee/sharedKeyManagement"; +import { useRoomEncryptionSystem } from "../e2ee/sharedKeyManagement"; interface CallListProps { rooms: GroupCallRoom[]; @@ -66,16 +66,11 @@ interface CallTileProps { } const CallTile: FC = ({ name, avatarUrl, room }) => { - const roomSharedKey = useRoomSharedKey(room.roomId); - + const roomEncryptionSystem = useRoomEncryptionSystem(room.roomId); return (
@@ -89,11 +84,8 @@ const CallTile: FC = ({ name, avatarUrl, room }) => {
); diff --git a/src/home/useGroupCallRooms.ts b/src/home/useGroupCallRooms.ts index bea7ea1e..d5a26012 100644 --- a/src/home/useGroupCallRooms.ts +++ b/src/home/useGroupCallRooms.ts @@ -15,20 +15,22 @@ limitations under the License. */ import { MatrixClient } from "matrix-js-sdk/src/client"; -import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; -import { Room } from "matrix-js-sdk/src/models/room"; +import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEventHandler"; import { useState, useEffect } from "react"; +import { EventTimeline, EventType, JoinRule } from "matrix-js-sdk"; +import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager"; +import { KnownMembership } from "matrix-js-sdk/src/types"; -import { getKeyForRoom, isRoomE2EE } from "../e2ee/sharedKeyManagement"; +import { getKeyForRoom } from "../e2ee/sharedKeyManagement"; export interface GroupCallRoom { roomAlias?: string; roomName: string; avatarUrl: string; room: Room; - groupCall: GroupCall; + session: MatrixRTCSession; participants: RoomMember[]; } const tsCache: { [index: string]: number } = {}; @@ -46,7 +48,7 @@ function getLastTs(client: MatrixClient, r: Room): number { const myUserId = client.getUserId()!; - if (r.getMyMembership() !== "join") { + if (r.getMyMembership() !== KnownMembership.Join) { const membershipEvent = r.currentState.getStateEvents( "m.room.member", myUserId, @@ -80,38 +82,51 @@ function sortRooms(client: MatrixClient, rooms: Room[]): Room[] { }); } -function roomIsJoinable(room: Room): boolean { - if (isRoomE2EE(room)) { - return Boolean(getKeyForRoom(room.roomId)); - } else { - return true; +const roomIsJoinable = (room: Room): boolean => { + if (!room.hasEncryptionStateEvent() && !getKeyForRoom(room.roomId)) { + // if we have an non encrypted room (no encryption state event) we need a locally stored shared key. + // in case this key also does not exists we cannot join the room. + return false; } -} + // otherwise we can always join rooms because we will automatically decide if we want to use perParticipant or password + const joinRule = room.getJoinRule(); + return joinRule === JoinRule.Knock || joinRule === JoinRule.Public; +}; + +const roomIsJoinedWithCall = (room: Room): boolean => { + const roomStateEvents = room + .getLiveTimeline() + .getState(EventTimeline.FORWARDS)?.events; + return ( + room.getMyMembership() === KnownMembership.Join && + !!roomStateEvents?.get(EventType.GroupCallMemberPrefix) + ); +}; export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] { const [rooms, setRooms] = useState([]); useEffect(() => { function updateRooms(): void { - if (!client.groupCallEventHandler) { - return; - } - - const groupCalls = client.groupCallEventHandler.groupCalls.values(); - const rooms = Array.from(groupCalls) - .map((groupCall) => groupCall.room) + const rooms = client + .getRooms() + .filter(roomIsJoinedWithCall) .filter(roomIsJoinable); const sortedRooms = sortRooms(client, rooms); const items = sortedRooms.map((room) => { - const groupCall = client.getGroupCallForRoom(room.roomId)!; - + // const groupCall = client.getGroupCallForRoom(room.roomId)!; + const session = client.matrixRTC.getRoomSession(room); + session.memberships; return { roomAlias: room.getCanonicalAlias() ?? undefined, roomName: room.name, avatarUrl: room.getMxcAvatarUrl()!, room, - groupCall, - participants: [...groupCall!.participants.keys()], + session, + participants: session.memberships + .filter((m) => m.sender) + .map((m) => room.getMember(m.sender!)) + .filter((m) => m) as RoomMember[], }; }); @@ -120,15 +135,17 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] { updateRooms(); - client.on(GroupCallEventHandlerEvent.Incoming, updateRooms); - client.on(GroupCallEventHandlerEvent.Participants, updateRooms); - + client.matrixRTC.on( + MatrixRTCSessionManagerEvents.SessionStarted, + updateRooms, + ); + client.on(RoomEvent.MyMembership, updateRooms); return () => { - client.removeListener(GroupCallEventHandlerEvent.Incoming, updateRooms); - client.removeListener( - GroupCallEventHandlerEvent.Participants, + client.matrixRTC.off( + MatrixRTCSessionManagerEvents.SessionStarted, updateRooms, ); + client.off(RoomEvent.MyMembership, updateRooms); }; }, [client]); diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index f5025710..5540592b 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -24,20 +24,21 @@ import { ClientEvent } from "matrix-js-sdk/src/client"; import { Visibility, Preset } from "matrix-js-sdk/src/@types/partials"; import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; import { logger } from "matrix-js-sdk/src/logger"; -import { - GroupCallIntent, - GroupCallType, -} from "matrix-js-sdk/src/webrtc/groupCall"; import { secureRandomBase64Url } from "matrix-js-sdk/src/randomstring"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; import IndexedDBWorker from "./IndexedDBWorker?worker"; -import { getUrlParams, PASSWORD_STRING } from "./UrlParams"; +import { + getUrlParams, + PASSWORD_STRING, + PER_PARTICIPANT_STRING, + VIA_SERVERS_STRING, +} from "./UrlParams"; import { loadOlm } from "./olm"; import { Config } from "./config/Config"; import { E2eeType } from "./e2ee/e2eeType"; -import { saveKeyForRoom } from "./e2ee/sharedKeyManagement"; +import { EncryptionSystem, saveKeyForRoom } from "./e2ee/sharedKeyManagement"; export const fallbackICEServerAllowed = import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; @@ -340,14 +341,6 @@ export async function createRoom( logger.log(`Creating group call in ${result.room_id}`); - await client.createGroupCall( - result.room_id, - GroupCallType.Video, - false, - GroupCallIntent.Room, - true, - ); - let password; if (e2ee == E2eeType.SHARED_KEY) { password = secureRandomBase64Url(16); @@ -365,39 +358,58 @@ export async function createRoom( * Returns an absolute URL to that will load Element Call with the given room * @param roomId ID of the room * @param roomName Name of the room - * @param password e2e key for the room + * @param encryptionSystem what encryption (or EncryptionSystem.Unencrypted) the room uses */ export function getAbsoluteRoomUrl( roomId: string, + encryptionSystem: EncryptionSystem, roomName?: string, - password?: string, + viaServers?: string[], ): string { return `${window.location.protocol}//${ window.location.host - }${getRelativeRoomUrl(roomId, roomName, password)}`; + }${getRelativeRoomUrl(roomId, encryptionSystem, roomName, viaServers)}`; } /** * Returns a relative URL to that will load Element Call with the given room * @param roomId ID of the room * @param roomName Name of the room - * @param password e2e key for the room + * @param encryptionSystem what encryption (or EncryptionSystem.Unencrypted) the room uses */ export function getRelativeRoomUrl( roomId: string, + encryptionSystem: EncryptionSystem, roomName?: string, - password?: string, + viaServers?: string[], ): string { // The password shouldn't need URL encoding here (we generate URL-safe ones) but encode // it in case it came from another client that generated a non url-safe one - const encodedPassword = password ? encodeURIComponent(password) : undefined; - if (password && encodedPassword !== password) { - logger.info("Encoded call password used non URL-safe chars: buggy client?"); - } + const encryptionPart = ((): string => { + switch (encryptionSystem?.kind) { + case E2eeType.SHARED_KEY: { + const encodedPassword = encodeURIComponent(encryptionSystem.secret); + if (encodedPassword !== encryptionSystem.secret) { + logger.info( + "Encoded call password used non URL-safe chars: buggy client?", + ); + } + return "&" + PASSWORD_STRING + encodedPassword; + } + case E2eeType.PER_PARTICIPANT: + return "&" + PER_PARTICIPANT_STRING + "true"; + case E2eeType.NONE: + return ""; + } + })(); + const roomIdPart = `roomId=${roomId}`; + const viaServersPart = viaServers + ? viaServers.map((s) => "&" + VIA_SERVERS_STRING + s).join("") + : ""; return `/room/#${ roomName ? "/" + roomAliasLocalpartFromRoomName(roomName) : "" - }?roomId=${roomId}${password ? "&" + PASSWORD_STRING + encodedPassword : ""}`; + }?${roomIdPart}${encryptionPart}${viaServersPart}`; } export function getAvatarUrl(