Delegate the sending of call notifications to Element Call (#30507)
* Move Element Call event types to a more appropriate file To remove the potential for import cycles in src/models/Call.ts, which I was accidentally creating when I tried to reference data from the RoomListStore in the ElementCall class. * Make sure ElementCall tests clean up the call object * Upgrade Element Call to v0.14.1 * Delegate the sending of call notifications to Element Call As of Element Call version 0.14.0, the widget is now capable of sending call notifications itself if we just request this with the sendNotificationType URL parameter. This makes Element Web's group call code a little bit more succinct. * Fix createRoom test
This commit is contained in:
+1
-1
@@ -186,7 +186,7 @@
|
|||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||||
"@element-hq/element-call-embedded": "0.13.1",
|
"@element-hq/element-call-embedded": "0.14.1",
|
||||||
"@element-hq/element-web-playwright-common": "^1.4.4",
|
"@element-hq/element-web-playwright-common": "^1.4.4",
|
||||||
"@peculiar/webcrypto": "^1.4.3",
|
"@peculiar/webcrypto": "^1.4.3",
|
||||||
"@playwright/test": "^1.50.1",
|
"@playwright/test": "^1.50.1",
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog";
|
|||||||
import AccessibleButton from "./components/views/elements/AccessibleButton";
|
import AccessibleButton from "./components/views/elements/AccessibleButton";
|
||||||
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||||
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||||
import { ElementCall } from "./models/Call";
|
|
||||||
import { getSenderName } from "./utils/event/getSenderName";
|
import { getSenderName } from "./utils/event/getSenderName";
|
||||||
import PosthogTrackers from "./PosthogTrackers.ts";
|
import PosthogTrackers from "./PosthogTrackers.ts";
|
||||||
|
import { ElementCallEventType } from "./call-types.ts";
|
||||||
|
|
||||||
function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string {
|
function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string {
|
||||||
const roomId = event.getRoomId();
|
const roomId = event.getRoomId();
|
||||||
@@ -922,7 +922,7 @@ for (const evType of ALL_RULE_TYPES) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add both stable and unstable m.call events
|
// Add both stable and unstable m.call events
|
||||||
for (const evType of ElementCall.CALL_EVENT_TYPE.names) {
|
for (const evType of ElementCallEventType.names) {
|
||||||
stateHandlers[evType] = textForCallEvent;
|
stateHandlers[evType] = textForCallEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ 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.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { EventType } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { NamespacedValue } from "matrix-js-sdk/src/NamespacedValue";
|
||||||
|
|
||||||
export const JitsiCallMemberEventType = "io.element.video.member";
|
export const JitsiCallMemberEventType = "io.element.video.member";
|
||||||
|
|
||||||
export interface JitsiCallMemberContent {
|
export interface JitsiCallMemberContent {
|
||||||
@@ -14,3 +17,9 @@ export interface JitsiCallMemberContent {
|
|||||||
// Time at which this state event should be considered stale
|
// Time at which this state event should be considered stale
|
||||||
expires_ts: number;
|
expires_ts: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Element Call no longer sends this event type; it only exists to support timeline rendering of
|
||||||
|
// group calls from a previous iteration of the group VoIP MSCs (MSC3401) which used it.
|
||||||
|
export const ElementCallEventType = new NamespacedValue(null, EventType.GroupCallPrefix);
|
||||||
|
|
||||||
|
export const ElementCallMemberEventType = new NamespacedValue(null, EventType.GroupCallMemberPrefix);
|
||||||
|
|||||||
@@ -76,13 +76,13 @@ import ThreadSummary, { ThreadMessagePreview } from "./ThreadSummary";
|
|||||||
import { ReadReceiptGroup } from "./ReadReceiptGroup";
|
import { ReadReceiptGroup } from "./ReadReceiptGroup";
|
||||||
import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
|
import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
|
||||||
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
|
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
|
||||||
import { ElementCall } from "../../../models/Call";
|
|
||||||
import { UnreadNotificationBadge } from "./NotificationBadge/UnreadNotificationBadge";
|
import { UnreadNotificationBadge } from "./NotificationBadge/UnreadNotificationBadge";
|
||||||
import { EventTileThreadToolbar } from "./EventTile/EventTileThreadToolbar";
|
import { EventTileThreadToolbar } from "./EventTile/EventTileThreadToolbar";
|
||||||
import { getLateEventInfo } from "../../structures/grouper/LateEventGrouper";
|
import { getLateEventInfo } from "../../structures/grouper/LateEventGrouper";
|
||||||
import PinningUtils from "../../../utils/PinningUtils";
|
import PinningUtils from "../../../utils/PinningUtils";
|
||||||
import { PinnedMessageBadge } from "../messages/PinnedMessageBadge";
|
import { PinnedMessageBadge } from "../messages/PinnedMessageBadge";
|
||||||
import { EventPreview } from "./EventPreview";
|
import { EventPreview } from "./EventPreview";
|
||||||
|
import { ElementCallEventType } from "../../../call-types";
|
||||||
|
|
||||||
export type GetRelationsForEvent = (
|
export type GetRelationsForEvent = (
|
||||||
eventId: string,
|
eventId: string,
|
||||||
@@ -984,7 +984,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
mx_EventTile_highlight: this.shouldHighlight(),
|
mx_EventTile_highlight: this.shouldHighlight(),
|
||||||
mx_EventTile_selected: this.props.isSelectedEvent || this.state.contextMenu,
|
mx_EventTile_selected: this.props.isSelectedEvent || this.state.contextMenu,
|
||||||
mx_EventTile_continuation:
|
mx_EventTile_continuation:
|
||||||
isContinuation || eventType === EventType.CallInvite || ElementCall.CALL_EVENT_TYPE.matches(eventType),
|
isContinuation || eventType === EventType.CallInvite || ElementCallEventType.matches(eventType),
|
||||||
mx_EventTile_last: this.props.last,
|
mx_EventTile_last: this.props.last,
|
||||||
mx_EventTile_lastInSection: this.props.lastInSection,
|
mx_EventTile_lastInSection: this.props.lastInSection,
|
||||||
mx_EventTile_contextual: this.props.contextual,
|
mx_EventTile_contextual: this.props.contextual,
|
||||||
@@ -1037,7 +1037,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
} else if (
|
} else if (
|
||||||
(this.props.continuation && this.context.timelineRenderingType !== TimelineRenderingType.File) ||
|
(this.props.continuation && this.context.timelineRenderingType !== TimelineRenderingType.File) ||
|
||||||
eventType === EventType.CallInvite ||
|
eventType === EventType.CallInvite ||
|
||||||
ElementCall.CALL_EVENT_TYPE.matches(eventType)
|
ElementCallEventType.matches(eventType)
|
||||||
) {
|
) {
|
||||||
// no avatar or sender profile for continuation messages and call tiles
|
// no avatar or sender profile for continuation messages and call tiles
|
||||||
avatarSize = null;
|
avatarSize = null;
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ import ErrorDialog from "../../../dialogs/ErrorDialog";
|
|||||||
import PowerSelector from "../../../elements/PowerSelector";
|
import PowerSelector from "../../../elements/PowerSelector";
|
||||||
import SettingsFieldset from "../../SettingsFieldset";
|
import SettingsFieldset from "../../SettingsFieldset";
|
||||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
import { ElementCall } from "../../../../../models/Call";
|
|
||||||
import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig";
|
import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig";
|
||||||
import { AddPrivilegedUsers } from "../../AddPrivilegedUsers";
|
import { AddPrivilegedUsers } from "../../AddPrivilegedUsers";
|
||||||
import SettingsTab from "../SettingsTab";
|
import SettingsTab from "../SettingsTab";
|
||||||
import { SettingsSection } from "../../shared/SettingsSection";
|
import { SettingsSection } from "../../shared/SettingsSection";
|
||||||
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||||
import { PowerLevelSelector } from "../../PowerLevelSelector";
|
import { PowerLevelSelector } from "../../PowerLevelSelector";
|
||||||
|
import { ElementCallEventType, ElementCallMemberEventType } from "../../../../../call-types";
|
||||||
|
|
||||||
interface IEventShowOpts {
|
interface IEventShowOpts {
|
||||||
isState?: boolean;
|
isState?: boolean;
|
||||||
@@ -63,8 +63,8 @@ const plEventsToShow: Record<string, IEventShowOpts> = {
|
|||||||
[EventType.RoomRedaction]: { isState: false, hideForSpace: true },
|
[EventType.RoomRedaction]: { isState: false, hideForSpace: true },
|
||||||
|
|
||||||
// MSC3401: Native Group VoIP signaling
|
// MSC3401: Native Group VoIP signaling
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: { isState: true, hideForSpace: true },
|
[ElementCallEventType.name]: { isState: true, hideForSpace: true },
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: { isState: true, hideForSpace: true },
|
[ElementCallMemberEventType.name]: { isState: true, hideForSpace: true },
|
||||||
|
|
||||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||||
"im.vector.modular.widgets": { isState: true, hideForSpace: true },
|
"im.vector.modular.widgets": { isState: true, hideForSpace: true },
|
||||||
@@ -298,8 +298,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps, RolesR
|
|||||||
|
|
||||||
// MSC3401: Native Group VoIP signaling
|
// MSC3401: Native Group VoIP signaling
|
||||||
if (SettingsStore.getValue("feature_group_calls")) {
|
if (SettingsStore.getValue("feature_group_calls")) {
|
||||||
plEventsToLabels[ElementCall.CALL_EVENT_TYPE.name] = _td("room_settings|permissions|m.call");
|
plEventsToLabels[ElementCallEventType.name] = _td("room_settings|permissions|m.call");
|
||||||
plEventsToLabels[ElementCall.MEMBER_EVENT_TYPE.name] = _td("room_settings|permissions|m.call.member");
|
plEventsToLabels[ElementCallMemberEventType.name] = _td("room_settings|permissions|m.call.member");
|
||||||
}
|
}
|
||||||
|
|
||||||
const powerLevelDescriptors: Record<string, IPowerLevelDescriptor> = {
|
const powerLevelDescriptors: Record<string, IPowerLevelDescriptor> = {
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import { _t } from "../../../../../languageHandler";
|
|||||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||||
import { SettingsSubsection } from "../../shared/SettingsSubsection";
|
import { SettingsSubsection } from "../../shared/SettingsSubsection";
|
||||||
import SettingsTab from "../SettingsTab";
|
import SettingsTab from "../SettingsTab";
|
||||||
import { ElementCall } from "../../../../../models/Call";
|
|
||||||
import { useRoomState } from "../../../../../hooks/useRoomState";
|
import { useRoomState } from "../../../../../hooks/useRoomState";
|
||||||
import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig";
|
import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig";
|
||||||
import { SettingsSection } from "../../shared/SettingsSection";
|
import { SettingsSection } from "../../shared/SettingsSection";
|
||||||
|
import { ElementCallEventType, ElementCallMemberEventType } from "../../../../../call-types";
|
||||||
|
|
||||||
interface ElementCallSwitchProps {
|
interface ElementCallSwitchProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
@@ -42,7 +42,7 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [elementCallEnabled, setElementCallEnabled] = useState<boolean>(() => {
|
const [elementCallEnabled, setElementCallEnabled] = useState<boolean>(() => {
|
||||||
return content.events?.[ElementCall.MEMBER_EVENT_TYPE.name] === 0;
|
return content.events?.[ElementCallMemberEventType.name] === 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
@@ -56,13 +56,13 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
|||||||
const userLevel = newContent.events[EventType.RoomMessage] ?? content.users_default ?? 0;
|
const userLevel = newContent.events[EventType.RoomMessage] ?? content.users_default ?? 0;
|
||||||
const moderatorLevel = content.kick ?? 50;
|
const moderatorLevel = content.kick ?? 50;
|
||||||
|
|
||||||
newContent.events[ElementCall.CALL_EVENT_TYPE.name] = isPublic ? moderatorLevel : userLevel;
|
newContent.events[ElementCallEventType.name] = isPublic ? moderatorLevel : userLevel;
|
||||||
newContent.events[ElementCall.MEMBER_EVENT_TYPE.name] = userLevel;
|
newContent.events[ElementCallMemberEventType.name] = userLevel;
|
||||||
} else {
|
} else {
|
||||||
const adminLevel = newContent.events[EventType.RoomPowerLevels] ?? content.state_default ?? 100;
|
const adminLevel = newContent.events[EventType.RoomPowerLevels] ?? content.state_default ?? 100;
|
||||||
|
|
||||||
newContent.events[ElementCall.CALL_EVENT_TYPE.name] = adminLevel;
|
newContent.events[ElementCallEventType.name] = adminLevel;
|
||||||
newContent.events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
|
newContent.events[ElementCallMemberEventType.name] = adminLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, newContent);
|
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, newContent);
|
||||||
|
|||||||
+5
-4
@@ -42,6 +42,7 @@ import { waitForMember } from "./utils/membership";
|
|||||||
import { PreferredRoomVersions } from "./utils/PreferredRoomVersions";
|
import { PreferredRoomVersions } from "./utils/PreferredRoomVersions";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { MEGOLM_ENCRYPTION_ALGORITHM } from "./utils/crypto";
|
import { MEGOLM_ENCRYPTION_ALGORITHM } from "./utils/crypto";
|
||||||
|
import { ElementCallEventType, ElementCallMemberEventType } from "./call-types";
|
||||||
|
|
||||||
// we define a number of interfaces which take their names from the js-sdk
|
// we define a number of interfaces which take their names from the js-sdk
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
@@ -165,9 +166,9 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
|
|||||||
events: {
|
events: {
|
||||||
...DEFAULT_EVENT_POWER_LEVELS,
|
...DEFAULT_EVENT_POWER_LEVELS,
|
||||||
// Allow all users to send call membership updates
|
// Allow all users to send call membership updates
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
[ElementCallMemberEventType.name]: 0,
|
||||||
// Make calls immutable, even to admins
|
// Make calls immutable, even to admins
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: 200,
|
[ElementCallEventType.name]: 200,
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
// Temporarily give ourselves the power to set up a call
|
// Temporarily give ourselves the power to set up a call
|
||||||
@@ -180,9 +181,9 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
|
|||||||
events: {
|
events: {
|
||||||
...DEFAULT_EVENT_POWER_LEVELS,
|
...DEFAULT_EVENT_POWER_LEVELS,
|
||||||
// It should always (including non video rooms) be possible to join a group call.
|
// It should always (including non video rooms) be possible to join a group call.
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
[ElementCallMemberEventType.name]: 0,
|
||||||
// Make sure only admins can enable it (DEPRECATED)
|
// Make sure only admins can enable it (DEPRECATED)
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: 100,
|
[ElementCallEventType.name]: 100,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ import { getMessageModerationState, MessageModerationState } from "../utils/Even
|
|||||||
import HiddenBody from "../components/views/messages/HiddenBody";
|
import HiddenBody from "../components/views/messages/HiddenBody";
|
||||||
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
|
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
|
||||||
import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
|
import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline";
|
||||||
import { ElementCall } from "../models/Call";
|
|
||||||
import { type IBodyProps } from "../components/views/messages/IBodyProps";
|
import { type IBodyProps } from "../components/views/messages/IBodyProps";
|
||||||
import ModuleApi from "../modules/Api";
|
import ModuleApi from "../modules/Api";
|
||||||
import { TextualEventViewModel } from "../viewmodels/event-tiles/TextualEventViewModel";
|
import { TextualEventViewModel } from "../viewmodels/event-tiles/TextualEventViewModel";
|
||||||
import { TextualEventView } from "../shared-components/event-tiles/TextualEventView";
|
import { TextualEventView } from "../shared-components/event-tiles/TextualEventView";
|
||||||
|
import { ElementCallEventType } from "../call-types";
|
||||||
|
|
||||||
// Subset of EventTile's IProps plus some mixins
|
// Subset of EventTile's IProps plus some mixins
|
||||||
export interface EventTileTypeProps
|
export interface EventTileTypeProps
|
||||||
@@ -122,7 +122,7 @@ const STATE_EVENT_TILE_TYPES = new Map<string, Factory>([
|
|||||||
[EventType.RoomGuestAccess, TextualEventFactory],
|
[EventType.RoomGuestAccess, TextualEventFactory],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (const evType of ElementCall.CALL_EVENT_TYPE.names) {
|
for (const evType of ElementCallEventType.names) {
|
||||||
STATE_EVENT_TILE_TYPES.set(evType, CallEventFactory);
|
STATE_EVENT_TILE_TYPES.set(evType, CallEventFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,9 +444,7 @@ export function haveRendererForEvent(
|
|||||||
const dynamicPredecessorsEnabled = SettingsStore.getValue("feature_dynamic_room_predecessors");
|
const dynamicPredecessorsEnabled = SettingsStore.getValue("feature_dynamic_room_predecessors");
|
||||||
const predecessor = matrixClient.getRoom(mxEvent.getRoomId())?.findPredecessor(dynamicPredecessorsEnabled);
|
const predecessor = matrixClient.getRoom(mxEvent.getRoomId())?.findPredecessor(dynamicPredecessorsEnabled);
|
||||||
return Boolean(predecessor);
|
return Boolean(predecessor);
|
||||||
} else if (
|
} else if (ElementCallEventType.names.some((eventType) => handler === STATE_EVENT_TILE_TYPES.get(eventType))) {
|
||||||
ElementCall.CALL_EVENT_TYPE.names.some((eventType) => handler === STATE_EVENT_TILE_TYPES.get(eventType))
|
|
||||||
) {
|
|
||||||
const intent = mxEvent.getContent()["m.intent"];
|
const intent = mxEvent.getContent()["m.intent"];
|
||||||
const newlyStarted = Object.keys(mxEvent.getPrevContent()).length === 0;
|
const newlyStarted = Object.keys(mxEvent.getPrevContent()).length === 0;
|
||||||
// Only interested in events that mark the start of a non-room call
|
// Only interested in events that mark the start of a non-room call
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useWidgets } from "../../utils/WidgetUtils";
|
|||||||
import { WidgetType } from "../../widgets/WidgetType";
|
import { WidgetType } from "../../widgets/WidgetType";
|
||||||
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
|
import { useCall, useConnectionState, useParticipantCount } from "../useCall";
|
||||||
import { useRoomMemberCount } from "../useRoomMembers";
|
import { useRoomMemberCount } from "../useRoomMembers";
|
||||||
import { ConnectionState, ElementCall } from "../../models/Call";
|
import { ConnectionState } from "../../models/Call";
|
||||||
import { placeCall } from "../../utils/room/placeCall";
|
import { placeCall } from "../../utils/room/placeCall";
|
||||||
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
||||||
import { useRoomState } from "../useRoomState";
|
import { useRoomState } from "../useRoomState";
|
||||||
@@ -36,6 +36,7 @@ import { useGuestAccessInformation } from "./useGuestAccessInformation";
|
|||||||
import { UIFeature } from "../../settings/UIFeature";
|
import { UIFeature } from "../../settings/UIFeature";
|
||||||
import { BetaPill } from "../../components/views/beta/BetaCard";
|
import { BetaPill } from "../../components/views/beta/BetaCard";
|
||||||
import { type InteractionName } from "../../PosthogTrackers";
|
import { type InteractionName } from "../../PosthogTrackers";
|
||||||
|
import { ElementCallMemberEventType } from "../../call-types";
|
||||||
|
|
||||||
export enum PlatformCallType {
|
export enum PlatformCallType {
|
||||||
ElementCall,
|
ElementCall,
|
||||||
@@ -135,7 +136,7 @@ export const useRoomCall = (
|
|||||||
|
|
||||||
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
const [mayEditWidgets, mayCreateElementCalls] = useRoomState(room, () => [
|
||||||
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
room.currentState.mayClientSendStateEvent("im.vector.modular.widgets", room.client),
|
||||||
room.currentState.mayClientSendStateEvent(ElementCall.MEMBER_EVENT_TYPE.name, room.client),
|
room.currentState.mayClientSendStateEvent(ElementCallMemberEventType.name, room.client),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// The options provided to the RoomHeader.
|
// The options provided to the RoomHeader.
|
||||||
|
|||||||
+13
-38
@@ -10,7 +10,6 @@ import {
|
|||||||
TypedEventEmitter,
|
TypedEventEmitter,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
RoomStateEvent,
|
RoomStateEvent,
|
||||||
EventType,
|
|
||||||
type MatrixClient,
|
type MatrixClient,
|
||||||
type IMyDevice,
|
type IMyDevice,
|
||||||
type Room,
|
type Room,
|
||||||
@@ -20,14 +19,12 @@ import { KnownMembership, type Membership } from "matrix-js-sdk/src/types";
|
|||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { NamespacedValue } from "matrix-js-sdk/src/NamespacedValue";
|
|
||||||
import { type IWidgetApiRequest, type ClientWidgetApi, type IWidgetData } from "matrix-widget-api";
|
import { type IWidgetApiRequest, type ClientWidgetApi, type IWidgetData } from "matrix-widget-api";
|
||||||
import {
|
import {
|
||||||
MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
type CallMembership,
|
type CallMembership,
|
||||||
MatrixRTCSessionManagerEvents,
|
MatrixRTCSessionManagerEvents,
|
||||||
type ICallNotifyContent,
|
|
||||||
} from "matrix-js-sdk/src/matrixrtc";
|
} from "matrix-js-sdk/src/matrixrtc";
|
||||||
|
|
||||||
import type EventEmitter from "events";
|
import type EventEmitter from "events";
|
||||||
@@ -44,11 +41,12 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../stores/ActiveWidge
|
|||||||
import { getCurrentLanguage } from "../languageHandler";
|
import { getCurrentLanguage } from "../languageHandler";
|
||||||
import { Anonymity, PosthogAnalytics } from "../PosthogAnalytics";
|
import { Anonymity, PosthogAnalytics } from "../PosthogAnalytics";
|
||||||
import { UPDATE_EVENT } from "../stores/AsyncStore";
|
import { UPDATE_EVENT } from "../stores/AsyncStore";
|
||||||
import { getJoinedNonFunctionalMembers } from "../utils/room/getJoinedNonFunctionalMembers";
|
|
||||||
import { isVideoRoom } from "../utils/video-rooms";
|
import { isVideoRoom } from "../utils/video-rooms";
|
||||||
import { FontWatcher } from "../settings/watchers/FontWatcher";
|
import { FontWatcher } from "../settings/watchers/FontWatcher";
|
||||||
import { type JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-types";
|
import { type JitsiCallMemberContent, JitsiCallMemberEventType } from "../call-types";
|
||||||
import SdkConfig from "../SdkConfig.ts";
|
import SdkConfig from "../SdkConfig.ts";
|
||||||
|
import RoomListStore from "../stores/room-list/RoomListStore.ts";
|
||||||
|
import { DefaultTagID } from "../stores/room-list/models.ts";
|
||||||
|
|
||||||
const TIMEOUT_MS = 16000;
|
const TIMEOUT_MS = 16000;
|
||||||
|
|
||||||
@@ -643,10 +641,6 @@ export class JitsiCall extends Call {
|
|||||||
* (somewhat cheekily named)
|
* (somewhat cheekily named)
|
||||||
*/
|
*/
|
||||||
export class ElementCall extends Call {
|
export class ElementCall extends Call {
|
||||||
// TODO this is only there to support backwards compatibility in timeline rendering
|
|
||||||
// this should not be part of this class since it has nothing to do with it.
|
|
||||||
public static readonly CALL_EVENT_TYPE = new NamespacedValue(null, EventType.GroupCallPrefix);
|
|
||||||
public static readonly MEMBER_EVENT_TYPE = new NamespacedValue(null, EventType.GroupCallMemberPrefix);
|
|
||||||
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
|
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
|
||||||
|
|
||||||
private settingsStoreCallEncryptionWatcher?: string;
|
private settingsStoreCallEncryptionWatcher?: string;
|
||||||
@@ -684,6 +678,14 @@ export class ElementCall extends Call {
|
|||||||
theme: "$org.matrix.msc2873.client_theme",
|
theme: "$org.matrix.msc2873.client_theme",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
if (room !== null && !isVideoRoom(room)) {
|
||||||
|
params.append(
|
||||||
|
"sendNotificationType",
|
||||||
|
RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.DM) ? "ring" : "notification",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const rageshakeSubmitUrl = SdkConfig.get("bug_report_endpoint_url");
|
const rageshakeSubmitUrl = SdkConfig.get("bug_report_endpoint_url");
|
||||||
if (rageshakeSubmitUrl) {
|
if (rageshakeSubmitUrl) {
|
||||||
params.append("rageshakeSubmitUrl", rageshakeSubmitUrl);
|
params.append("rageshakeSubmitUrl", rageshakeSubmitUrl);
|
||||||
@@ -858,31 +860,6 @@ export class ElementCall extends Call {
|
|||||||
ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, isVideoRoom(room));
|
ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, isVideoRoom(room));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async sendCallNotify(): Promise<void> {
|
|
||||||
const room = this.room;
|
|
||||||
const existingOtherRoomCallMembers = MatrixRTCSession.callMembershipsForRoom(room).filter(
|
|
||||||
// filter all memberships where the application is m.call and the call_id is ""
|
|
||||||
(m) => {
|
|
||||||
const isRoomCallMember = m.application === "m.call" && m.callId === "";
|
|
||||||
const isThisDevice = m.deviceId === this.client.deviceId;
|
|
||||||
return isRoomCallMember && !isThisDevice;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const memberCount = getJoinedNonFunctionalMembers(room).length;
|
|
||||||
if (!isVideoRoom(room) && existingOtherRoomCallMembers.length === 0) {
|
|
||||||
// send ringing event
|
|
||||||
const content: ICallNotifyContent = {
|
|
||||||
"application": "m.call",
|
|
||||||
"m.mentions": { user_ids: [], room: true },
|
|
||||||
"notify_type": memberCount == 2 ? "ring" : "notify",
|
|
||||||
"call_id": "",
|
|
||||||
};
|
|
||||||
|
|
||||||
await room.client.sendEvent(room.roomId, EventType.CallNotify, content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async performConnection(
|
protected async performConnection(
|
||||||
audioInput: MediaDeviceInfo | null,
|
audioInput: MediaDeviceInfo | null,
|
||||||
videoInput: MediaDeviceInfo | null,
|
videoInput: MediaDeviceInfo | null,
|
||||||
@@ -891,9 +868,8 @@ export class ElementCall extends Call {
|
|||||||
this.messaging!.once(`action:${ElementWidgetActions.Close}`, this.onClose);
|
this.messaging!.once(`action:${ElementWidgetActions.Close}`, this.onClose);
|
||||||
this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
|
this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
|
||||||
|
|
||||||
// TODO: if the widget informs us when the join button is clicked (widget action), so we can
|
// TODO: Watch for a widget action telling us that the join button was clicked, rather than
|
||||||
// - set state to connecting
|
// relying on the MatrixRTC session state, to set the state to connecting
|
||||||
// - send call notify
|
|
||||||
const session = this.client.matrixRTC.getActiveRoomSession(this.room);
|
const session = this.client.matrixRTC.getActiveRoomSession(this.room);
|
||||||
if (session) {
|
if (session) {
|
||||||
await waitForEvent(
|
await waitForEvent(
|
||||||
@@ -912,7 +888,6 @@ export class ElementCall extends Call {
|
|||||||
false, // allow user to wait as long as they want (no timeout)
|
false, // allow user to wait as long as they want (no timeout)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.sendCallNotify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async performDisconnection(): Promise<void> {
|
protected async performDisconnection(): Promise<void> {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import { haveRendererForEvent, JitsiEventFactory, JSONEventFactory, pickFactory } from "../events/EventTileFactory";
|
import { haveRendererForEvent, JitsiEventFactory, JSONEventFactory, pickFactory } from "../events/EventTileFactory";
|
||||||
import { getMessageModerationState, isLocationEvent, MessageModerationState } from "./EventUtils";
|
import { getMessageModerationState, isLocationEvent, MessageModerationState } from "./EventUtils";
|
||||||
import { ElementCall } from "../models/Call";
|
import { ElementCallEventType } from "../call-types";
|
||||||
|
|
||||||
const calcIsInfoMessage = (
|
const calcIsInfoMessage = (
|
||||||
eventType: EventType | string,
|
eventType: EventType | string,
|
||||||
@@ -82,7 +82,7 @@ export function getEventDisplayInfo(
|
|||||||
eventType === EventType.RoomEncryption ||
|
eventType === EventType.RoomEncryption ||
|
||||||
factory === JitsiEventFactory;
|
factory === JitsiEventFactory;
|
||||||
const isLeftAlignedBubbleMessage =
|
const isLeftAlignedBubbleMessage =
|
||||||
!isBubbleMessage && (eventType === EventType.CallInvite || ElementCall.CALL_EVENT_TYPE.matches(eventType));
|
!isBubbleMessage && (eventType === EventType.CallInvite || ElementCallEventType.matches(eventType));
|
||||||
let isInfoMessage = calcIsInfoMessage(eventType, content, isBubbleMessage, isLeftAlignedBubbleMessage);
|
let isInfoMessage = calcIsInfoMessage(eventType, content, isBubbleMessage, isLeftAlignedBubbleMessage);
|
||||||
// Some non-info messages want to be rendered in the appropriate bubble column but without the bubble background
|
// Some non-info messages want to be rendered in the appropriate bubble column but without the bubble background
|
||||||
const noBubbleEvent =
|
const noBubbleEvent =
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import SettingsStore from "../../src/settings/SettingsStore";
|
|||||||
import { createTestClient, stubClient } from "../test-utils";
|
import { createTestClient, stubClient } from "../test-utils";
|
||||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||||
import UserIdentifierCustomisations from "../../src/customisations/UserIdentifier";
|
import UserIdentifierCustomisations from "../../src/customisations/UserIdentifier";
|
||||||
import { ElementCall } from "../../src/models/Call";
|
|
||||||
import { getSenderName } from "../../src/utils/event/getSenderName";
|
import { getSenderName } from "../../src/utils/event/getSenderName";
|
||||||
|
import { ElementCallEventType } from "../../src/call-types";
|
||||||
|
|
||||||
jest.mock("../../src/settings/SettingsStore");
|
jest.mock("../../src/settings/SettingsStore");
|
||||||
jest.mock("../../src/customisations/UserIdentifier", () => ({
|
jest.mock("../../src/customisations/UserIdentifier", () => ({
|
||||||
@@ -471,7 +471,7 @@ describe("TextForEvent", () => {
|
|||||||
} as unknown as MatrixEvent;
|
} as unknown as MatrixEvent;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each(ElementCall.CALL_EVENT_TYPE.names)("eventType=%s", (eventType: string) => {
|
describe.each(ElementCallEventType.names)("eventType=%s", (eventType: string) => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocked(callEvent).getType.mockReturnValue(eventType);
|
mocked(callEvent).getType.mockReturnValue(eventType);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
|||||||
import SdkConfig from "../../../../../../src/SdkConfig";
|
import SdkConfig from "../../../../../../src/SdkConfig";
|
||||||
import dispatcher from "../../../../../../src/dispatcher/dispatcher";
|
import dispatcher from "../../../../../../src/dispatcher/dispatcher";
|
||||||
import { CallStore } from "../../../../../../src/stores/CallStore";
|
import { CallStore } from "../../../../../../src/stores/CallStore";
|
||||||
import { type Call, ElementCall } from "../../../../../../src/models/Call";
|
import { type Call } from "../../../../../../src/models/Call";
|
||||||
import * as ShieldUtils from "../../../../../../src/utils/ShieldUtils";
|
import * as ShieldUtils from "../../../../../../src/utils/ShieldUtils";
|
||||||
import { Container, WidgetLayoutStore } from "../../../../../../src/stores/widgets/WidgetLayoutStore";
|
import { Container, WidgetLayoutStore } from "../../../../../../src/stores/widgets/WidgetLayoutStore";
|
||||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||||
@@ -58,6 +58,7 @@ import { SdkContextClass } from "../../../../../../src/contexts/SDKContext";
|
|||||||
import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore";
|
import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore";
|
||||||
import { UIFeature } from "../../../../../../src/settings/UIFeature";
|
import { UIFeature } from "../../../../../../src/settings/UIFeature";
|
||||||
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||||
|
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
|
||||||
|
|
||||||
jest.mock("../../../../../../src/utils/ShieldUtils");
|
jest.mock("../../../../../../src/utils/ShieldUtils");
|
||||||
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
|
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
|
||||||
@@ -599,7 +600,7 @@ describe("RoomHeader", () => {
|
|||||||
mockRoomMembers(room, 3);
|
mockRoomMembers(room, 3);
|
||||||
|
|
||||||
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockImplementation((key) => {
|
||||||
if (key === ElementCall.MEMBER_EVENT_TYPE.name) return true;
|
if (key === ElementCallMemberEventType.name) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import RolesRoomSettingsTab from "../../../../../../../src/components/views/sett
|
|||||||
import { mkStubRoom, withClientContextRenderOptions, stubClient } from "../../../../../../test-utils";
|
import { mkStubRoom, withClientContextRenderOptions, stubClient } from "../../../../../../test-utils";
|
||||||
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
||||||
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
||||||
import { ElementCall } from "../../../../../../../src/models/Call";
|
import { ElementCallEventType, ElementCallMemberEventType } from "../../../../../../../src/call-types";
|
||||||
|
|
||||||
describe("RolesRoomSettingsTab", () => {
|
describe("RolesRoomSettingsTab", () => {
|
||||||
const userId = "@alice:server.org";
|
const userId = "@alice:server.org";
|
||||||
@@ -116,7 +116,7 @@ describe("RolesRoomSettingsTab", () => {
|
|||||||
expect(getJoinCallSelectedOption(tab)?.textContent).toBe("Default");
|
expect(getJoinCallSelectedOption(tab)?.textContent).toBe("Default");
|
||||||
expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, {
|
expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, {
|
||||||
events: {
|
events: {
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
[ElementCallMemberEventType.name]: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -137,7 +137,7 @@ describe("RolesRoomSettingsTab", () => {
|
|||||||
expect(getStartCallSelectedOption(tab)?.textContent).toBe("Default");
|
expect(getStartCallSelectedOption(tab)?.textContent).toBe("Default");
|
||||||
expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, {
|
expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, {
|
||||||
events: {
|
events: {
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: 0,
|
[ElementCallEventType.name]: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { type MatrixClient, type Room, type MatrixEvent, EventType, JoinRule } f
|
|||||||
import { mkStubRoom, stubClient } from "../../../../../../test-utils";
|
import { mkStubRoom, stubClient } from "../../../../../../test-utils";
|
||||||
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
||||||
import { VoipRoomSettingsTab } from "../../../../../../../src/components/views/settings/tabs/room/VoipRoomSettingsTab";
|
import { VoipRoomSettingsTab } from "../../../../../../../src/components/views/settings/tabs/room/VoipRoomSettingsTab";
|
||||||
import { ElementCall } from "../../../../../../../src/models/Call";
|
import { ElementCallEventType, ElementCallMemberEventType } from "../../../../../../../src/call-types";
|
||||||
|
|
||||||
describe("VoipRoomSettingsTab", () => {
|
describe("VoipRoomSettingsTab", () => {
|
||||||
const roomId = "!room:example.com";
|
const roomId = "!room:example.com";
|
||||||
@@ -48,7 +48,7 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
|
|
||||||
describe("correct state", () => {
|
describe("correct state", () => {
|
||||||
it("shows enabled when call member power level is 0", () => {
|
it("shows enabled when call member power level is 0", () => {
|
||||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 0 });
|
mockPowerLevels({ [ElementCallMemberEventType.name]: 0 });
|
||||||
|
|
||||||
const tab = renderTab();
|
const tab = renderTab();
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([1, 50, 100])("shows disabled when call member power level is 0", (level: number) => {
|
it.each([1, 50, 100])("shows disabled when call member power level is 0", (level: number) => {
|
||||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: level });
|
mockPowerLevels({ [ElementCallMemberEventType.name]: level });
|
||||||
|
|
||||||
const tab = renderTab();
|
const tab = renderTab();
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
describe("enabling/disabling", () => {
|
describe("enabling/disabling", () => {
|
||||||
describe("enabling Element calls", () => {
|
describe("enabling Element calls", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 100 });
|
mockPowerLevels({ [ElementCallMemberEventType.name]: 100 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("enables Element calls in public room", async () => {
|
it("enables Element calls in public room", async () => {
|
||||||
@@ -82,8 +82,8 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
EventType.RoomPowerLevels,
|
EventType.RoomPowerLevels,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
events: {
|
events: {
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: 50,
|
[ElementCallEventType.name]: 50,
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
[ElementCallMemberEventType.name]: 0,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -102,8 +102,8 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
EventType.RoomPowerLevels,
|
EventType.RoomPowerLevels,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
events: {
|
events: {
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: 0,
|
[ElementCallEventType.name]: 0,
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
|
[ElementCallMemberEventType.name]: 0,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -112,7 +112,7 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("disables Element calls", async () => {
|
it("disables Element calls", async () => {
|
||||||
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 0 });
|
mockPowerLevels({ [ElementCallMemberEventType.name]: 0 });
|
||||||
|
|
||||||
const tab = renderTab();
|
const tab = renderTab();
|
||||||
|
|
||||||
@@ -123,8 +123,8 @@ describe("VoipRoomSettingsTab", () => {
|
|||||||
EventType.RoomPowerLevels,
|
EventType.RoomPowerLevels,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
events: {
|
events: {
|
||||||
[ElementCall.CALL_EVENT_TYPE.name]: 100,
|
[ElementCallEventType.name]: 100,
|
||||||
[ElementCall.MEMBER_EVENT_TYPE.name]: 100,
|
[ElementCallMemberEventType.name]: 100,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import WidgetUtils from "../../src/utils/WidgetUtils";
|
|||||||
import { JitsiCall, ElementCall } from "../../src/models/Call";
|
import { JitsiCall, ElementCall } from "../../src/models/Call";
|
||||||
import createRoom, { checkUserIsAllowedToChangeEncryption, canEncryptToAllUsers } from "../../src/createRoom";
|
import createRoom, { checkUserIsAllowedToChangeEncryption, canEncryptToAllUsers } from "../../src/createRoom";
|
||||||
import SettingsStore from "../../src/settings/SettingsStore";
|
import SettingsStore from "../../src/settings/SettingsStore";
|
||||||
|
import { ElementCallEventType, ElementCallMemberEventType } from "../../src/call-types";
|
||||||
|
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||||
|
|
||||||
describe("createRoom", () => {
|
describe("createRoom", () => {
|
||||||
mockPlatformPeg();
|
mockPlatformPeg();
|
||||||
@@ -26,6 +28,7 @@ describe("createRoom", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubClient();
|
stubClient();
|
||||||
client = mocked(MatrixClientPeg.safeGet());
|
client = mocked(MatrixClientPeg.safeGet());
|
||||||
|
DMRoomMap.makeShared(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => jest.clearAllMocks());
|
afterEach(() => jest.clearAllMocks());
|
||||||
@@ -75,11 +78,9 @@ describe("createRoom", () => {
|
|||||||
|
|
||||||
const userPower = client.createRoom.mock.calls[0][0].power_level_content_override?.users?.[userId];
|
const userPower = client.createRoom.mock.calls[0][0].power_level_content_override?.users?.[userId];
|
||||||
const callPower =
|
const callPower =
|
||||||
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCall.CALL_EVENT_TYPE.name];
|
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCallEventType.name];
|
||||||
const callMemberPower =
|
const callMemberPower =
|
||||||
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[
|
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCallMemberEventType.name];
|
||||||
ElementCall.MEMBER_EVENT_TYPE.name
|
|
||||||
];
|
|
||||||
|
|
||||||
// We should have had enough power to be able to set up the call
|
// We should have had enough power to be able to set up the call
|
||||||
expect(userPower).toBeGreaterThanOrEqual(callPower!);
|
expect(userPower).toBeGreaterThanOrEqual(callPower!);
|
||||||
@@ -112,11 +113,9 @@ describe("createRoom", () => {
|
|||||||
await createRoom(client, {});
|
await createRoom(client, {});
|
||||||
|
|
||||||
const callPower =
|
const callPower =
|
||||||
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCall.CALL_EVENT_TYPE.name];
|
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCallEventType.name];
|
||||||
const callMemberPower =
|
const callMemberPower =
|
||||||
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[
|
client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCallMemberEventType.name];
|
||||||
ElementCall.MEMBER_EVENT_TYPE.name
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(callPower).toBe(100);
|
expect(callPower).toBe(100);
|
||||||
expect(callMemberPower).toBe(0);
|
expect(callMemberPower).toBe(0);
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ import SettingsStore from "../../../src/settings/SettingsStore";
|
|||||||
import { Anonymity, PosthogAnalytics } from "../../../src/PosthogAnalytics";
|
import { Anonymity, PosthogAnalytics } from "../../../src/PosthogAnalytics";
|
||||||
import { type SettingKey } from "../../../src/settings/Settings.tsx";
|
import { type SettingKey } from "../../../src/settings/Settings.tsx";
|
||||||
import SdkConfig from "../../../src/SdkConfig.ts";
|
import SdkConfig from "../../../src/SdkConfig.ts";
|
||||||
|
import RoomListStore from "../../../src/stores/room-list/RoomListStore.ts";
|
||||||
|
import { DefaultTagID } from "../../../src/stores/room-list/models.ts";
|
||||||
|
import DMRoomMap from "../../../src/utils/DMRoomMap.ts";
|
||||||
|
|
||||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||||
[MediaDeviceKindEnum.AudioInput]: [
|
[MediaDeviceKindEnum.AudioInput]: [
|
||||||
@@ -78,6 +81,7 @@ const setUpClientRoomAndStores = (): {
|
|||||||
} => {
|
} => {
|
||||||
stubClient();
|
stubClient();
|
||||||
const client = mocked<MatrixClient>(MatrixClientPeg.safeGet());
|
const client = mocked<MatrixClient>(MatrixClientPeg.safeGet());
|
||||||
|
DMRoomMap.makeShared(client);
|
||||||
|
|
||||||
const room = new Room("!1:example.org", client, "@alice:example.org", {
|
const room = new Room("!1:example.org", client, "@alice:example.org", {
|
||||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||||
@@ -674,6 +678,8 @@ describe("ElementCall", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("get", () => {
|
describe("get", () => {
|
||||||
|
afterEach(() => Call.get(room)?.destroy());
|
||||||
|
|
||||||
it("finds no calls", () => {
|
it("finds no calls", () => {
|
||||||
expect(Call.get(room)).toBeNull();
|
expect(Call.get(room)).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -681,7 +687,6 @@ describe("ElementCall", () => {
|
|||||||
it("finds calls", async () => {
|
it("finds calls", async () => {
|
||||||
ElementCall.create(room);
|
ElementCall.create(room);
|
||||||
expect(Call.get(room)).toBeInstanceOf(ElementCall);
|
expect(Call.get(room)).toBeInstanceOf(ElementCall);
|
||||||
Call.get(room)?.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use element call URL from developer settings if present", async () => {
|
it("should use element call URL from developer settings if present", async () => {
|
||||||
@@ -698,7 +703,6 @@ describe("ElementCall", () => {
|
|||||||
const call = ElementCall.get(room);
|
const call = ElementCall.get(room);
|
||||||
expect(call?.widget.url.startsWith("https://call.element.dev/")).toBeTruthy();
|
expect(call?.widget.url.startsWith("https://call.element.dev/")).toBeTruthy();
|
||||||
SettingsStore.getValue = originalGetValue;
|
SettingsStore.getValue = originalGetValue;
|
||||||
call?.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("finds ongoing calls that are created by the session manager", async () => {
|
it("finds ongoing calls that are created by the session manager", async () => {
|
||||||
@@ -710,7 +714,6 @@ describe("ElementCall", () => {
|
|||||||
} as unknown as MatrixRTCSession);
|
} as unknown as MatrixRTCSession);
|
||||||
const call = Call.get(room);
|
const call = Call.get(room);
|
||||||
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
||||||
call.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes font settings through widget URL", async () => {
|
it("passes font settings through widget URL", async () => {
|
||||||
@@ -772,7 +775,6 @@ describe("ElementCall", () => {
|
|||||||
const urlParams2 = new URLSearchParams(new URL(call2.widget.url).hash.slice(1));
|
const urlParams2 = new URLSearchParams(new URL(call2.widget.url).hash.slice(1));
|
||||||
expect(urlParams2.has("allowIceFallback")).toBe(true);
|
expect(urlParams2.has("allowIceFallback")).toBe(true);
|
||||||
|
|
||||||
call2.destroy();
|
|
||||||
SettingsStore.getValue = originalGetValue;
|
SettingsStore.getValue = originalGetValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -799,7 +801,6 @@ describe("ElementCall", () => {
|
|||||||
expect(urlParams.get("posthogUserId")).toBe("123456789987654321");
|
expect(urlParams.get("posthogUserId")).toBe("123456789987654321");
|
||||||
expect(urlParams.get("posthogApiHost")).toBe("https://posthog");
|
expect(urlParams.get("posthogApiHost")).toBe("https://posthog");
|
||||||
expect(urlParams.get("posthogApiKey")).toBe("DEADBEEF");
|
expect(urlParams.get("posthogApiKey")).toBe("DEADBEEF");
|
||||||
call.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not pass analyticsID if `pseudonymousAnalyticsOptIn` set to false", async () => {
|
it("does not pass analyticsID if `pseudonymousAnalyticsOptIn` set to false", async () => {
|
||||||
@@ -817,7 +818,6 @@ describe("ElementCall", () => {
|
|||||||
|
|
||||||
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
||||||
expect(urlParams.get("analyticsID")).toBeFalsy();
|
expect(urlParams.get("analyticsID")).toBeFalsy();
|
||||||
call.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes feature_allow_screen_share_only_mode setting to allowVoipWithNoMedia url param", async () => {
|
it("passes feature_allow_screen_share_only_mode setting to allowVoipWithNoMedia url param", async () => {
|
||||||
@@ -840,7 +840,6 @@ describe("ElementCall", () => {
|
|||||||
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
||||||
expect(urlParams.get("allowVoipWithNoMedia")).toBe("true");
|
expect(urlParams.get("allowVoipWithNoMedia")).toBe("true");
|
||||||
SettingsStore.getValue = originalGetValue;
|
SettingsStore.getValue = originalGetValue;
|
||||||
call.destroy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes empty analyticsID if the id is not in the account data", async () => {
|
it("passes empty analyticsID if the id is not in the account data", async () => {
|
||||||
@@ -857,6 +856,30 @@ describe("ElementCall", () => {
|
|||||||
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
||||||
expect(urlParams.get("analyticsID")).toBeFalsy();
|
expect(urlParams.get("analyticsID")).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requests ringing notifications in DMs", async () => {
|
||||||
|
const tagsSpy = jest.spyOn(RoomListStore.instance, "getTagsForRoom");
|
||||||
|
try {
|
||||||
|
tagsSpy.mockReturnValue([DefaultTagID.DM]);
|
||||||
|
ElementCall.create(room);
|
||||||
|
const call = Call.get(room);
|
||||||
|
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
||||||
|
expect(urlParams.get("sendNotificationType")).toBe("ring");
|
||||||
|
} finally {
|
||||||
|
tagsSpy.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests visual notifications in non-DMs", async () => {
|
||||||
|
ElementCall.create(room);
|
||||||
|
const call = Call.get(room);
|
||||||
|
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
|
||||||
|
expect(urlParams.get("sendNotificationType")).toBe("notification");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("instance in a non-video room", () => {
|
describe("instance in a non-video room", () => {
|
||||||
@@ -1019,31 +1042,6 @@ describe("ElementCall", () => {
|
|||||||
roomSpy.mockRestore();
|
roomSpy.mockRestore();
|
||||||
addWidgetSpy.mockRestore();
|
addWidgetSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends notify event on connect in a room with more than two members", async () => {
|
|
||||||
const sendEventSpy = jest.spyOn(room.client, "sendEvent");
|
|
||||||
ElementCall.create(room);
|
|
||||||
await callConnectProcedure(Call.get(room) as ElementCall);
|
|
||||||
expect(sendEventSpy).toHaveBeenCalledWith("!1:example.org", "org.matrix.msc4075.call.notify", {
|
|
||||||
"application": "m.call",
|
|
||||||
"call_id": "",
|
|
||||||
"m.mentions": { room: true, user_ids: [] },
|
|
||||||
"notify_type": "notify",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("sends ring on create in a DM (two participants) room", async () => {
|
|
||||||
setRoomMembers(["@user:example.com", "@user2:example.com"]);
|
|
||||||
|
|
||||||
const sendEventSpy = jest.spyOn(room.client, "sendEvent");
|
|
||||||
ElementCall.create(room);
|
|
||||||
await callConnectProcedure(Call.get(room) as ElementCall);
|
|
||||||
expect(sendEventSpy).toHaveBeenCalledWith("!1:example.org", "org.matrix.msc4075.call.notify", {
|
|
||||||
"application": "m.call",
|
|
||||||
"call_id": "",
|
|
||||||
"m.mentions": { room: true, user_ids: [] },
|
|
||||||
"notify_type": "ring",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("instance in a video room", () => {
|
describe("instance in a video room", () => {
|
||||||
|
|||||||
@@ -1677,10 +1677,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
|
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
|
||||||
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
|
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
|
||||||
|
|
||||||
"@element-hq/element-call-embedded@0.13.1":
|
"@element-hq/element-call-embedded@0.14.1":
|
||||||
version "0.13.1"
|
version "0.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.13.1.tgz#9161f657f7bebdb5339847b7a8f0a3149a36f95c"
|
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.14.1.tgz#358c537e147ff3d48028cfb65d414cfe89ac1371"
|
||||||
integrity sha512-6RGZPdx+gOCzpJNe+dbftEyiWuNx+2H+uXiZp7QN8SOZ3dl/yjg0JcK60wsC48i7gXy/6ERdbwTgaL9ez8mvhA==
|
integrity sha512-1ODnohNvg7bgR8tg+rIF81MYGChNXVD96lBWkCI96ygjGg7U+HqqA8sY0YsRN5oJ9aLDQPicSr09XwLEXSPmjQ==
|
||||||
|
|
||||||
"@element-hq/element-web-module-api@1.3.0":
|
"@element-hq/element-web-module-api@1.3.0":
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user