Files
element-call/src/home/useGroupCallRooms.ts
T

181 lines
4.9 KiB
TypeScript
Raw Normal View History

2022-05-04 17:09:48 +01:00
/*
Copyright 2022-2024 New Vector Ltd.
2022-05-04 17:09:48 +01:00
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
2022-05-04 17:09:48 +01:00
*/
2025-03-13 16:58:14 +01:00
import {
type MatrixClient,
type RoomMember,
type Room,
RoomEvent,
EventTimeline,
EventType,
JoinRule,
KnownMembership,
} from "matrix-js-sdk";
2022-01-05 17:19:03 -08:00
import { useState, useEffect } from "react";
2025-03-13 16:58:14 +01:00
import {
MatrixRTCSessionManagerEvents,
type MatrixRTCSession,
} from "matrix-js-sdk/lib/matrixrtc";
import { logger } from "matrix-js-sdk/lib/logger";
2022-01-05 17:19:03 -08:00
2024-04-23 15:15:13 +02:00
import { getKeyForRoom } from "../e2ee/sharedKeyManagement";
2022-07-14 19:20:52 +02:00
export interface GroupCallRoom {
2023-09-19 19:23:19 +01:00
roomAlias?: string;
2022-07-14 19:20:52 +02:00
roomName: string;
avatarUrl: string;
room: Room;
2024-04-23 15:15:13 +02:00
session: MatrixRTCSession;
2022-07-14 19:20:52 +02:00
participants: RoomMember[];
}
const tsCache: { [index: string]: number } = {};
2022-01-05 17:19:03 -08:00
2023-09-22 18:05:13 -04:00
function getLastTs(client: MatrixClient, r: Room): number {
2022-01-05 17:19:03 -08:00
if (tsCache[r.roomId]) {
return tsCache[r.roomId];
}
if (!r || !r.timeline) {
const ts = Number.MAX_SAFE_INTEGER;
tsCache[r.roomId] = ts;
return ts;
}
2023-06-30 16:43:28 +01:00
const myUserId = client.getUserId()!;
2022-01-05 17:19:03 -08:00
2024-04-23 15:15:13 +02:00
if (r.getMyMembership() !== KnownMembership.Join) {
2022-01-05 17:19:03 -08:00
const membershipEvent = r.currentState.getStateEvents(
"m.room.member",
2023-10-11 10:42:04 -04:00
myUserId,
2022-01-05 17:19:03 -08:00
);
if (membershipEvent && !Array.isArray(membershipEvent)) {
const ts = membershipEvent.getTs();
tsCache[r.roomId] = ts;
return ts;
}
}
for (let i = r.timeline.length - 1; i >= 0; --i) {
const ev = r.timeline[i];
const ts = ev.getTs();
if (ts) {
tsCache[r.roomId] = ts;
return ts;
}
}
const ts = Number.MAX_SAFE_INTEGER;
tsCache[r.roomId] = ts;
return ts;
}
2022-07-14 19:20:52 +02:00
function sortRooms(client: MatrixClient, rooms: Room[]): Room[] {
2022-01-05 17:19:03 -08:00
return rooms.sort((a, b) => {
return getLastTs(client, b) - getLastTs(client, a);
});
}
2024-04-23 15:15:13 +02:00
const roomIsJoinable = (room: Room): boolean => {
2025-06-04 22:51:13 +02:00
const password = getKeyForRoom(room.roomId);
if (!room.hasEncryptionStateEvent() && !password) {
// if we have a non encrypted room (no encryption state event) we need a locally stored shared key.
2024-04-23 15:15:13 +02:00
// in case this key also does not exists we cannot join the room.
return false;
}
2024-04-23 15:15:13 +02:00
// otherwise we can always join rooms because we will automatically decide if we want to use perParticipant or password
switch (room.getJoinRule()) {
case JoinRule.Public:
return true;
case JoinRule.Knock:
switch (room.getMyMembership()) {
case KnownMembership.Join:
case KnownMembership.Knock:
return true;
case KnownMembership.Invite:
return (
room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
?.getStateEvents(EventType.RoomMember, room.myUserId)
?.getPrevContent().membership === JoinRule.Knock
);
default:
return false;
}
// TODO: check JoinRule.Restricted and return true if join condition is satisfied
default:
return room.getMyMembership() === KnownMembership.Join;
}
2024-04-23 15:15:13 +02:00
};
const roomHasCallMembershipEvents = (room: Room): boolean => {
switch (room.getMyMembership()) {
case KnownMembership.Join:
return !!room
.getLiveTimeline()
.getState(EventTimeline.FORWARDS)
?.events?.get(EventType.GroupCallMemberPrefix);
case KnownMembership.Knock:
// Assume that a room you've knocked on is able to hold calls
return true;
default:
return false;
}
2024-04-23 15:15:13 +02:00
};
2022-07-14 19:20:52 +02:00
export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
2023-07-03 19:23:26 +02:00
const [rooms, setRooms] = useState<GroupCallRoom[]>([]);
2022-01-05 17:19:03 -08:00
useEffect(() => {
2023-09-22 18:05:13 -04:00
function updateRooms(): void {
// We want to show all rooms that historically had a call and which we are (or can become) part of.
2024-04-23 15:15:13 +02:00
const rooms = client
.getRooms()
.filter(roomHasCallMembershipEvents)
.filter(roomIsJoinable);
2023-08-11 13:25:09 +02:00
const sortedRooms = sortRooms(client, rooms);
Promise.all(
sortedRooms.map(async (room) => {
const session = await client.matrixRTC.getRoomSession(room);
return {
roomAlias: room.getCanonicalAlias() ?? undefined,
roomName: room.name,
avatarUrl: room.getMxcAvatarUrl()!,
room,
session,
participants: session.memberships
.filter((m) => m.sender)
.map((m) => room.getMember(m.sender!))
.filter((m) => m) as RoomMember[],
};
}),
)
.then((items) => setRooms(items))
.catch(logger.error);
2022-01-05 17:19:03 -08:00
}
updateRooms();
2024-04-23 15:15:13 +02:00
client.matrixRTC.on(
MatrixRTCSessionManagerEvents.SessionStarted,
updateRooms,
);
client.on(RoomEvent.MyMembership, updateRooms);
2024-06-04 11:20:25 -04:00
return (): void => {
2024-04-23 15:15:13 +02:00
client.matrixRTC.off(
MatrixRTCSessionManagerEvents.SessionStarted,
2023-10-11 10:42:04 -04:00
updateRooms,
2022-07-14 19:20:52 +02:00
);
2024-04-23 15:15:13 +02:00
client.off(RoomEvent.MyMembership, updateRooms);
2022-01-05 17:19:03 -08:00
};
2022-05-05 13:15:07 +01:00
}, [client]);
2022-01-05 17:19:03 -08:00
return rooms;
}