diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 094e685fc92..167b0281a4a 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -26,6 +26,7 @@ import CopyableText from "../elements/CopyableText"; import RoomNotifications from "./devtools/RoomNotifications"; import { Crypto } from "./devtools/Crypto"; import SettingsField from "../elements/SettingsField.tsx"; +import MatrixRtcDebug from "./devtools/MatrixRtcDebug.tsx"; enum Category { Room, @@ -46,6 +47,7 @@ const Tools: Record = { [_td("devtools|view_servers_in_room"), ServersInRoom], [_td("devtools|notifications_debug"), RoomNotifications], [_td("devtools|active_widgets"), WidgetExplorer], + [_td("devtools|matrix_rtc_debug"), MatrixRtcDebug], ], [Category.Other]: [ [_td("devtools|explore_account_data"), AccountDataExplorer], diff --git a/src/components/views/dialogs/devtools/MatrixRtcDebug.tsx b/src/components/views/dialogs/devtools/MatrixRtcDebug.tsx new file mode 100644 index 00000000000..6322042aa99 --- /dev/null +++ b/src/components/views/dialogs/devtools/MatrixRtcDebug.tsx @@ -0,0 +1,174 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2023 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import React, { type JSX, useContext, useEffect, useMemo, useState } from "react"; +import { + type MatrixRTCSession, + MatrixRTCSessionEvent, + MatrixRTCSessionManagerEvents, + type CallMembership, +} from "matrix-js-sdk/src/matrixrtc"; +import { Badge } from "@vector-im/compound-web"; + +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx"; +import { _t } from "../../../../languageHandler.tsx"; +import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool.tsx"; +import { useTypedEventEmitter, useTypedEventEmitterState } from "../../../../hooks/useEventEmitter.ts"; +import { useCall } from "../../../../hooks/useCall.ts"; +import { type ElementCall } from "../../../../models/Call.ts"; + +function MatrixRTCSessionInfo({ + session, + active, + call, +}: { + session: MatrixRTCSession; + active: boolean; + call?: ElementCall; +}): JSX.Element { + const memberships = useTypedEventEmitterState( + session, + MatrixRTCSessionEvent.MembershipsChanged, + (_old, newMembership) => (newMembership ? newMembership : session.memberships), + ) as CallMembership[]; + const latestChange = useTypedEventEmitterState( + session, + MatrixRTCSessionEvent.MembershipsChanged, + (_old, newMembership) => (newMembership ? { members: newMembership, changeTs: new Date() } : undefined), + ) as { members: CallMembership[]; changeTs: Date } | undefined; + + // Re-check when memberships change. + const focus = useMemo(() => session.getActiveFocus(), [memberships, session]); + return ( +
+

+ {session.sessionDescription.application} {session.callId}{" "} + + {active ? _t("devtools|matrix_rtc|session_active") : _t("devtools|matrix_rtc|session_ended")} + +

+ {latestChange && ( +

{`${latestChange.members.map((m) => m.membershipID).join(", ")} ${latestChange.changeTs.toTimeString()}`}

+ )} +

+ {_t("devtools|matrix_rtc|call_intent")}: {session.getConsensusCallIntent() ?? "mixed"} +

+ {focus && ( +
+ {_t("devtools|matrix_rtc|active_focus")} +
{JSON.stringify(focus, undefined, 2)}
+
+ )} + {memberships.length === 0 ? ( +

No members connected.

+ ) : ( + <> +

{_t("common|n_members", { count: memberships.length })}:

+ + + )} + {call && ( + <> +

{_t("voip|element_call")}

+

+ {_t("devtools|matrix_rtc|connection_state")}: {call.connectionState} +

+ {call.participants.size === 0 ? ( +

No call participants.

+ ) : ( + <> +

{_t("devtools|matrix_rtc|participants")}:

+ + + )} +
+ Widget Params +
{JSON.stringify(call.widgetGenerationParameters, undefined, 2)}
+
+ + )} +
+ ); +} + +export default function MatrixRtcDebug({ onBack }: IDevtoolsProps): JSX.Element { + const context = useContext(DevtoolsContext); + const client = useMatrixClientContext(); + const call = useCall(context.room.roomId); + const [sessions, setSession] = useState<{ session: MatrixRTCSession; active: boolean; start: Date }[]>([]); + useTypedEventEmitter( + client.matrixRTC, + MatrixRTCSessionManagerEvents.SessionStarted, + (roomId: string, sesh: MatrixRTCSession) => { + if (context.room.roomId !== roomId) { + return; + } + console.log(roomId, sesh); + setSession((sessions) => [...sessions, { session: sesh, active: true, start: new Date() }]); + }, + ); + useTypedEventEmitter( + client.matrixRTC, + MatrixRTCSessionManagerEvents.SessionEnded, + (roomId: string, sesh: MatrixRTCSession) => { + if (context.room.roomId !== roomId) { + return; + } + const existingSessionData = sessions.find((s) => s.session === sesh); + if (!existingSessionData) { + return; + } + setSession((sessions) => [ + ...sessions.filter((s) => s.session !== sesh), + { ...existingSessionData, active: false }, + ]); + }, + ); + + useEffect(() => { + const existingSession = client.matrixRTC.getActiveRoomSession(context.room); + if (existingSession) { + setSession([{ session: existingSession, active: true, start: new Date() }]); + } + }); + + return ( + + {sessions.length === 0 ? ( +

{_t("devtools|matrix_rtc|no_active_sessions")}

+ ) : ( + sessions.map((s) => ( + + )) + )} +
+ ); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b4405e21903..ab4fe604535 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -826,6 +826,16 @@ "low_bandwidth_mode_description": "Requires compatible homeserver.", "main_timeline": "Main timeline", "manual_device_verification": "Manual device verification", + "matrix_rtc": { + "active_focus": "Active focus", + "call_intent": "Call intent", + "connection_state": "Connection state", + "no_active_sessions": "There are no active Matrix RTC sessions", + "participants": "Participants", + "session_active": "Session active", + "session_ended": "Session ended" + }, + "matrix_rtc_debug": "Matrix RTC debug", "no_receipt_found": "No receipt found", "notification_state": "Notification state is %(notificationState)s", "notifications_debug": "Notifications debug", diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index afd0a85cfc5..4f542cd441a 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -138,7 +138,7 @@ export class StopGapWidgetDriver extends WidgetDriver { const clientUserId = MatrixClientPeg.safeGet().getSafeUserId(); // For the legacy membership type this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Send, "org.matrix.msc3401.call.member", clientUserId) + WidgetEventCapability.forStateEvent(EventDirection.Send, EventType.GroupCallMemberPrefix, clientUserId) .raw, ); const clientDeviceId = MatrixClientPeg.safeGet().getDeviceId(); @@ -148,14 +148,14 @@ export class StopGapWidgetDriver extends WidgetDriver { this.allowedCapabilities.add( WidgetEventCapability.forStateEvent( EventDirection.Send, - "org.matrix.msc3401.call.member", + EventType.GroupCallMemberPrefix, `_${clientUserId}_${clientDeviceId}`, ).raw, ); this.allowedCapabilities.add( WidgetEventCapability.forStateEvent( EventDirection.Send, - "org.matrix.msc3401.call.member", + EventType.GroupCallMemberPrefix, `_${clientUserId}_${clientDeviceId}_m.call`, ).raw, ); @@ -163,20 +163,20 @@ export class StopGapWidgetDriver extends WidgetDriver { this.allowedCapabilities.add( WidgetEventCapability.forStateEvent( EventDirection.Send, - "org.matrix.msc3401.call.member", + EventType.GroupCallMemberPrefix, `${clientUserId}_${clientDeviceId}`, ).raw, ); this.allowedCapabilities.add( WidgetEventCapability.forStateEvent( EventDirection.Send, - "org.matrix.msc3401.call.member", + EventType.GroupCallMemberPrefix, `${clientUserId}_${clientDeviceId}_m.call`, ).raw, ); } this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Receive, "org.matrix.msc3401.call.member").raw, + WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.GroupCallMemberPrefix).raw, ); // for determining auth rules specific to the room version this.allowedCapabilities.add( @@ -193,6 +193,8 @@ export class StopGapWidgetDriver extends WidgetDriver { // MSC4310: Add dev and final event to ease future transition, EventType.RTCDecline, "m.rtc.decline", + // For sticky events + EventType.GroupCallMemberPrefix, ]; for (const eventType of [...sendRoomEvents, ...sendRecvRoomEvents]) this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw);