This commit is contained in:
Half-Shot 2025-10-28 09:46:48 +00:00
parent 5697eec4dc
commit e3ef27c31d
9 changed files with 39 additions and 26 deletions

View File

@ -25,7 +25,11 @@ import {
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { type PermissionChanged as PermissionChangedEvent } from "@matrix-org/analytics-events/types/typescript/PermissionChanged";
import { type IRTCNotificationContent, MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
import {
DefaultCallApplicationSlot,
type IRTCNotificationContent,
MatrixRTCSession,
} from "matrix-js-sdk/src/matrixrtc";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { PosthogAnalytics } from "./PosthogAnalytics";
@ -487,7 +491,10 @@ class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents
const cli = MatrixClientPeg.safeGet();
const room = cli.getRoom(ev.getRoomId());
const thisUserHasConnectedDevice =
room && MatrixRTCSession.callMembershipsForRoom(room).some((m) => m.sender === cli.getUserId());
room &&
MatrixRTCSession.sessionMembershipsForSlot(room, DefaultCallApplicationSlot).some(
(m) => m.sender === cli.getUserId(),
);
if (EventType.RTCNotification === ev.getType() && !thisUserHasConnectedDevice) {
const content = ev.getContent() as IRTCNotificationContent;

View File

@ -296,8 +296,12 @@ export default class RolesRoomSettingsTab extends React.Component<IProps, RolesR
"im.vector.modular.widgets": isSpaceRoom ? null : _td("room_settings|permissions|m.widget"),
};
// MSC3401: Native Group VoIP signaling
if (SettingsStore.getValue("feature_group_calls")) {
if (SettingsStore.getValue("feature_element_call_nextgen")) {
// MSC4143: Matrix RTC Slots
plEventsToLabels[EventType.RTCSlot] = _td("room_settings|permissions|m.rtc.slot");
}
else if (SettingsStore.getValue("feature_group_calls")) {
// MSC3401: Native Group VoIP signaling
plEventsToLabels[ElementCallEventType.name] = _td("room_settings|permissions|m.call");
plEventsToLabels[ElementCallMemberEventType.name] = _td("room_settings|permissions|m.call.member");
}

View File

@ -16,6 +16,7 @@ import { LocalRoom } from "../../models/LocalRoom";
import QuestionDialog from "../../components/views/dialogs/QuestionDialog";
import Modal from "../../Modal";
import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
import { DefaultCallApplicationSlot, RtcSlotEventContent, slotDescriptionToId } from "matrix-js-sdk/src/matrixrtc";
type ElementCallPermissions = {
canStartCall: boolean;
@ -75,6 +76,7 @@ function useLegacyCallPermissions(room: Room| LocalRoom): ElementCallPermissions
};
}
/**
* Hook for adjusting permissions for enabling Element Call.
* This requires MSC4354 (Sticky events) to work.
@ -83,10 +85,11 @@ function useLegacyCallPermissions(room: Room| LocalRoom): ElementCallPermissions
const useSlotsCallPermissions = (
room: Room | LocalRoom,
): ElementCallPermissions => {
const slotId = slotDescriptionToId({id: "", application: "m.call"});
const [maySendSlot, hasRoomSlot] = useRoomState(room, () => [
room.currentState.mayClientSendStateEvent("org.matrix.msc4143.rtc.slot", room.client),
room.currentState.mayClientSendStateEvent(EventType.RTCSlot, room.client),
// TODO: Replace with proper const
room.currentState.getStateEvents("org.matrix.msc4143.rtc.slot", "m.call#ROOM")?.getContent()?.application?.type === 'm.call',
room.currentState.getStateEvents(EventType.RTCSlot, slotId)?.getContent<RtcSlotEventContent>().application.type === DefaultCallApplicationSlot.application.type,
]);
// TODO: Check that we are allowed to create audio/video calls, when the telephony PR lands.
@ -109,17 +112,14 @@ const useSlotsCallPermissions = (
return false;
}
await room.client.sendStateEvent(room.roomId, "org.matrix.msc4143.rtc.slot", {
"application": {
"type": "m.call",
// m.call.id is not specified here, as we want this slot to be general purpose
}
}, "m.call#ROOM");
"application": DefaultCallApplicationSlot.application
}, slotId);
return true;
}, [room, hasRoomSlot]);
const removeElementCallSlot = useCallback(async (): Promise<void> => {
if (hasRoomSlot) {
await room.client.sendStateEvent(room.roomId, "org.matrix.msc4143.rtc.slot", { }, "m.call#ROOM");
await room.client.sendStateEvent(room.roomId, "org.matrix.msc4143.rtc.slot", { }, slotId);
}
}, [room, hasRoomSlot]);
@ -142,6 +142,6 @@ export function useElementCallPermissions (room: Room | LocalRoom): ElementCallP
// We load both, to avoid conditional hook rendering on settings change.
const slotsPerms = useSlotsCallPermissions(room);
const legacyPerms = useLegacyCallPermissions(room);
const isMSC4354Enabled = useFeatureEnabled("feature_element_call_msc4354");
const isMSC4354Enabled = useFeatureEnabled("feature_element_call_nextgen");
return isMSC4354Enabled ? slotsPerms : legacyPerms;
}

View File

@ -1500,8 +1500,8 @@
"experimental_description": "Feeling experimental? Try out our latest ideas in development. These features are not finalised; they may be unstable, may change, or may be dropped altogether. <a>Learn more</a>.",
"experimental_section": "Early previews",
"extended_profiles_msc_support": "Requires your server to support MSC4133",
"feature_element_call_msc4354": "Use sticky events for Element Call",
"feature_element_call_msc4354_msc_support": "Requires your server to support MSC4354 (Sticky Events)",
"feature_element_call_nextgen": "Enable MultiSFU support for Element Call",
"feature_element_call_nextgen_msc_support": "Requires your server to support MSC4354 (Sticky Events)",
"feature_disable_call_per_sender_encryption": "Disable per-sender encryption for Element Call",
"feature_wysiwyg_composer_description": "Use rich text instead of Markdown in the message composer.",
"group_calls": "New group call experience",
@ -2369,6 +2369,7 @@
"m.room.tombstone": "Upgrade the room",
"m.room.topic": "Change topic",
"m.room.topic_space": "Change description",
"m.rtc.slot": "Enable or disable %(brand)s calls",
"m.space.child": "Manage rooms in this space",
"m.widget": "Modify widgets",
"muted_users_section": "Muted Users",

View File

@ -709,8 +709,8 @@ export class ElementCall extends Call {
params.append("allowVoipWithNoMedia", "true");
}
if (SettingsStore.getValue("feature_element_call_msc4354")) {
params.append("preferStickyEvents", "true");
if (SettingsStore.getValue("feature_element_call_nextgen")) {
params.append("multiSFU", "true");
}
// Set custom fonts

View File

@ -222,7 +222,7 @@ export interface Settings {
"feature_sliding_sync": IBaseSetting<boolean>;
"feature_simplified_sliding_sync": IFeature;
"feature_element_call_video_rooms": IFeature;
"feature_element_call_msc4354": IFeature;
"feature_element_call_nextgen": IFeature;
"feature_group_calls": IFeature;
"feature_disable_call_per_sender_encryption": IFeature;
"feature_allow_screen_share_only_mode": IFeature;
@ -630,18 +630,18 @@ export const SETTINGS: Settings = {
controller: new ReloadOnChangeController(),
default: false,
},
"feature_element_call_msc4354": {
"feature_element_call_nextgen": {
isFeature: true,
labsGroup: LabGroup.VoiceAndVideo,
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
supportedLevelsAreOrdered: true,
displayName: _td("labs|feature_element_call_msc4354"),
displayName: _td("labs|feature_element_call_nextgen"),
controller: new ServerSupportUnstableFeatureController(
"feature_element_call_msc4354",
"feature_element_call_nextgen",
defaultWatchManager,
[[UNSTABLE_MSC4354_STICKY_EVENTS]],
undefined,
_td("labs|feature_element_call_msc4354_msc_support"),
_td("labs|feature_element_call_nextgen_msc_support"),
false,
),
default: false,

View File

@ -433,7 +433,7 @@ describe("Notifier", () => {
});
it("should not show toast when group call is already connected", () => {
const spyCallMemberships = jest.spyOn(MatrixRTCSession, "callMembershipsForRoom").mockReturnValue([
const spyCallMemberships = jest.spyOn(MatrixRTCSession, "sessionMembershipsForSlot").mockReturnValue([
new CallMembership(
mkEvent({
event: true,

View File

@ -125,7 +125,7 @@ describe("createRoom", () => {
it("sets up Element video rooms correctly", async () => {
const userId = client.getUserId()!;
const createCallSpy = jest.spyOn(ElementCall, "create");
const callMembershipSpy = jest.spyOn(MatrixRTCSession, "callMembershipsForRoom");
const callMembershipSpy = jest.spyOn(MatrixRTCSession, "sessionMembershipsForSlot");
callMembershipSpy.mockReturnValue([]);
const roomId = await createRoom(client, { roomType: RoomType.UnstableCall });

View File

@ -25,6 +25,7 @@ import {
MatrixRTCSessionManagerEvents,
MatrixRTCSession,
MatrixRTCSessionEvent,
DefaultCallApplicationSlot,
} from "matrix-js-sdk/src/matrixrtc";
import type { Mocked } from "jest-mock";
@ -933,8 +934,8 @@ describe("ElementCall", () => {
setRoomMembers(["@user:example.com", "@user2:example.com", "@user4:example.com"]);
});
it("don't sent notify event if there are existing room call members", async () => {
jest.spyOn(MatrixRTCSession, "callMembershipsForRoom").mockReturnValue([
{ application: "m.call", callId: "" } as unknown as CallMembership,
jest.spyOn(MatrixRTCSession, "sessionMembershipsForSlot").mockReturnValue([
DefaultCallApplicationSlot as unknown as CallMembership,
]);
const sendEventSpy = jest.spyOn(room.client, "sendEvent");
ElementCall.create(room);