Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/views/dialogs/DevtoolsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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],
Expand Down
174 changes: 174 additions & 0 deletions src/components/views/dialogs/devtools/MatrixRtcDebug.tsx
Original file line number Diff line number Diff line change
@@ -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]);

Check failure on line 46 in src/components/views/dialogs/devtools/MatrixRtcDebug.tsx

View workflow job for this annotation

GitHub Actions / ESLint

React Hook useMemo has an unnecessary dependency: 'memberships'. Either exclude it or remove the dependency array
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 key={member.membershipID}>
<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 key={roomMember.userId}>
{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);
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(() => {

Check failure on line 152 in src/components/views/dialogs/devtools/MatrixRtcDebug.tsx

View workflow job for this annotation

GitHub Actions / ESLint

React Hook useEffect contains a call to 'setSession'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [client.matrixRTC, context.room] as a second argument to the useEffect Hook
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>
);
}
10 changes: 10 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 8 additions & 6 deletions src/stores/widgets/StopGapWidgetDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -148,35 +148,35 @@ 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,
);
// Version with no leading underscore, for room versions whose auth rules allow it
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(
Expand All @@ -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);
Expand Down
Loading