Ensure correct focus configuration for Element Call before allowing users to call. (#31490)
* fixup type * Validate Element Call foci config * revert changes * Split out logic to CallStore so we don't repeat checks. * Refactor to use CallStore so we only fetch once. * Add test for useRoomCall * lint * Ensure we enable MatrixRTC when configuring element call. * fix test * Update @element-hq/element-web-playwright-common to 2.2.2 and enable matrix rtc * lint * Ensure call is configured for header test * type * Improve coverage * Update based on feedback * fix type
This commit is contained in:
@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import type React from "react";
|
||||
import { useFeatureEnabled, useSettingValue } from "../useSettings";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { useEventEmitter, useEventEmitterState } from "../useEventEmitter";
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { useWidgets } from "../../utils/WidgetUtils";
|
||||
import { WidgetType } from "../../widgets/WidgetType";
|
||||
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
|
||||
@@ -37,6 +38,9 @@ import { type InteractionName } from "../../PosthogTrackers";
|
||||
import { ElementCallMemberEventType } from "../../call-types";
|
||||
import { LocalRoom, LocalRoomState } from "../../models/LocalRoom";
|
||||
import { useScopedRoomContext } from "../../contexts/ScopedRoomContext";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
const logger = rootLogger.getChild("useRoomCall");
|
||||
|
||||
export enum PlatformCallType {
|
||||
ElementCall,
|
||||
@@ -67,6 +71,8 @@ export const getPlatformCallTypeProps = (
|
||||
label: _t("voip|legacy_call"),
|
||||
analyticsName: "WebVoipOptionLegacy",
|
||||
};
|
||||
default:
|
||||
throw Error(`Unexpected PlatformCallType ${platformCallType}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,10 +116,22 @@ export const useRoomCall = (
|
||||
return SdkConfig.get("element_call").use_exclusively;
|
||||
}, []);
|
||||
|
||||
const serverIsConfiguredForElementCall = CallStore.instance
|
||||
.getConfiguredRTCTransports()
|
||||
.some((s) => s.type === "livekit" && s.livekit_service_url);
|
||||
|
||||
useEffect(() => {
|
||||
if (useElementCallExclusively && !serverIsConfiguredForElementCall) {
|
||||
logger.warn(
|
||||
"Element Call is configured to be used exclusively, but the server is not configured with a transport",
|
||||
);
|
||||
}
|
||||
}, [useElementCallExclusively, serverIsConfiguredForElementCall]);
|
||||
|
||||
const hasLegacyCall = useEventEmitterState(
|
||||
LegacyCallHandler.instance,
|
||||
SdkContextClass.instance.legacyCallHandler,
|
||||
LegacyCallHandlerEvent.CallsChanged,
|
||||
() => LegacyCallHandler.instance.getCallForRoom(room.roomId) !== null,
|
||||
() => SdkContextClass.instance.legacyCallHandler.getCallForRoom(room.roomId) !== null,
|
||||
);
|
||||
// settings
|
||||
const widgets = useWidgets(room);
|
||||
@@ -143,11 +161,13 @@ export const useRoomCall = (
|
||||
// room
|
||||
const memberCount = useRoomMemberCount(room);
|
||||
|
||||
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
||||
const [mayEditWidgets, mayCreateElementCallState] = useRoomState(room, () => [
|
||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||
room.currentState.mayClientSendStateEvent(ElementCallMemberEventType.name, room.client),
|
||||
]);
|
||||
|
||||
const mayCreateElementCalls = mayCreateElementCallState && serverIsConfiguredForElementCall;
|
||||
|
||||
// The options provided to the RoomHeader.
|
||||
// If there are multiple options, the user will be prompted to choose.
|
||||
const callOptions = useMemo((): PlatformCallType[] => {
|
||||
@@ -221,6 +241,10 @@ export const useRoomCall = (
|
||||
if (!callOptions.includes(PlatformCallType.LegacyCall) && !mayCreateElementCalls && !mayEditWidgets) {
|
||||
return State.NoPermission;
|
||||
}
|
||||
// Catch-all for just not having any call options available.
|
||||
if (!callOptions.length) {
|
||||
return State.NoPermission;
|
||||
}
|
||||
return State.NoCall;
|
||||
}, [
|
||||
callOptions,
|
||||
|
||||
+37
-2
@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type MatrixRTCSession, MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc";
|
||||
import { type MatrixRTCSession, MatrixRTCSessionManagerEvents, type Transport } from "matrix-js-sdk/src/matrixrtc";
|
||||
import { MatrixError, type EmptyObject, type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { EmptyObject, Room } from "matrix-js-sdk/src/matrix";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { UPDATE_EVENT } from "./AsyncStore";
|
||||
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
||||
@@ -35,6 +35,8 @@ export class CallStore extends AsyncStoreWithClient<EmptyObject> {
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
private readonly configuredMatrixRTCTransports = new Set<Transport>();
|
||||
|
||||
private constructor() {
|
||||
super(defaultDispatcher);
|
||||
this.setMaxListeners(100); // One for each RoomTile
|
||||
@@ -44,8 +46,36 @@ export class CallStore extends AsyncStoreWithClient<EmptyObject> {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch transports used by MatrixRTC services, such as Element Call.
|
||||
* This function is called once during Store startup which means we don't refetch
|
||||
* transports every time we need to check for Element Call support.
|
||||
*/
|
||||
protected async fetchTransports(): Promise<void> {
|
||||
if (!this.matrixClient) return;
|
||||
// Prefer checking the proper endpoint for transports.
|
||||
try {
|
||||
const transports = await this.matrixClient._unstable_getRTCTransports();
|
||||
transports.forEach((t) => this.configuredMatrixRTCTransports.add(t));
|
||||
} catch (ex) {
|
||||
// Expected, MSC not implemented.
|
||||
if (ex instanceof MatrixError === false || ex.errcode !== "M_NOT_FOUND") {
|
||||
logger.warn("Unexpected error when trying to fetch RTC transports", ex);
|
||||
}
|
||||
}
|
||||
// See https://github.com/matrix-org/matrix-spec-proposals/blob/d61969a9a3696b6c54d7987b1643b5bc03670927/proposals/4143-matrix-rtc.md#discovery-of-foci-using-well-knownmatrixclient
|
||||
// This well-known option has since been removed from the spec but is still widely deployed.
|
||||
await this.matrixClient.waitForClientWellKnown();
|
||||
const foci = this.matrixClient.getClientWellKnown()?.["org.matrix.msc4143.rtc_foci"];
|
||||
if (Array.isArray(foci)) {
|
||||
foci.forEach((foci) => this.configuredMatrixRTCTransports.add(foci));
|
||||
}
|
||||
}
|
||||
|
||||
protected async onReady(): Promise<any> {
|
||||
if (!this.matrixClient) return;
|
||||
// Fetch transports, but don't await the result.
|
||||
void this.fetchTransports();
|
||||
// We assume that the calls present in a room are a function of room
|
||||
// widgets and group calls, so we initialize the room map here and then
|
||||
// update it whenever those change
|
||||
@@ -81,6 +111,7 @@ export class CallStore extends AsyncStoreWithClient<EmptyObject> {
|
||||
this.callListeners.clear();
|
||||
this.calls.clear();
|
||||
this._connectedCalls.clear();
|
||||
this.configuredMatrixRTCTransports.clear();
|
||||
|
||||
this.matrixClient?.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, this.onRTCSessionStart);
|
||||
WidgetStore.instance.off(UPDATE_EVENT, this.onWidgets);
|
||||
@@ -187,6 +218,10 @@ export class CallStore extends AsyncStoreWithClient<EmptyObject> {
|
||||
}
|
||||
};
|
||||
|
||||
public getConfiguredRTCTransports(): Transport[] {
|
||||
return [...this.configuredMatrixRTCTransports];
|
||||
}
|
||||
|
||||
private onRTCSessionStart = (roomId: string, session: MatrixRTCSession): void => {
|
||||
this.updateRoom(session.room);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user