mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-12 07:56:18 +02:00
Complete first pass on call permissions
This commit is contained in:
parent
4b80d23a1f
commit
0656fff3cb
@ -28,6 +28,7 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
||||
const onToggle = useCallback(() => {
|
||||
setBusy(true)
|
||||
void (async () => {
|
||||
console.log({canStartCall, canAdjustCallPermissions});
|
||||
try {
|
||||
if (canStartCall) {
|
||||
await disableCallInRoom();
|
||||
@ -38,7 +39,6 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
||||
setBusy(false);
|
||||
}
|
||||
})();
|
||||
|
||||
}, [canStartCall, enableCallInRoom, disableCallInRoom]);
|
||||
|
||||
const brand = SdkConfig.get("element_call").brand ?? DEFAULTS.element_call.brand;
|
||||
|
||||
@ -6,65 +6,98 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { EventType, JoinRule, RoomState, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import type React from "react";
|
||||
import { useFeatureEnabled } from "../useSettings";
|
||||
import { useRoomState } from "../useRoomState";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { ElementCallMemberEventType } from "../../call-types";
|
||||
import { ElementCallEventType, ElementCallMemberEventType } from "../../call-types";
|
||||
import { LocalRoom } from "../../models/LocalRoom";
|
||||
import QuestionDialog from "../../components/views/dialogs/QuestionDialog";
|
||||
import Modal from "../../Modal";
|
||||
import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
|
||||
|
||||
type ElementCallPermissions = {
|
||||
canStartCall: boolean;
|
||||
canAdjustCallPermissions: boolean;
|
||||
enableCallInRoom(): void;
|
||||
disableCallInRoom(): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function useLegacyCallPermissions(room: Room | LocalRoom): ElementCallPermissions {
|
||||
/**
|
||||
* Hook for adjusting permissions for enabling Element Call.
|
||||
* This uses the legacy state controlled system.
|
||||
* @param room the room to track
|
||||
*/
|
||||
function useLegacyCallPermissions(room: Room| LocalRoom): ElementCallPermissions {
|
||||
const [powerLevelContent, maySend, elementCallEnabled] = useRoomState(
|
||||
room,
|
||||
useCallback(
|
||||
(state: RoomState) => {
|
||||
const content = state
|
||||
?.getStateEvents(EventType.RoomPowerLevels, "")
|
||||
?.getContent<RoomPowerLevelsEventContent>();
|
||||
return [
|
||||
content ?? {},
|
||||
state?.maySendStateEvent(EventType.RoomPowerLevels, room.client.getSafeUserId()),
|
||||
content?.events?.[ElementCallMemberEventType.name] === 0
|
||||
] as const;
|
||||
},
|
||||
[room.client],
|
||||
),
|
||||
);
|
||||
|
||||
const enableCallInRoom = useCallback(() => {
|
||||
console.log('Enabling call');
|
||||
const newContent = { events: {}, ...powerLevelContent };
|
||||
const userLevel = newContent.events[EventType.RoomMessage] ?? powerLevelContent.users_default ?? 0;
|
||||
const moderatorLevel = powerLevelContent.kick ?? 50;
|
||||
const isPublic = room.getJoinRule() === JoinRule.Public;
|
||||
console.log(newContent.events);
|
||||
newContent.events[ElementCallEventType.name] = isPublic ? moderatorLevel : userLevel;
|
||||
newContent.events[ElementCallMemberEventType.name] = userLevel;
|
||||
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, newContent);
|
||||
},[room, powerLevelContent]);
|
||||
|
||||
|
||||
const disableCallInRoom = useCallback(() => {
|
||||
console.log('Disabling call');
|
||||
const newContent = { events: {}, ...powerLevelContent };
|
||||
const adminLevel = newContent.events[EventType.RoomPowerLevels] ?? powerLevelContent.state_default ?? 100;
|
||||
newContent.events[ElementCallEventType.name] = adminLevel;
|
||||
newContent.events[ElementCallMemberEventType.name] = adminLevel;
|
||||
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, newContent);
|
||||
},[room, powerLevelContent]);
|
||||
|
||||
return {
|
||||
canStartCall: true,
|
||||
canAdjustCallPermissions: true,
|
||||
enableCallInRoom: () => {},
|
||||
disableCallInRoom: () => {},
|
||||
}
|
||||
canStartCall: elementCallEnabled,
|
||||
canAdjustCallPermissions: maySend,
|
||||
enableCallInRoom,
|
||||
disableCallInRoom,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for adjusting permissions for enabling Element Call.
|
||||
* This requires MSC4354 (Sticky events) to work.
|
||||
* @param room the room to track
|
||||
* @returns the call button attributes for the given room
|
||||
*/
|
||||
const useSlotsCallPermissions = (
|
||||
room: Room | LocalRoom,
|
||||
): ElementCallPermissions => {
|
||||
// Use sticky events
|
||||
const isMSC4354Enabled = useFeatureEnabled("feature_element_call_msc4354");
|
||||
const [mayCreateElementCallState, maySendSlot, hasRoomSlot] = useRoomState(room, () => [
|
||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||
room.currentState.mayClientSendStateEvent(ElementCallMemberEventType.name, room.client),
|
||||
const [maySendSlot, hasRoomSlot] = useRoomState(room, () => [
|
||||
room.currentState.mayClientSendStateEvent("org.matrix.msc4143.rtc.slot", room.client),
|
||||
// TODO: Replace with proper const
|
||||
room.currentState.getStateEvents("org.matrix.msc4143.rtc.slot", "m.call#ROOM")?.getContent()?.application?.type === 'm.call',
|
||||
]);
|
||||
|
||||
// TODO: Check that we are allowed to create audio/video calls, when the telephony PR lands.
|
||||
const hasElementCallSlot = !isMSC4354Enabled || hasRoomSlot;
|
||||
|
||||
const mayCreateElementCalls = useMemo(() => {
|
||||
if (isMSC4354Enabled) {
|
||||
return hasElementCallSlot || maySendSlot
|
||||
}
|
||||
return mayCreateElementCallState;
|
||||
}, [isMSC4354Enabled, mayCreateElementCallState, maySendSlot, hasElementCallSlot]);
|
||||
|
||||
const createElementCallSlot = useCallback(async (): Promise<boolean> => {
|
||||
if (hasElementCallSlot) {
|
||||
console.log('createElementCallSlot', { hasRoomSlot });
|
||||
if (hasRoomSlot) {
|
||||
return true;
|
||||
}
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
@ -89,27 +122,34 @@ const useSlotsCallPermissions = (
|
||||
}
|
||||
}, "m.call#ROOM");
|
||||
return true;
|
||||
}, [room, hasElementCallSlot]);
|
||||
}, [room, hasRoomSlot]);
|
||||
|
||||
const removeElementCallSlot = useCallback(async (): Promise<void> => {
|
||||
if (hasElementCallSlot) {
|
||||
console.log('removeElementCallSlot', { hasRoomSlot });
|
||||
if (hasRoomSlot) {
|
||||
await room.client.sendStateEvent(room.roomId, "org.matrix.msc4143.rtc.slot", { }, "m.call#ROOM");
|
||||
}
|
||||
}, [room, hasElementCallSlot]);
|
||||
}, [room, hasRoomSlot]);
|
||||
|
||||
|
||||
return {
|
||||
canStartCall: mayCreateElementCalls,
|
||||
canStartCall: hasRoomSlot,
|
||||
canAdjustCallPermissions: maySendSlot,
|
||||
enableCallInRoom: createElementCallSlot,
|
||||
disableCallInRoom: removeElementCallSlot,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get and set whether an Element Call session may take place. If MSC4354 is enabled,
|
||||
* this will use the new slots flow. Otherwise, this will fallback to the older state-based permissions.
|
||||
* @param room
|
||||
* @returns
|
||||
*/
|
||||
export function useElementCallPermissions (room: Room | LocalRoom): ElementCallPermissions {
|
||||
// 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");
|
||||
if (isMSC4354Enabled) {
|
||||
return useSlotsCallPermissions(room);
|
||||
}
|
||||
return useLegacyCallPermissions(room);
|
||||
return isMSC4354Enabled ? slotsPerms : legacyPerms;
|
||||
}
|
||||
@ -35,10 +35,8 @@ import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||
import { isVideoRoom } from "../../utils/video-rooms";
|
||||
import { UIFeature } from "../../settings/UIFeature";
|
||||
import { type InteractionName } from "../../PosthogTrackers";
|
||||
import { ElementCallMemberEventType } from "../../call-types";
|
||||
import { LocalRoom, LocalRoomState } from "../../models/LocalRoom";
|
||||
import QuestionDialog from "../../components/views/dialogs/QuestionDialog";
|
||||
import Modal from "../../Modal";
|
||||
import { useElementCallPermissions } from "./useElementCallPermissions";
|
||||
|
||||
export enum PlatformCallType {
|
||||
ElementCall,
|
||||
@ -75,6 +73,7 @@ export const getPlatformCallTypeProps = (
|
||||
const enum State {
|
||||
NoCall,
|
||||
NoPermission,
|
||||
CallingDisabled,
|
||||
Unpinned,
|
||||
Ongoing,
|
||||
NotJoined,
|
||||
@ -99,11 +98,6 @@ export const useRoomCall = (
|
||||
callOptions: PlatformCallType[];
|
||||
showVideoCallButton: boolean;
|
||||
showVoiceCallButton: boolean;
|
||||
|
||||
hasElementCallSlot: boolean;
|
||||
canAdjustElementCallSlot: boolean;
|
||||
createElementCallSlot(): void;
|
||||
removeElementCallSlot(): void;
|
||||
} => {
|
||||
// settings
|
||||
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
|
||||
@ -112,8 +106,7 @@ export const useRoomCall = (
|
||||
const useElementCallExclusively = useMemo(() => {
|
||||
return SdkConfig.get("element_call").use_exclusively;
|
||||
}, []);
|
||||
// Use sticky events
|
||||
const isMSC4354Enabled = useFeatureEnabled("feature_element_call_msc4354");
|
||||
const { canStartCall: mayCreateElementCalls } = useElementCallPermissions(room);
|
||||
|
||||
const hasLegacyCall = useEventEmitterState(
|
||||
LegacyCallHandler.instance,
|
||||
@ -141,24 +134,10 @@ export const useRoomCall = (
|
||||
// room
|
||||
const memberCount = useRoomMemberCount(room);
|
||||
|
||||
const [mayEditWidgets, mayCreateElementCallState, maySendSlot, hasRoomSlot] = useRoomState<[boolean, boolean, boolean, boolean]>(room, () => [
|
||||
const [mayEditWidgets] = useRoomState<[boolean]>(room, () => [
|
||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||
room.currentState.mayClientSendStateEvent(ElementCallMemberEventType.name, room.client),
|
||||
room.currentState.mayClientSendStateEvent("org.matrix.msc4143.rtc.slot", room.client),
|
||||
// TODO: Replace with proper const
|
||||
room.currentState.getStateEvents("org.matrix.msc4143.rtc.slot", "m.call#ROOM")?.getContent()?.application?.type === 'm.call'
|
||||
]);
|
||||
|
||||
// TODO: Check that we are allowed to create audio/video calls, when the telephony PR lands.
|
||||
const hasElementCallSlot = !isMSC4354Enabled || hasRoomSlot;
|
||||
|
||||
const mayCreateElementCalls = useMemo(() => {
|
||||
if (isMSC4354Enabled) {
|
||||
return hasElementCallSlot || maySendSlot
|
||||
}
|
||||
return mayCreateElementCallState;
|
||||
}, [isMSC4354Enabled, mayCreateElementCallState, maySendSlot, hasElementCallSlot]);
|
||||
|
||||
// The options provided to the RoomHeader.
|
||||
// If there are multiple options, the user will be prompted to choose.
|
||||
const callOptions = useMemo((): PlatformCallType[] => {
|
||||
@ -172,7 +151,7 @@ export const useRoomCall = (
|
||||
if (hasGroupCall || mayCreateElementCalls) {
|
||||
options.push(PlatformCallType.ElementCall);
|
||||
}
|
||||
if (useElementCallExclusively && !hasJitsiWidget) {
|
||||
if (useElementCallExclusively && mayCreateElementCalls && !hasJitsiWidget) {
|
||||
return [PlatformCallType.ElementCall];
|
||||
}
|
||||
}
|
||||
@ -229,9 +208,14 @@ export const useRoomCall = (
|
||||
return State.Ongoing;
|
||||
}
|
||||
|
||||
if (callOptions.length === 0 && !mayCreateElementCalls) {
|
||||
return State.CallingDisabled;
|
||||
}
|
||||
|
||||
if (!callOptions.includes(PlatformCallType.LegacyCall) && !mayCreateElementCalls && !mayEditWidgets) {
|
||||
return State.NoPermission;
|
||||
}
|
||||
|
||||
return State.NoCall;
|
||||
}, [
|
||||
callOptions,
|
||||
@ -246,40 +230,6 @@ export const useRoomCall = (
|
||||
room.roomId,
|
||||
]);
|
||||
|
||||
const createElementCallSlot = useCallback(async (): Promise<boolean> => {
|
||||
if (hasElementCallSlot) {
|
||||
return true;
|
||||
}
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: "Do you want to allow calls in this room?",
|
||||
description: (
|
||||
<p>
|
||||
This room doesn't currently permit calling. If you continue, other users will
|
||||
be able to place calls in the future. You may turn this off in the Room Settings.
|
||||
</p>
|
||||
),
|
||||
button: _t("action|continue"),
|
||||
});
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
await room.client.sendStateEvent(room.roomId, "org.matrix.msc4143.rtc.slot", {
|
||||
"application": {
|
||||
"type": "m.call",
|
||||
//
|
||||
"m.call.id": "i_dont_know_what_this_should_be",
|
||||
}
|
||||
}, "m.call#ROOM");
|
||||
return true;
|
||||
}, [room, hasElementCallSlot]);
|
||||
|
||||
const removeElementCallSlot = useCallback(async (): Promise<void> => {
|
||||
if (hasElementCallSlot) {
|
||||
await room.client.sendStateEvent(room.roomId, "org.matrix.msc4143.rtc.slot", { }, "m.call#ROOM");
|
||||
}
|
||||
}, [room, hasElementCallSlot]);
|
||||
|
||||
const voiceCallClick = useCallback(
|
||||
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||
evt?.stopPropagation();
|
||||
@ -287,13 +237,11 @@ export const useRoomCall = (
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top);
|
||||
} else {
|
||||
void (async () => {
|
||||
if (callPlatformType !== PlatformCallType.ElementCall || await createElementCallSlot()) {
|
||||
await placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined);
|
||||
}
|
||||
await placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined);
|
||||
})();
|
||||
}
|
||||
},
|
||||
[promptPinWidget, room, widget, createElementCallSlot],
|
||||
[promptPinWidget, room, widget],
|
||||
);
|
||||
const videoCallClick = useCallback(
|
||||
(evt: React.MouseEvent | undefined, callPlatformType: PlatformCallType): void => {
|
||||
@ -304,18 +252,19 @@ export const useRoomCall = (
|
||||
// If we have pressed shift then always skip the lobby, otherwise `undefined` will defer
|
||||
// to the defaults of the call implementation.
|
||||
void (async () => {
|
||||
if (callPlatformType !== PlatformCallType.ElementCall || await createElementCallSlot()) {
|
||||
await placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined);
|
||||
}
|
||||
await placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined);
|
||||
})();
|
||||
}
|
||||
},
|
||||
[widget, promptPinWidget, room, createElementCallSlot],
|
||||
[widget, promptPinWidget, room],
|
||||
);
|
||||
|
||||
let voiceCallDisabledReason: string | null;
|
||||
let videoCallDisabledReason: string | null;
|
||||
switch (state) {
|
||||
case State.CallingDisabled:
|
||||
voiceCallDisabledReason = videoCallDisabledReason = _t("voip|disabled_branded_call", { brand: SdkConfig.get("element_call").brand });
|
||||
break;
|
||||
case State.NoPermission:
|
||||
voiceCallDisabledReason = _t("voip|disabled_no_perms_start_voice_call");
|
||||
videoCallDisabledReason = _t("voip|disabled_no_perms_start_video_call");
|
||||
@ -353,6 +302,8 @@ export const useRoomCall = (
|
||||
hideVideoCallButton = true;
|
||||
}
|
||||
|
||||
console.log("useRoomCall", { voiceCallDisabledReason, videoCallDisabledReason, callOptions, hideVideoCallButton, hideVoiceCallButton, mayCreateElementCalls });
|
||||
|
||||
/**
|
||||
* We've gone through all the steps
|
||||
*/
|
||||
@ -368,9 +319,5 @@ export const useRoomCall = (
|
||||
callOptions,
|
||||
showVoiceCallButton: !hideVoiceCallButton,
|
||||
showVideoCallButton: !hideVideoCallButton,
|
||||
hasElementCallSlot,
|
||||
canAdjustElementCallSlot: maySendSlot,
|
||||
createElementCallSlot,
|
||||
removeElementCallSlot,
|
||||
};
|
||||
};
|
||||
|
||||
@ -4008,6 +4008,7 @@
|
||||
"disable_microphone": "Mute microphone",
|
||||
"disabled_no_perms_start_video_call": "You do not have permission to start video calls",
|
||||
"disabled_no_perms_start_voice_call": "You do not have permission to start voice calls",
|
||||
"disabled_branded_call": "Enable %(brand)s in the room settings",
|
||||
"disabled_ongoing_call": "Ongoing call",
|
||||
"element_call": "Element Call",
|
||||
"enable_camera": "Turn on camera",
|
||||
|
||||
@ -641,6 +641,7 @@ export const SETTINGS: Settings = {
|
||||
[[UNSTABLE_MSC4354_STICKY_EVENTS]],
|
||||
undefined,
|
||||
_td("labs|feature_element_call_msc4354_msc_support"),
|
||||
false,
|
||||
),
|
||||
default: false,
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user