element-web/src/VoipUserMapper.ts
David Langley 491f0cd08a
Change license (#13)
* Copyright headers 1

* Licence headers 2

* Copyright Headers 3

* Copyright Headers 4

* Copyright Headers 5

* Copyright Headers 6

* Copyright headers 7

* Add copyright headers for html and config file

* Replace license files and update package.json

* Update with CLA

* lint
2024-09-09 13:57:16 +00:00

150 lines
6.5 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { Room, EventType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import { ensureVirtualRoomExists } from "./createRoom";
import { MatrixClientPeg } from "./MatrixClientPeg";
import DMRoomMap from "./utils/DMRoomMap";
import LegacyCallHandler from "./LegacyCallHandler";
import { VIRTUAL_ROOM_EVENT_TYPE } from "./call-types";
import { findDMForUser } from "./utils/dm/findDMForUser";
// Functions for mapping virtual users & rooms. Currently the only lookup
// is sip virtual: there could be others in the future.
export default class VoipUserMapper {
// We store mappings of virtual -> native room IDs here until the local echo for the
// account data arrives.
private virtualToNativeRoomIdCache = new Map<string, string>();
public static sharedInstance(): VoipUserMapper {
if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper();
return window.mxVoipUserMapper;
}
private async userToVirtualUser(userId: string): Promise<string | null> {
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
if (results.length === 0 || !results[0].fields.lookup_success) return null;
return results[0].userid;
}
private async getVirtualUserForRoom(roomId: string): Promise<string | null> {
const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
if (!userId) return null;
const virtualUser = await this.userToVirtualUser(userId);
if (!virtualUser) return null;
return virtualUser;
}
public async getOrCreateVirtualRoomForRoom(roomId: string): Promise<string | null> {
const virtualUser = await this.getVirtualUserForRoom(roomId);
if (!virtualUser) return null;
const cli = MatrixClientPeg.safeGet();
const virtualRoomId = await ensureVirtualRoomExists(cli, virtualUser, roomId);
cli.setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
native_room: roomId,
});
this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
return virtualRoomId;
}
/**
* Gets the ID of the virtual room for a room, or null if the room has no
* virtual room
*/
public async getVirtualRoomForRoom(roomId: string): Promise<Room | undefined> {
const virtualUser = await this.getVirtualUserForRoom(roomId);
if (!virtualUser) return undefined;
return findDMForUser(MatrixClientPeg.safeGet(), virtualUser);
}
public nativeRoomForVirtualRoom(roomId: string): string | null {
const cachedNativeRoomId = this.virtualToNativeRoomIdCache.get(roomId);
if (cachedNativeRoomId) {
logger.log(
"Returning native room ID " + cachedNativeRoomId + " for virtual room ID " + roomId + " from cache",
);
return cachedNativeRoomId;
}
const cli = MatrixClientPeg.safeGet();
const virtualRoom = cli.getRoom(roomId);
if (!virtualRoom) return null;
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null;
const nativeRoomID = virtualRoomEvent.getContent()["native_room"];
const nativeRoom = cli.getRoom(nativeRoomID);
if (!nativeRoom || nativeRoom.getMyMembership() !== KnownMembership.Join) return null;
return nativeRoomID;
}
public isVirtualRoom(room: Room): boolean {
if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
if (this.virtualToNativeRoomIdCache.has(room.roomId)) return true;
// also look in the create event for the claimed native room ID, which is the only
// way we can recognise a virtual room we've created when it first arrives down
// our stream. We don't trust this in general though, as it could be faked by an
// inviter: our main source of truth is the DM state.
const roomCreateEvent = room.currentState.getStateEvents(EventType.RoomCreate, "");
if (!roomCreateEvent || !roomCreateEvent.getContent()) return false;
// we only look at this for rooms we created (so inviters can't just cause rooms
// to be invisible)
if (roomCreateEvent.getSender() !== MatrixClientPeg.safeGet().getUserId()) return false;
const claimedNativeRoomId = roomCreateEvent.getContent()[VIRTUAL_ROOM_EVENT_TYPE];
return Boolean(claimedNativeRoomId);
}
public async onNewInvitedRoom(invitedRoom: Room): Promise<void> {
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
const inviterId = invitedRoom.getDMInviter();
if (!inviterId) {
logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
}
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
if (result.length === 0) {
return;
}
if (result[0].fields.is_virtual) {
const cli = MatrixClientPeg.safeGet();
const nativeUser = result[0].userid;
const nativeRoom = findDMForUser(cli, nativeUser);
if (nativeRoom) {
// It's a virtual room with a matching native room, so set the room account data. This
// will make sure we know where how to map calls and also allow us know not to display
// it in the future.
cli.setRoomAccountData(invitedRoom.roomId, VIRTUAL_ROOM_EVENT_TYPE, {
native_room: nativeRoom.roomId,
});
// also auto-join the virtual room if we have a matching native room
// (possibly we should only join if we've also joined the native room, then we'd also have
// to make sure we joined virtual rooms on joining a native one)
cli.joinRoom(invitedRoom.roomId);
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
// in however long it takes for the echo of setAccountData to come down the sync
this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
}
}
}
}