Add devtools for Rtc

This commit is contained in:
Half-Shot 2025-10-03 17:29:07 +01:00
parent e83ddbc98a
commit e668d4461e
4 changed files with 190 additions and 6 deletions

View File

@ -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<Category, [label: TranslationKey, tool: Tool][]> = {
[_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],

View File

@ -0,0 +1,170 @@
/*
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 { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
import { _t, _td } from "../../../../languageHandler.tsx";
import BaseTool, { DevtoolsContext, type IDevtoolsProps } from "./BaseTool.tsx";
import { useTypedEventEmitter, useTypedEventEmitterState } from "../../../../hooks/useEventEmitter.ts";
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager.ts";
import { CallMembership, MatrixRTCSession, MatrixRTCSessionEvent } from "matrix-js-sdk/src/matrixrtc/index.ts";
import { Badge } from "@vector-im/compound-web";
import { useCall } from "../../../../hooks/useCall.ts";
import { 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]);
return (
<section>
<h3>
{session.sessionDescription.application} {session.callId}{" "}
<Badge kind={active ? "green" : "grey"}>
{active ? _t("devtools|matrix_rtc|session_active") : _t("devtools|matrix_rtc|session_ended")}
</Badge>
</h3>
{latestChange && (
<p>{`${latestChange.members.map((m) => m.membershipID).join(", ")} ${latestChange.changeTs.toTimeString()}`}</p>
)}
<p>
{_t("devtools|matrix_rtc|call_intent")}: {session.getConsensusCallIntent() ?? "mixed"}
</p>
{focus && (
<details>
<summary>{_t("devtools|matrix_rtc|active_focus")}</summary>
<pre>{JSON.stringify(focus, undefined, 2)}</pre>
</details>
)}
{memberships.length === 0 ? (
<p>No members connected.</p>
) : (
<>
<p>{_t("common|n_members", { count: memberships.length })}:</p>
<ul>
{memberships.map((member) => (
<li>
<code>
{member.sender} {member.deviceId}
</code>
{member.isExpired() && "(expired)"}{" "}
<details>
<summary>Inspect</summary>
<pre>{JSON.stringify(member, undefined, 2)}</pre>
</details>
</li>
))}
</ul>
</>
)}
{call && (
<>
<h4>{_t("voip|element_call")}</h4>
<p>
{_t("devtools|matrix_rtc|connection_state")}: {call.connectionState}
</p>
{call.participants.size === 0 ? (
<p>No call participants.</p>
) : (
<>
<p>{_t("devtools|matrix_rtc|participants")}:</p>
<ul>
{[...call.participants.entries()].map(([roomMember, deviceIds]) => (
<li>
{roomMember.userId} {[...deviceIds].join(", ")}
</li>
))}
</ul>
</>
)}
<details>
<summary>Widget Params</summary>
<pre>{JSON.stringify(call.widgetGenerationParameters, undefined, 2)}</pre>
</details>
</>
)}
</section>
);
}
export default function MatrixRtcDebug({ onBack }: IDevtoolsProps): JSX.Element {
const context = useContext(DevtoolsContext);
const client = useMatrixClientContext();
const call = useCall(context.room.roomId);
let [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 (
<BaseTool onBack={onBack}>
{sessions.length === 0 ? (
<p>{_t("devtools|matrix_rtc|no_active_sessions")}</p>
) : (
sessions.map((s) => (
<MatrixRTCSessionInfo
{...s}
call={(call as ElementCall)?.session === s.session ? (call as ElementCall) : undefined}
key={s.start.toString()}
/>
))
)}
</BaseTool>
);
}

View File

@ -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 <strong>%(notificationState)s</strong>",
"notifications_debug": "Notifications debug",

View File

@ -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);