Files
cinny/src/app/plugins/call/utils.ts
T
jaredandClaude Opus 5 81a6d9c9ed
CI / Build & Quality Checks (push) Successful in 2m6s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 9s
CI / Trigger Desktop Build (push) Successful in 10s
CI / Playwright smoke (e2e) (push) Successful in 1m42s
fix(call): serve RTC transports to Element Call over MSC4515 — calls work again
Since the upstream v0.25.0 merge, Element Call in widget mode no longer
reads .well-known for its LiveKit transport; it asks the HOST via the
widget API (org.matrix.msc4515.get_rtc_transports, capability
org.matrix.msc4515.rtc_transports). cinny never granted the capability
nor implemented WidgetDriver.getRtcTransports(), so discovery returned
nothing and every join failed with "Call is not supported"
(MISSING_MATRIX_RTC_TRANSPORT) — the [LocalMembership] Multiple
Transport Errors line in the browser console.

- matrix-widget-api 1.17.0 -> 1.18.0 (adds MSC4515; also changes the
  sendDelayedEvent driver signature, adapted — parent delay ids were
  removed from the draft).
- Grant MSC4515RtcTransports in getCallCapabilities.
- CallWidgetDriver.getRtcTransports(): homeserver /rtc/transports
  (MSC4143) first, then the .well-known org.matrix.msc4143.rtc_foci list
  (what matrix.lotusguild.org advertises today). Unit-tested.

Server side needs no change: the well-known already carries the livekit
focus and the JWT service answers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-14 19:11:41 -04:00

119 lines
3.7 KiB
TypeScript

import {
type Capability,
EventDirection,
MatrixCapabilities,
WidgetEventCapability,
} from 'matrix-widget-api';
import { EventType } from 'matrix-js-sdk';
export function getCallCapabilities(
roomId: string,
userId: string,
deviceId: string,
): Set<Capability> {
const capabilities: Set<Capability> = new Set();
capabilities.add(MatrixCapabilities.Screenshots);
capabilities.add(MatrixCapabilities.AlwaysOnScreen);
capabilities.add(MatrixCapabilities.MSC4039UploadFile);
capabilities.add(MatrixCapabilities.MSC4039DownloadFile);
capabilities.add(MatrixCapabilities.MSC3846TurnServers);
capabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent);
capabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent);
// MSC4515: Element Call >= 0.22 in widget mode discovers its LiveKit
// transport by asking the host (it no longer reads .well-known itself), so
// without this capability every call fails with MISSING_MATRIX_RTC_TRANSPORT.
capabilities.add(MatrixCapabilities.MSC4515RtcTransports);
capabilities.add(`org.matrix.msc2762.timeline:${roomId}`);
capabilities.add(`org.matrix.msc2762.state:${roomId}`);
capabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomMember).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, 'org.matrix.msc3401.call').raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomEncryption).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomName).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(
EventDirection.Send,
'org.matrix.msc3401.call.member',
userId,
).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(
EventDirection.Send,
'org.matrix.msc3401.call.member',
`_${userId}_${deviceId}_m.call`,
).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(
EventDirection.Send,
'org.matrix.msc3401.call.member',
`${userId}_${deviceId}_m.call`,
).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(
EventDirection.Send,
'org.matrix.msc3401.call.member',
`_${userId}_${deviceId}`,
).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(
EventDirection.Send,
'org.matrix.msc3401.call.member',
`${userId}_${deviceId}`,
).raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, 'org.matrix.msc3401.call.member')
.raw,
);
capabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomCreate).raw,
);
[
'io.element.call.encryption_keys',
'org.matrix.rageshake_request',
EventType.Reaction,
EventType.RoomRedaction,
'io.element.call.reaction',
'org.matrix.msc4075.rtc.notification',
'org.matrix.msc4310.rtc.decline',
].forEach((type) => {
capabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, type).raw);
capabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Receive, type).raw);
});
[
EventType.CallInvite,
EventType.CallCandidates,
EventType.CallAnswer,
EventType.CallHangup,
EventType.CallReject,
EventType.CallSelectAnswer,
EventType.CallNegotiate,
EventType.CallSDPStreamMetadataChanged,
EventType.CallSDPStreamMetadataChangedPrefix,
EventType.CallReplaces,
EventType.CallEncryptionKeysPrefix,
].forEach((type) => {
capabilities.add(WidgetEventCapability.forToDeviceEvent(EventDirection.Send, type).raw);
capabilities.add(WidgetEventCapability.forToDeviceEvent(EventDirection.Receive, type).raw);
});
return capabilities;
}