170 lines
4.9 KiB
TypeScript
170 lines
4.9 KiB
TypeScript
/*
|
|
Copyright 2022-2024 New Vector Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
|
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|
import { useState, useEffect } from "react";
|
|
import { EventTimeline, EventType, JoinRule } from "matrix-js-sdk/src/matrix";
|
|
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 } from "../e2ee/sharedKeyManagement";
|
|
|
|
export interface GroupCallRoom {
|
|
roomAlias?: string;
|
|
roomName: string;
|
|
avatarUrl: string;
|
|
room: Room;
|
|
session: MatrixRTCSession;
|
|
participants: RoomMember[];
|
|
}
|
|
const tsCache: { [index: string]: number } = {};
|
|
|
|
function getLastTs(client: MatrixClient, r: Room): number {
|
|
if (tsCache[r.roomId]) {
|
|
return tsCache[r.roomId];
|
|
}
|
|
|
|
if (!r || !r.timeline) {
|
|
const ts = Number.MAX_SAFE_INTEGER;
|
|
tsCache[r.roomId] = ts;
|
|
return ts;
|
|
}
|
|
|
|
const myUserId = client.getUserId()!;
|
|
|
|
if (r.getMyMembership() !== KnownMembership.Join) {
|
|
const membershipEvent = r.currentState.getStateEvents(
|
|
"m.room.member",
|
|
myUserId,
|
|
);
|
|
|
|
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;
|
|
}
|
|
|
|
function sortRooms(client: MatrixClient, rooms: Room[]): Room[] {
|
|
return rooms.sort((a, b) => {
|
|
return getLastTs(client, b) - getLastTs(client, a);
|
|
});
|
|
}
|
|
|
|
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
|
|
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;
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
|
const [rooms, setRooms] = useState<GroupCallRoom[]>([]);
|
|
|
|
useEffect(() => {
|
|
function updateRooms(): void {
|
|
// We want to show all rooms that historically had a call and which we are (or can become) part of.
|
|
const rooms = client
|
|
.getRooms()
|
|
.filter(roomHasCallMembershipEvents)
|
|
.filter(roomIsJoinable);
|
|
const sortedRooms = sortRooms(client, rooms);
|
|
const items = sortedRooms.map((room) => {
|
|
const session = 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[],
|
|
};
|
|
});
|
|
|
|
setRooms(items);
|
|
}
|
|
|
|
updateRooms();
|
|
|
|
client.matrixRTC.on(
|
|
MatrixRTCSessionManagerEvents.SessionStarted,
|
|
updateRooms,
|
|
);
|
|
client.on(RoomEvent.MyMembership, updateRooms);
|
|
return (): void => {
|
|
client.matrixRTC.off(
|
|
MatrixRTCSessionManagerEvents.SessionStarted,
|
|
updateRooms,
|
|
);
|
|
client.off(RoomEvent.MyMembership, updateRooms);
|
|
};
|
|
}, [client]);
|
|
|
|
return rooms;
|
|
}
|