Remove LegacyCallHandler singleton (#34086)
* Remove dead code * Remove LegacyCallHandler singleton Route via SDKContext to cut import cycles * Remove unused setting * Fix tests * Fix tests * Cascade SDKContext through PersistedElement * Improve coverage * Iterate * Improve coverage * Improve coverage
This commit is contained in:
Vendored
-2
@@ -21,7 +21,6 @@ import { type ModalManager } from "../Modal";
|
||||
import type SettingsStore from "../settings/SettingsStore";
|
||||
import type RightPanelStore from "../stores/right-panel/RightPanelStore";
|
||||
import type WidgetStore from "../stores/WidgetStore";
|
||||
import type LegacyCallHandler from "../LegacyCallHandler";
|
||||
import type UserActivity from "../UserActivity";
|
||||
import { type ModalWidgetStore } from "../stores/ModalWidgetStore";
|
||||
import { type WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||
@@ -99,7 +98,6 @@ declare global {
|
||||
mxRightPanelStore: RightPanelStore;
|
||||
mxWidgetStore: WidgetStore;
|
||||
mxWidgetLayoutStore: WidgetLayoutStore;
|
||||
mxLegacyCallHandler: LegacyCallHandler;
|
||||
mxUserActivity: UserActivity;
|
||||
mxModalWidgetStore: ModalWidgetStore;
|
||||
mxSpaceStore: SpaceStoreClass;
|
||||
|
||||
@@ -34,7 +34,6 @@ import { WidgetType } from "./widgets/WidgetType";
|
||||
import { SettingLevel } from "./settings/SettingLevel";
|
||||
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||
import WidgetStore from "./stores/WidgetStore";
|
||||
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
|
||||
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
|
||||
import { UIFeature } from "./settings/UIFeature";
|
||||
@@ -42,7 +41,6 @@ import { Action } from "./dispatcher/actions";
|
||||
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "./widgets/ManagedHybrid";
|
||||
import SdkConfig from "./SdkConfig";
|
||||
import { ensureDMExists } from "./createRoom";
|
||||
import { WidgetLayoutStore } from "./stores/widgets/WidgetLayoutStore";
|
||||
import IncomingLegacyCallToast, { getIncomingLegacyCallToastKey } from "./toasts/IncomingLegacyCallToast";
|
||||
import ToastStore from "./stores/ToastStore";
|
||||
import { type ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
||||
@@ -54,41 +52,13 @@ import { localNotificationsAreSilenced } from "./utils/notifications";
|
||||
import { isNotNull } from "./Typeguards";
|
||||
import { BackgroundAudio } from "./audio/BackgroundAudio";
|
||||
import { Jitsi } from "./widgets/Jitsi.ts";
|
||||
import { type SDKContextClass } from "./contexts/SDKContextClass.ts";
|
||||
|
||||
export const PROTOCOL_PSTN = "m.protocol.pstn";
|
||||
export const PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn";
|
||||
|
||||
const CHECK_PROTOCOLS_ATTEMPTS = 3;
|
||||
|
||||
type MediaEventType = keyof HTMLMediaElementEventMap;
|
||||
const MEDIA_ERROR_EVENT_TYPES: MediaEventType[] = [
|
||||
"error",
|
||||
// The media has become empty; for example, this event is sent if the media has
|
||||
// already been loaded (or partially loaded), and the HTMLMediaElement.load method
|
||||
// is called to reload it.
|
||||
"emptied",
|
||||
// The user agent is trying to fetch media data, but data is unexpectedly not
|
||||
// forthcoming.
|
||||
"stalled",
|
||||
// Media data loading has been suspended.
|
||||
"suspend",
|
||||
// Playback has stopped because of a temporary lack of data
|
||||
"waiting",
|
||||
];
|
||||
const MEDIA_DEBUG_EVENT_TYPES: MediaEventType[] = [
|
||||
"play",
|
||||
"pause",
|
||||
"playing",
|
||||
"ended",
|
||||
"loadeddata",
|
||||
"loadedmetadata",
|
||||
"canplay",
|
||||
"canplaythrough",
|
||||
"volumechange",
|
||||
];
|
||||
|
||||
const MEDIA_EVENT_TYPES = [...MEDIA_ERROR_EVENT_TYPES, ...MEDIA_DEBUG_EVENT_TYPES];
|
||||
|
||||
export enum AudioID {
|
||||
Ring = "ringAudio",
|
||||
Ringback = "ringbackAudio",
|
||||
@@ -96,13 +66,6 @@ export enum AudioID {
|
||||
Busy = "busyAudio",
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const debuglog = (...args: any[]): void => {
|
||||
if (SettingsStore.getValue("debug_legacy_call_handler")) {
|
||||
logger.log.call(console, "LegacyCallHandler debuglog:", ...args);
|
||||
}
|
||||
};
|
||||
|
||||
interface ThirdpartyLookupResponse {
|
||||
userid: string;
|
||||
protocol: string;
|
||||
@@ -151,12 +114,8 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
private backgroundAudio = new BackgroundAudio();
|
||||
private playingSources: Record<string, AudioBufferSourceNode> = {}; // Record them for stopping
|
||||
|
||||
public static get instance(): LegacyCallHandler {
|
||||
if (!window.mxLegacyCallHandler) {
|
||||
window.mxLegacyCallHandler = new LegacyCallHandler();
|
||||
}
|
||||
|
||||
return window.mxLegacyCallHandler;
|
||||
public constructor(private readonly sdkContext: SDKContextClass) {
|
||||
super();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -193,18 +152,6 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next (remove if we start using this function for things other than debug logging) */
|
||||
public handleEvent(e: Event): void {
|
||||
const target = e.target as HTMLElement;
|
||||
const audioId = target?.id;
|
||||
|
||||
if (MEDIA_ERROR_EVENT_TYPES.includes(e.type as MediaEventType)) {
|
||||
logger.error(`LegacyCallHandler: encountered "${e.type}" event with <audio id="${audioId}">`, e);
|
||||
} else if (MEDIA_EVENT_TYPES.includes(e.type as MediaEventType)) {
|
||||
debuglog(`encountered "${e.type}" event with <audio id="${audioId}">`, e);
|
||||
}
|
||||
}
|
||||
|
||||
public isForcedSilent(): boolean {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
return localNotificationsAreSilenced(cli);
|
||||
@@ -308,7 +255,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||
const mappedRoomId = this.roomIdForCall(call);
|
||||
if (!mappedRoomId) return;
|
||||
if (this.getCallForRoom(mappedRoomId)) {
|
||||
logger.log(
|
||||
@@ -365,7 +312,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
|
||||
public getAllActiveCallsForPip(roomId: string): MatrixCall[] {
|
||||
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||
if (room && WidgetLayoutStore.instance.hasMaximisedWidget(room)) {
|
||||
if (room && this.sdkContext.widgetLayoutStore.hasMaximisedWidget(room)) {
|
||||
// This checks if there is space for the call view in the aux panel
|
||||
// If there is no space any call should be displayed in PiP
|
||||
return this.getAllActiveCalls();
|
||||
@@ -667,7 +614,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
}
|
||||
|
||||
private setCallState(call: MatrixCall, status: CallState): void {
|
||||
const mappedRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||
const mappedRoomId = this.roomIdForCall(call);
|
||||
|
||||
logger.log(`Call state in ${mappedRoomId} changed to ${status}`);
|
||||
|
||||
@@ -1022,12 +969,12 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
dis.dispatch({ action: "appsDrawer", show: true });
|
||||
|
||||
// Prevent double clicking the call button
|
||||
const widget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type));
|
||||
const widget = this.sdkContext.widgetStore.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type));
|
||||
if (widget) {
|
||||
// If there already is a Jitsi widget, pin it
|
||||
const room = client.getRoom(roomId);
|
||||
if (isNotNull(room)) {
|
||||
WidgetLayoutStore.instance.moveToContainer(room, widget, "top");
|
||||
this.sdkContext.widgetLayoutStore.moveToContainer(room, widget, "top");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1049,7 +996,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
|
||||
public hangupCallApp(roomId: string): void {
|
||||
logger.info("Leaving conference call in " + roomId);
|
||||
|
||||
const roomInfo = WidgetStore.instance.getRoom(roomId);
|
||||
const roomInfo = this.sdkContext.widgetStore.getRoom(roomId);
|
||||
if (!roomInfo) return; // "should never happen" clauses go here
|
||||
|
||||
const jitsiWidgets = roomInfo.widgets.filter((w) => WidgetType.JITSI.matches(w.type));
|
||||
|
||||
@@ -44,7 +44,6 @@ import { Jitsi } from "./widgets/Jitsi";
|
||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform";
|
||||
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||
import LegacyCallHandler from "./LegacyCallHandler";
|
||||
import LifecycleCustomisations from "./customisations/Lifecycle";
|
||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||
import { _t } from "./languageHandler";
|
||||
@@ -1102,7 +1101,7 @@ async function startMatrixClient(
|
||||
DMRoomMap.makeShared(client).start();
|
||||
IntegrationManagers.sharedInstance().startWatching();
|
||||
ActiveWidgetStore.instance.start();
|
||||
LegacyCallHandler.instance.start();
|
||||
SDKContextClass.instance.legacyCallHandler.start();
|
||||
checkBrowserSupport();
|
||||
|
||||
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
|
||||
@@ -1228,8 +1227,8 @@ export async function clearStorage(opts?: { deleteEverything?: boolean }): Promi
|
||||
* on MatrixClientPeg after stopping.
|
||||
*/
|
||||
export function stopMatrixClient(unsetClient = true): void {
|
||||
SDKContextClass.instance.legacyCallHandler.stop();
|
||||
SDKContextClass.instance.notifier.stop();
|
||||
LegacyCallHandler.instance.stop();
|
||||
UserActivity.sharedInstance().stop();
|
||||
SDKContextClass.instance.typingStore.reset();
|
||||
Presence.stop();
|
||||
|
||||
@@ -10,8 +10,9 @@ import { EventType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { CallEvent, CallState, CallType, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import { SDKContextClass } from "../../contexts/SDKContextClass.ts";
|
||||
|
||||
export enum LegacyCallEventGrouperEvent {
|
||||
StateChanged = "state_changed",
|
||||
@@ -65,8 +66,8 @@ export default class LegacyCallEventGrouper extends EventEmitter {
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallsChanged, this.setCall);
|
||||
LegacyCallHandler.instance.addListener(
|
||||
SDKContextClass.instance.legacyCallHandler.addListener(LegacyCallHandlerEvent.CallsChanged, this.setCall);
|
||||
SDKContextClass.instance.legacyCallHandler.addListener(
|
||||
LegacyCallHandlerEvent.SilencedCallsChanged,
|
||||
this.onSilencedCallsChanged,
|
||||
);
|
||||
@@ -133,7 +134,7 @@ export default class LegacyCallEventGrouper extends EventEmitter {
|
||||
}
|
||||
|
||||
private onSilencedCallsChanged = (): void => {
|
||||
const newState = LegacyCallHandler.instance.isCallSilenced(this.callId);
|
||||
const newState = SDKContextClass.instance.legacyCallHandler.isCallSilenced(this.callId);
|
||||
this.emit(LegacyCallEventGrouperEvent.SilencedChanged, newState);
|
||||
};
|
||||
|
||||
@@ -144,27 +145,27 @@ export default class LegacyCallEventGrouper extends EventEmitter {
|
||||
public answerCall = (): void => {
|
||||
const roomId = this.roomId;
|
||||
if (!roomId) return;
|
||||
LegacyCallHandler.instance.answerCall(roomId);
|
||||
SDKContextClass.instance.legacyCallHandler.answerCall(roomId);
|
||||
};
|
||||
|
||||
public rejectCall = (): void => {
|
||||
const roomId = this.roomId;
|
||||
if (!roomId) return;
|
||||
LegacyCallHandler.instance.hangupOrReject(roomId, true);
|
||||
SDKContextClass.instance.legacyCallHandler.hangupOrReject(roomId, true);
|
||||
};
|
||||
|
||||
public callBack = (): void => {
|
||||
const roomId = this.roomId;
|
||||
if (!roomId) return;
|
||||
LegacyCallHandler.instance.placeCall(roomId, this.isVoice ? CallType.Voice : CallType.Video);
|
||||
SDKContextClass.instance.legacyCallHandler.placeCall(roomId, this.isVoice ? CallType.Voice : CallType.Video);
|
||||
};
|
||||
|
||||
public toggleSilenced = (): void => {
|
||||
const silenced = LegacyCallHandler.instance.isCallSilenced(this.callId);
|
||||
const silenced = SDKContextClass.instance.legacyCallHandler.isCallSilenced(this.callId);
|
||||
if (silenced) {
|
||||
LegacyCallHandler.instance.unSilenceCall(this.callId);
|
||||
SDKContextClass.instance.legacyCallHandler.unSilenceCall(this.callId);
|
||||
} else {
|
||||
LegacyCallHandler.instance.silenceCall(this.callId);
|
||||
SDKContextClass.instance.legacyCallHandler.silenceCall(this.callId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -195,7 +196,7 @@ export default class LegacyCallEventGrouper extends EventEmitter {
|
||||
const callId = this.callId;
|
||||
if (!callId || this.call) return;
|
||||
|
||||
this.call = LegacyCallHandler.instance.getCallById(callId);
|
||||
this.call = SDKContextClass.instance.legacyCallHandler.getCallById(callId);
|
||||
this.setCallListeners();
|
||||
this.setState();
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ import Modal from "../../Modal";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { type IOpts } from "../../createRoom";
|
||||
import SpacePanel from "../views/spaces/SpacePanel";
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import AudioFeedArrayForLegacyCall from "../views/voip/AudioFeedArrayForLegacyCall";
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
@@ -138,7 +138,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
// use compact timeline view
|
||||
useCompactLayout: SettingsStore.getValue("useCompactLayout"),
|
||||
usageLimitDismissed: false,
|
||||
activeCalls: LegacyCallHandler.instance.getAllActiveCalls(),
|
||||
activeCalls: context.legacyCallHandler.getAllActiveCalls(),
|
||||
};
|
||||
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
@@ -151,7 +151,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
|
||||
public componentDidMount(): void {
|
||||
document.addEventListener("keydown", this.onNativeKeyDown, false);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this.context.legacyCallHandler.addListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
this.updateServerNoticeEvents();
|
||||
|
||||
@@ -229,7 +229,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
document.removeEventListener("keydown", this.onNativeKeyDown, false);
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this._matrixClient.removeListener(ClientEvent.AccountData, this.onAccountData);
|
||||
this._matrixClient.removeListener(ClientEvent.Sync, this.onSync);
|
||||
this._matrixClient.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
@@ -242,7 +242,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
private onCallState = (): void => {
|
||||
const activeCalls = LegacyCallHandler.instance.getAllActiveCalls();
|
||||
const activeCalls = this.context.legacyCallHandler.getAllActiveCalls();
|
||||
if (activeCalls === this.state.activeCalls) return;
|
||||
this.setState({ activeCalls });
|
||||
};
|
||||
|
||||
@@ -95,7 +95,6 @@ import SoftLogout from "./auth/SoftLogout";
|
||||
import { copyPlaintext } from "../../utils/strings";
|
||||
import { PosthogAnalytics } from "../../PosthogAnalytics";
|
||||
import { initSentry } from "../../sentry";
|
||||
import LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { showSpaceInvite } from "../../utils/space";
|
||||
import { type ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import { type ActionPayload } from "../../dispatcher/payloads";
|
||||
@@ -695,7 +694,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
break;
|
||||
case "logout":
|
||||
LegacyCallHandler.instance.hangupAllCalls();
|
||||
this.stores.legacyCallHandler.hangupAllCalls();
|
||||
Promise.all([...[...CallStore.instance.connectedCalls].map((call) => call.disconnect())]).finally(() =>
|
||||
Lifecycle.logout(this.stores.oidcClientStore),
|
||||
);
|
||||
|
||||
@@ -12,8 +12,8 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { useCreateAutoDisposedViewModel, WidgetPipView } from "@element-hq/web-shared-components";
|
||||
|
||||
import LegacyCallView from "../views/voip/LegacyCallView";
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import type LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import PictureInPictureDragger, { type CreatePipChildren } from "./PictureInPictureDragger";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
@@ -21,9 +21,9 @@ import { WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
||||
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../stores/ActiveWidgetStore";
|
||||
import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import { SDKContextClass } from "../../contexts/SDKContextClass";
|
||||
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||
import { WidgetPipViewModel, type Props as WidgetPipViewModelProps } from "../../viewmodels/room/WidgetPipViewModel";
|
||||
import { SDKContext } from "../../contexts/SDKContext.ts";
|
||||
|
||||
const SHOW_CALL_IN_STATES = [
|
||||
CallState.Connected,
|
||||
@@ -58,10 +58,13 @@ interface IState {
|
||||
// (which should be a single element) of other calls.
|
||||
// The primary will be the one not on hold, or an arbitrary one
|
||||
// if they're all on hold)
|
||||
function getPrimarySecondaryCallsForPip(roomId: string | null): [MatrixCall | null, MatrixCall[]] {
|
||||
function getPrimarySecondaryCallsForPip(
|
||||
legacyCallHandler: LegacyCallHandler,
|
||||
roomId: string | null,
|
||||
): [MatrixCall | null, MatrixCall[]] {
|
||||
if (!roomId) return [null, []];
|
||||
|
||||
const calls = LegacyCallHandler.instance.getAllActiveCallsForPip(roomId);
|
||||
const calls = legacyCallHandler.getAllActiveCallsForPip(roomId);
|
||||
|
||||
let primary: MatrixCall | null = null;
|
||||
let secondaries: MatrixCall[] = [];
|
||||
@@ -96,12 +99,15 @@ function getPrimarySecondaryCallsForPip(roomId: string | null): [MatrixCall | nu
|
||||
*/
|
||||
|
||||
class PipContainerInner extends React.Component<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props);
|
||||
|
||||
const roomId = SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const roomId = context.roomViewStore.getRoomId();
|
||||
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(roomId);
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(context.legacyCallHandler, roomId);
|
||||
|
||||
this.state = {
|
||||
viewedRoomId: roomId || undefined,
|
||||
@@ -114,13 +120,13 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
SDKContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
MatrixClientPeg.safeGet().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
const room = MatrixClientPeg.safeGet().getRoom(this.state.viewedRoomId);
|
||||
this.context.legacyCallHandler.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||
this.context.legacyCallHandler.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
this.context.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
this.context.client?.on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
const room = this.context.client?.getRoom(this.state.viewedRoomId);
|
||||
if (room) {
|
||||
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||
}
|
||||
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Persistence, this.onWidgetPersistence);
|
||||
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Dock, this.onWidgetDockChanges);
|
||||
@@ -128,14 +134,13 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli?.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
SDKContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
const room = cli?.getRoom(this.state.viewedRoomId);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
this.context.client?.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
this.context.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
const room = this.context.client?.getRoom(this.state.viewedRoomId);
|
||||
if (room) {
|
||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||
this.context.widgetLayoutStore.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||
}
|
||||
ActiveWidgetStore.instance.off(ActiveWidgetStoreEvent.Persistence, this.onWidgetPersistence);
|
||||
ActiveWidgetStore.instance.off(ActiveWidgetStoreEvent.Dock, this.onWidgetDockChanges);
|
||||
@@ -145,22 +150,22 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
private onMove = (): void => this.props.movePersistedElement.current?.();
|
||||
|
||||
private onRoomViewStoreUpdate = (): void => {
|
||||
const newRoomId = SDKContextClass.instance.roomViewStore.getRoomId();
|
||||
const newRoomId = this.context.roomViewStore.getRoomId();
|
||||
const oldRoomId = this.state.viewedRoomId;
|
||||
if (newRoomId === oldRoomId) return;
|
||||
// The WidgetLayoutStore observer always tracks the currently viewed Room,
|
||||
// so we don't end up with multiple observers and know what observer to remove on unmount
|
||||
const oldRoom = MatrixClientPeg.get()?.getRoom(oldRoomId);
|
||||
const oldRoom = this.context.client?.getRoom(oldRoomId);
|
||||
if (oldRoom) {
|
||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(oldRoom), this.updateCalls);
|
||||
this.context.widgetLayoutStore.off(WidgetLayoutStore.emissionForRoom(oldRoom), this.updateCalls);
|
||||
}
|
||||
const newRoom = MatrixClientPeg.get()?.getRoom(newRoomId || undefined);
|
||||
const newRoom = this.context.client?.getRoom(newRoomId || undefined);
|
||||
if (newRoom) {
|
||||
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(newRoom), this.updateCalls);
|
||||
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(newRoom), this.updateCalls);
|
||||
}
|
||||
if (!newRoomId) return;
|
||||
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(newRoomId);
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(this.context.legacyCallHandler, newRoomId);
|
||||
this.setState({
|
||||
viewedRoomId: newRoomId,
|
||||
primaryCall: primaryCall,
|
||||
@@ -179,7 +184,10 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
|
||||
private updateCalls = (): void => {
|
||||
if (!this.state.viewedRoomId) return;
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(this.state.viewedRoomId);
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(
|
||||
this.context.legacyCallHandler,
|
||||
this.state.viewedRoomId,
|
||||
);
|
||||
|
||||
this.setState({
|
||||
primaryCall: primaryCall,
|
||||
@@ -190,7 +198,10 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
|
||||
private onCallRemoteHold = (): void => {
|
||||
if (!this.state.viewedRoomId) return;
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(this.state.viewedRoomId);
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(
|
||||
this.context.legacyCallHandler,
|
||||
this.state.viewedRoomId,
|
||||
);
|
||||
|
||||
this.setState({
|
||||
primaryCall: primaryCall,
|
||||
@@ -217,7 +228,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
let notDocked = false;
|
||||
// Sanity check the room - the widget may have been destroyed between render cycles, and
|
||||
// thus no room is associated anymore.
|
||||
if (persistentWidgetId && persistentRoomId && MatrixClientPeg.safeGet().getRoom(persistentRoomId)) {
|
||||
if (persistentWidgetId && persistentRoomId && this.context.client?.getRoom(persistentRoomId)) {
|
||||
notDocked = !ActiveWidgetStore.instance.isDocked(persistentWidgetId, persistentRoomId);
|
||||
fromAnotherRoom = this.state.viewedRoomId !== persistentRoomId;
|
||||
}
|
||||
@@ -256,7 +267,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||
<WidgetPipWrappedView
|
||||
key="widget-pip"
|
||||
widgetId={this.state.persistentWidgetId!}
|
||||
room={MatrixClientPeg.safeGet().getRoom(this.state.persistentRoomId ?? undefined)!}
|
||||
room={this.context.client!.getRoom(this.state.persistentRoomId ?? undefined)!}
|
||||
viewingRoom={this.state.viewedRoomId === this.state.persistentRoomId}
|
||||
onStartMoving={onStartMoving}
|
||||
movePersistedElement={this.props.movePersistedElement}
|
||||
|
||||
@@ -10,26 +10,29 @@ import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import ContextMenu, { type IProps as IContextMenuProps, MenuItem } from "../../structures/ContextMenu";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
call: MatrixCall;
|
||||
}
|
||||
|
||||
export default class LegacyCallContextMenu extends React.Component<IProps> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
public onHoldClick = (): void => {
|
||||
this.props.call.setRemoteOnHold(true);
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
public onUnholdClick = (): void => {
|
||||
LegacyCallHandler.instance.setActiveCallRoomId(this.props.call.roomId);
|
||||
this.context.legacyCallHandler.setActiveCallRoomId(this.props.call.roomId);
|
||||
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
public onTransferClick = (): void => {
|
||||
LegacyCallHandler.instance.showTransferDialog(this.props.call);
|
||||
this.context.legacyCallHandler.showTransferDialog(this.props.call);
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import Dialpad from "../voip/DialPad";
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import CopyableText from "../elements/CopyableText";
|
||||
import { type ScreenName } from "../../../PosthogTrackers";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
@@ -483,9 +482,13 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
return;
|
||||
}
|
||||
|
||||
LegacyCallHandler.instance.startTransferToMatrixID(this.props.call, targetIds[0], this.state.consultFirst);
|
||||
SDKContextClass.instance.legacyCallHandler.startTransferToMatrixID(
|
||||
this.props.call,
|
||||
targetIds[0],
|
||||
this.state.consultFirst,
|
||||
);
|
||||
} else {
|
||||
LegacyCallHandler.instance.startTransferToPhoneNumber(
|
||||
SDKContextClass.instance.legacyCallHandler.startTransferToPhoneNumber(
|
||||
this.props.call,
|
||||
this.state.dialPadValue,
|
||||
this.state.consultFirst,
|
||||
|
||||
@@ -45,25 +45,21 @@ import PersistedElement, { getPersistKey } from "./PersistedElement";
|
||||
import { WidgetType } from "../../../widgets/WidgetType";
|
||||
import { ElementWidget, WidgetMessaging, WidgetMessagingEvent } from "../../../stores/widgets/WidgetMessaging";
|
||||
import WidgetAvatar from "../avatars/WidgetAvatar";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import { type IApp, isAppWidget } from "../../../stores/WidgetStore";
|
||||
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { type ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { ElementWidgetCapabilities } from "../../../stores/widgets/ElementWidgetCapabilities";
|
||||
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass";
|
||||
import { ModuleRunner } from "../../../modules/ModuleRunner";
|
||||
import { ModuleApi } from "../../../modules/Api";
|
||||
import { toWidgetDescriptor } from "../../../modules/WidgetLifecycleApi";
|
||||
import { parseUrl } from "../../../utils/UrlUtils";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore.ts";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases.ts";
|
||||
import { WidgetContextMenu } from "../../../viewmodels/room/right-panel/WidgetContextMenuViewModel.tsx";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
|
||||
// because that would allow the iframe to programmatically remove the sandbox attribute, but
|
||||
@@ -138,8 +134,8 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class AppTile extends React.Component<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
declare public context: ContextType<typeof MatrixClientContext>;
|
||||
public static contextType = SDKContext;
|
||||
declare public context: ContextType<typeof SDKContext>;
|
||||
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
waitForIframeLoad: true,
|
||||
@@ -162,7 +158,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
private dispatcherRef?: string;
|
||||
private unmounted = false;
|
||||
|
||||
public constructor(props: IProps, context: ContextType<typeof MatrixClientContext>) {
|
||||
public constructor(props: IProps, context: ContextType<typeof SDKContext>) {
|
||||
super(props, context);
|
||||
|
||||
// The key used for PersistedElement
|
||||
@@ -257,7 +253,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
);
|
||||
if (isActiveWidget) {
|
||||
// We just left the room that the active widget was from.
|
||||
if (this.props.room && SDKContextClass.instance.roomViewStore.getRoomId() !== this.props.room.roomId) {
|
||||
if (this.props.room && this.context.roomViewStore.getRoomId() !== this.props.room.roomId) {
|
||||
// If we are not actively looking at the room then destroy this widget entirely.
|
||||
this.endWidgetActions();
|
||||
} else if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
@@ -393,7 +389,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
this.watchUserReady();
|
||||
|
||||
if (this.props.room) {
|
||||
this.context.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
this.context.client?.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
}
|
||||
this.allowedWidgetsWatchRef = SettingsStore.watchSetting("allowedWidgets", null, this.onAllowedWidgetsChange);
|
||||
// Widget action listeners
|
||||
@@ -426,7 +422,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
|
||||
if (this.props.room) {
|
||||
this.context.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
this.context.client?.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
}
|
||||
|
||||
SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
|
||||
@@ -545,7 +541,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
*/
|
||||
private endWidgetActions(): void {
|
||||
if (WidgetType.JITSI.matches(this.props.app.type) && this.props.room) {
|
||||
LegacyCallHandler.instance.hangupCallApp(this.props.room.roomId);
|
||||
this.context.legacyCallHandler.hangupCallApp(this.props.room.roomId);
|
||||
}
|
||||
|
||||
// Delete the widget from the persisted store for good measure.
|
||||
@@ -682,25 +678,27 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
|
||||
private onToggleMaximisedClick = (): void => {
|
||||
if (!this.props.room) return; // ignore action - it shouldn't even be visible
|
||||
const targetContainer = WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, "center")
|
||||
const targetContainer = this.context.widgetLayoutStore.isInContainer(this.props.room, this.props.app, "center")
|
||||
? "top"
|
||||
: "center";
|
||||
WidgetLayoutStore.instance.moveToContainer(this.props.room, this.props.app, targetContainer);
|
||||
this.context.widgetLayoutStore.moveToContainer(this.props.room, this.props.app, targetContainer);
|
||||
|
||||
if (targetContainer === "top") this.closeChatCardIfNeeded();
|
||||
};
|
||||
|
||||
private onMinimiseClicked = (): void => {
|
||||
if (!this.props.room) return; // ignore action - it shouldn't even be visible
|
||||
WidgetLayoutStore.instance.moveToContainer(this.props.room, this.props.app, "right");
|
||||
this.context.widgetLayoutStore.moveToContainer(this.props.room, this.props.app, "right");
|
||||
this.closeChatCardIfNeeded();
|
||||
};
|
||||
|
||||
private closeChatCardIfNeeded = (): void => {
|
||||
if (!this.props.room) return; // ignore action - it shouldn't even be visible
|
||||
// If the right panel has a timeline, but we're about to show the timeline in the main view, pop the right panel
|
||||
if (RightPanelStore.instance.currentCardForRoom(this.props.room.roomId).phase === RightPanelPhases.Timeline) {
|
||||
RightPanelStore.instance.popCard(this.props.room.roomId);
|
||||
if (
|
||||
this.context.rightPanelStore.currentCardForRoom(this.props.room.roomId).phase === RightPanelPhases.Timeline
|
||||
) {
|
||||
this.context.rightPanelStore.popCard(this.props.room.roomId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -742,7 +740,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
);
|
||||
} else if (!this.state.hasPermissionToLoad && this.props.room && this.messaging) {
|
||||
// only possible for room widgets, can assert this.props.room here
|
||||
const isEncrypted = this.context.isRoomEncrypted(this.props.room.roomId);
|
||||
const isEncrypted = this.context.client?.isRoomEncrypted(this.props.room.roomId);
|
||||
appTileBody = (
|
||||
<div className={appTileBodyClass} style={appTileBodyStyles}>
|
||||
<AppPermission
|
||||
@@ -818,7 +816,8 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
const layoutButtons: ReactNode[] = [];
|
||||
if (this.props.showLayoutButtons) {
|
||||
const isMaximised =
|
||||
this.props.room && WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, "center");
|
||||
this.props.room &&
|
||||
this.context.widgetLayoutStore.isInContainer(this.props.room, this.props.app, "center");
|
||||
|
||||
layoutButtons.push(
|
||||
<AccessibleButton
|
||||
|
||||
@@ -14,6 +14,7 @@ import dis from "../../../dispatcher/dispatcher";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { type ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
export const getPersistKey = (appId: string): string => "widget_" + appId;
|
||||
|
||||
@@ -70,6 +71,9 @@ interface IProps {
|
||||
* bounding rect as the parent of PE.
|
||||
*/
|
||||
export default class PersistedElement extends React.Component<IProps> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private resizeObserver: ResizeObserver;
|
||||
private dispatcherRef?: string;
|
||||
private childContainer?: HTMLDivElement;
|
||||
@@ -165,13 +169,15 @@ export default class PersistedElement extends React.Component<IProps> {
|
||||
private renderApp(): void {
|
||||
const content = (
|
||||
<StrictMode>
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
|
||||
<TooltipProvider>
|
||||
<div ref={this.collectChild} style={this.props.style}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
<SDKContext.Provider value={this.context}>
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
|
||||
<TooltipProvider>
|
||||
<div ref={this.collectChild} style={this.props.style}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
</SDKContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useEffect, type JSX } from "react";
|
||||
import React, { useEffect, type JSX, useContext } from "react";
|
||||
import { RoomListSearchView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
||||
|
||||
import { RoomListSearchViewModel } from "../../../../viewmodels/room-list/RoomListSearchViewModel";
|
||||
import { SDKContext } from "../../../../contexts/SDKContext.ts";
|
||||
|
||||
type RoomListSearchProps = {
|
||||
/**
|
||||
@@ -23,7 +24,10 @@ type RoomListSearchProps = {
|
||||
* The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled.
|
||||
*/
|
||||
export function RoomListSearch({ activeSpace }: RoomListSearchProps): JSX.Element {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new RoomListSearchViewModel({ activeSpace }));
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const vm = useCreateAutoDisposedViewModel(
|
||||
() => new RoomListSearchViewModel({ activeSpace, legacyCallHandler: sdkContext.legacyCallHandler }),
|
||||
);
|
||||
useEffect(() => {
|
||||
vm.setActiveSpace(activeSpace);
|
||||
}, [activeSpace, vm]);
|
||||
|
||||
@@ -13,7 +13,7 @@ import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton
|
||||
import Field from "../elements/Field";
|
||||
import DialPad from "./DialPad";
|
||||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import { SDKContextClass } from "../../../contexts/SDKContextClass.ts";
|
||||
|
||||
interface IProps {
|
||||
onFinished: (dialled: boolean) => void;
|
||||
@@ -70,7 +70,7 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
||||
};
|
||||
|
||||
public onDialPress = async (): Promise<void> => {
|
||||
LegacyCallHandler.instance.dialNumber(this.state.value);
|
||||
SDKContextClass.instance.legacyCallHandler.dialNumber(this.state.value);
|
||||
this.props.onFinished(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
|
||||
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
|
||||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import VideoFeed from "./VideoFeed";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
@@ -28,6 +26,7 @@ import LegacyCallViewButtons from "./LegacyCallView/LegacyCallViewButtons";
|
||||
import { type ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
// The call for us to display
|
||||
@@ -81,6 +80,9 @@ function exitFullscreen(): void {
|
||||
}
|
||||
|
||||
export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
private contentWrapperRef = createRef<HTMLDivElement>();
|
||||
private buttonsRef = createRef<LegacyCallViewButtons>();
|
||||
@@ -308,18 +310,18 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
private onCallResumeClick = (): void => {
|
||||
const userFacingRoomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
||||
if (userFacingRoomId) LegacyCallHandler.instance.setActiveCallRoomId(userFacingRoomId);
|
||||
const userFacingRoomId = this.context.legacyCallHandler.roomIdForCall(this.props.call);
|
||||
if (userFacingRoomId) this.context.legacyCallHandler.setActiveCallRoomId(userFacingRoomId);
|
||||
};
|
||||
|
||||
private onTransferClick = (): void => {
|
||||
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(this.props.call.callId);
|
||||
const transfereeCall = this.context.legacyCallHandler.getTransfereeForCallId(this.props.call.callId);
|
||||
if (transfereeCall) this.props.call.transferToCall(transfereeCall);
|
||||
};
|
||||
|
||||
private onHangupClick = (): void => {
|
||||
const roomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
||||
if (roomId) LegacyCallHandler.instance.hangupOrReject(roomId);
|
||||
const roomId = this.context.legacyCallHandler.roomIdForCall(this.props.call);
|
||||
if (roomId) this.context.legacyCallHandler.hangupOrReject(roomId);
|
||||
};
|
||||
|
||||
private onToggleSidebar = (): void => {
|
||||
@@ -400,10 +402,10 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
const { pipMode, call, onResize, sidebarShown } = this.props;
|
||||
const { isLocalOnHold, isRemoteOnHold, primaryFeed, secondaryFeed, sidebarFeeds } = this.state;
|
||||
|
||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||
const callRoom = (callRoomId ? MatrixClientPeg.safeGet().getRoom(callRoomId) : undefined) ?? undefined;
|
||||
const callRoomId = this.context.legacyCallHandler.roomIdForCall(call);
|
||||
const callRoom = (callRoomId ? this.context.client?.getRoom(callRoomId) : undefined) ?? undefined;
|
||||
const avatarSize = pipMode ? "76px" : "160px";
|
||||
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(call.callId);
|
||||
const transfereeCall = this.context.legacyCallHandler.getTransfereeForCallId(call.callId);
|
||||
const isOnHold = isLocalOnHold || isRemoteOnHold;
|
||||
|
||||
let secondaryFeedElement: React.ReactNode;
|
||||
@@ -421,12 +423,11 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
|
||||
let holdTransferContent: React.ReactNode;
|
||||
if (transfereeCall) {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||
const transferTargetRoom = callRoomId ? cli.getRoom(callRoomId) : null;
|
||||
const callRoomId = this.context.legacyCallHandler.roomIdForCall(call);
|
||||
const transferTargetRoom = callRoomId ? this.context.client?.getRoom(callRoomId) : null;
|
||||
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("voip|unknown_person");
|
||||
const transfereeCallRoomId = LegacyCallHandler.instance.roomIdForCall(transfereeCall);
|
||||
const transfereeRoom = transfereeCallRoomId ? cli.getRoom(transfereeCallRoomId) : null;
|
||||
const transfereeCallRoomId = this.context.legacyCallHandler.roomIdForCall(transfereeCall);
|
||||
const transfereeRoom = transfereeCallRoomId ? this.context.client?.getRoom(transfereeCallRoomId) : null;
|
||||
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("voip|unknown_person");
|
||||
|
||||
holdTransferContent = (
|
||||
@@ -451,7 +452,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
let onHoldText: React.ReactNode;
|
||||
if (isRemoteOnHold) {
|
||||
onHoldText = _t(
|
||||
LegacyCallHandler.instance.hasAnyUnheldCall()
|
||||
this.context.legacyCallHandler.hasAnyUnheldCall()
|
||||
? _td("voip|call_held_switch")
|
||||
: _td("voip|call_held_resume"),
|
||||
{},
|
||||
@@ -544,13 +545,12 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
const { call, secondaryCall, pipMode, showApps, onMouseDownOnHeader, sidebarShown } = this.props;
|
||||
const { sidebarFeeds } = this.state;
|
||||
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
|
||||
const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
|
||||
const callRoomId = this.context.legacyCallHandler.roomIdForCall(call);
|
||||
const secondaryCallRoomId = this.context.legacyCallHandler.roomIdForCall(secondaryCall);
|
||||
const callRoom = callRoomId ? this.context.client?.getRoom(callRoomId) : null;
|
||||
if (!callRoom) return null;
|
||||
|
||||
const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
|
||||
const secCallRoom = secondaryCallRoomId ? this.context.client?.getRoom(secondaryCallRoomId) : null;
|
||||
|
||||
const callViewClasses = classNames({
|
||||
mx_LegacyCallView: true,
|
||||
@@ -565,7 +565,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
<LegacyCallViewHeader
|
||||
onPipMouseDown={onMouseDownOnHeader}
|
||||
pipMode={pipMode}
|
||||
callRooms={[callRoom, secCallRoom]}
|
||||
callRooms={[callRoom, secCallRoom ?? null]}
|
||||
onMaximize={this.onMaximizeClick}
|
||||
/>
|
||||
<div className="mx_LegacyCallView_content_wrapper" ref={this.contentWrapperRef}>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CallState, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import React from "react";
|
||||
import { Resizable } from "re-resizable";
|
||||
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../../LegacyCallHandler";
|
||||
import LegacyCallView from "./LegacyCallView";
|
||||
import { SDKContext } from "../../../contexts/SDKContext";
|
||||
|
||||
@@ -39,20 +39,20 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
|
||||
const call = this.getCall();
|
||||
this.state = {
|
||||
call,
|
||||
sidebarShown: !!call && LegacyCallHandler.instance.isCallSidebarShown(call.callId),
|
||||
sidebarShown: !!call && context.legacyCallHandler.isCallSidebarShown(call.callId),
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCall);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
|
||||
this.context.legacyCallHandler.addListener(LegacyCallHandlerEvent.CallState, this.updateCall);
|
||||
this.context.legacyCallHandler.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
|
||||
this.context.legacyCallHandler.addListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCall);
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.updateCall);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
|
||||
}
|
||||
|
||||
private updateCall = (): void => {
|
||||
@@ -60,14 +60,14 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
|
||||
if (newCall !== this.state.call) {
|
||||
this.setState({ call: newCall });
|
||||
}
|
||||
const newSidebarShown = !!newCall && LegacyCallHandler.instance.isCallSidebarShown(newCall.callId);
|
||||
const newSidebarShown = !!newCall && this.context.legacyCallHandler.isCallSidebarShown(newCall.callId);
|
||||
if (newSidebarShown !== this.state.sidebarShown) {
|
||||
this.setState({ sidebarShown: newSidebarShown });
|
||||
}
|
||||
};
|
||||
|
||||
private getCall(): MatrixCall | null {
|
||||
const call = LegacyCallHandler.instance.getCallForRoom(this.props.roomId);
|
||||
const call = this.context.legacyCallHandler.getCallForRoom(this.props.roomId);
|
||||
|
||||
if (call && [CallState.Ended, CallState.Ringing].includes(call.state)) return null;
|
||||
return call;
|
||||
@@ -87,7 +87,7 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
|
||||
|
||||
private setSidebarShown = (sidebarShown: boolean): void => {
|
||||
if (!this.state.call) return;
|
||||
LegacyCallHandler.instance.setCallSidebarShown(this.state.call.callId, sidebarShown);
|
||||
this.context.legacyCallHandler.setCallSidebarShown(this.state.call.callId, sidebarShown);
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
|
||||
@@ -16,9 +16,8 @@ import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventType
|
||||
import { MicOffSolidIcon, MicOnSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import LegacyCallHandler from "../../../LegacyCallHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { SDKContext } from "../../../contexts/SDKContext.ts";
|
||||
|
||||
interface IProps {
|
||||
call: MatrixCall;
|
||||
@@ -45,6 +44,9 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private element?: HTMLVideoElement;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
@@ -192,8 +194,8 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||
|
||||
let content;
|
||||
if (this.state.videoMuted) {
|
||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
||||
const callRoom = (callRoomId ? MatrixClientPeg.safeGet().getRoom(callRoomId) : undefined) ?? undefined;
|
||||
const callRoomId = this.context.legacyCallHandler.roomIdForCall(this.props.call);
|
||||
const callRoom = (callRoomId ? this.context.client?.getRoom(callRoomId) : undefined) ?? undefined;
|
||||
|
||||
let avatarSize;
|
||||
if (pipMode && primary) avatarSize = "76px";
|
||||
|
||||
@@ -97,7 +97,7 @@ export class SDKContextClass {
|
||||
|
||||
public get legacyCallHandler(): LegacyCallHandler {
|
||||
if (!this._LegacyCallHandler) {
|
||||
this._LegacyCallHandler = LegacyCallHandler.instance;
|
||||
this._LegacyCallHandler = new LegacyCallHandler(this);
|
||||
}
|
||||
return this._LegacyCallHandler;
|
||||
}
|
||||
|
||||
@@ -349,7 +349,6 @@ export interface Settings {
|
||||
"debug_timeline_panel": IBaseSetting<boolean>;
|
||||
"debug_registration": IBaseSetting<boolean>;
|
||||
"debug_animation": IBaseSetting<boolean>;
|
||||
"debug_legacy_call_handler": IBaseSetting<boolean>;
|
||||
"audioInputMuted": IBaseSetting<boolean>;
|
||||
"videoInputMuted": IBaseSetting<boolean>;
|
||||
"activeCallRoomIds": IBaseSetting<string[]>;
|
||||
@@ -1322,10 +1321,6 @@ export const SETTINGS: Settings = {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
default: false,
|
||||
},
|
||||
"debug_legacy_call_handler": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
default: false,
|
||||
},
|
||||
"audioInputMuted": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
default: false,
|
||||
|
||||
@@ -44,7 +44,6 @@ import SdkConfig from "../SdkConfig";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { UIComponent, UIFeature } from "../settings/UIFeature";
|
||||
import { CHAT_EFFECTS } from "../effects";
|
||||
import LegacyCallHandler from "../LegacyCallHandler";
|
||||
import { guessAndSetDMRoom } from "../Rooms";
|
||||
import DevtoolsDialog from "../components/views/dialogs/DevtoolsDialog";
|
||||
import InfoDialog from "../components/views/dialogs/InfoDialog";
|
||||
@@ -64,6 +63,7 @@ import { manuallyVerifyDevice } from "../components/views/dialogs/ManualDeviceKe
|
||||
import upgraderoom from "./upgraderoom/upgraderoom";
|
||||
import { emoticon } from "./emoticon";
|
||||
import { statusCommand } from "./status";
|
||||
import { SDKContextClass } from "../contexts/SDKContextClass.ts";
|
||||
|
||||
export { CommandCategories, Command };
|
||||
|
||||
@@ -712,7 +712,7 @@ export const Commands = [
|
||||
return success(
|
||||
(async (): Promise<void> => {
|
||||
if (isPhoneNumber) {
|
||||
const results = await LegacyCallHandler.instance.pstnLookup(userId);
|
||||
const results = await SDKContextClass.instance.legacyCallHandler.pstnLookup(userId);
|
||||
if (!results || results.length === 0 || !results[0].userid) {
|
||||
throw new UserFriendlyError("slash_command|query_not_found_phone_number");
|
||||
}
|
||||
@@ -773,7 +773,7 @@ export const Commands = [
|
||||
category: CommandCategories.other,
|
||||
isEnabled: (cli) => !isCurrentLocalRoom(cli),
|
||||
runFn: function (cli, roomId, threadId, args) {
|
||||
const call = LegacyCallHandler.instance.getCallForRoom(roomId);
|
||||
const call = SDKContextClass.instance.legacyCallHandler.getCallForRoom(roomId);
|
||||
if (!call) {
|
||||
return reject(new UserFriendlyError("slash_command|no_active_call"));
|
||||
}
|
||||
@@ -788,7 +788,7 @@ export const Commands = [
|
||||
category: CommandCategories.other,
|
||||
isEnabled: (cli) => !isCurrentLocalRoom(cli),
|
||||
runFn: function (cli, roomId, threadId, args) {
|
||||
const call = LegacyCallHandler.instance.getCallForRoom(roomId);
|
||||
const call = SDKContextClass.instance.legacyCallHandler.getCallForRoom(roomId);
|
||||
if (!call) {
|
||||
return reject(new UserFriendlyError("slash_command|no_active_call"));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
useId,
|
||||
useContext,
|
||||
} from "react";
|
||||
import {
|
||||
type Room,
|
||||
@@ -39,7 +40,6 @@ import { AvatarWithDetails } from "@element-hq/web-shared-components";
|
||||
|
||||
import { _t } from "../languageHandler";
|
||||
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
@@ -49,11 +49,12 @@ import AccessibleButton, { type ButtonEvent } from "../components/views/elements
|
||||
import { useDispatcher } from "../hooks/useDispatcher";
|
||||
import { type ActionPayload } from "../dispatcher/payloads";
|
||||
import { type Call, CallEvent } from "../models/Call";
|
||||
import LegacyCallHandler, { AudioID } from "../LegacyCallHandler";
|
||||
import { AudioID } from "../LegacyCallHandler";
|
||||
import { useEventEmitter, useTypedEventEmitter } from "../hooks/useEventEmitter";
|
||||
import { CallStore, CallStoreEvent } from "../stores/CallStore";
|
||||
import DMRoomMap from "../utils/DMRoomMap";
|
||||
import MemberAvatar from "../components/views/avatars/MemberAvatar";
|
||||
import { SDKContext } from "../contexts/SDKContext.ts";
|
||||
|
||||
/**
|
||||
* Get the key for the incoming call toast. A combination of the call ID and room ID.
|
||||
@@ -158,10 +159,11 @@ interface Props {
|
||||
}
|
||||
|
||||
export function IncomingCallToast({ notificationEvent, toastKey }: Props): JSX.Element {
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const roomId = notificationEvent.getRoomId()!;
|
||||
// Use a partial type so ts still helps us to not miss any type checks.
|
||||
const notificationContent = notificationEvent.getContent() as Partial<IRTCNotificationContent>;
|
||||
const room = MatrixClientPeg.safeGet().getRoom(roomId) ?? undefined;
|
||||
const room = sdkContext.client?.getRoom(roomId) ?? undefined;
|
||||
const call = useCall(roomId);
|
||||
const [connectedCalls, setConnectedCalls] = useState<Call[]>(Array.from(CallStore.instance.connectedCalls));
|
||||
useEventEmitter(CallStore.instance, CallStoreEvent.ConnectedCalls, () => {
|
||||
@@ -174,18 +176,18 @@ export function IncomingCallToast({ notificationEvent, toastKey }: Props): JSX.E
|
||||
// This is because `LegacyCallHandler.play` tries to load the sound and then play it asynchonously
|
||||
// and `LegacyCallHandler.isPlaying` will not be `true` until the sound starts playing.
|
||||
const isRingToast = notificationContent.notification_type === "ring";
|
||||
if (isRingToast && !soundHasStarted.current && !LegacyCallHandler.instance.isPlaying(AudioID.Ring)) {
|
||||
if (isRingToast && !soundHasStarted.current && !sdkContext.legacyCallHandler.isPlaying(AudioID.Ring)) {
|
||||
// Start ringing if not already.
|
||||
soundHasStarted.current = true;
|
||||
void LegacyCallHandler.instance.play(AudioID.Ring);
|
||||
void sdkContext.legacyCallHandler.play(AudioID.Ring);
|
||||
}
|
||||
}, [notificationContent.notification_type, soundHasStarted]);
|
||||
}, [notificationContent.notification_type, soundHasStarted, sdkContext.legacyCallHandler]);
|
||||
|
||||
// Stop ringing on dismiss.
|
||||
const dismissToast = useCallback((): void => {
|
||||
ToastStore.sharedInstance().dismissToast(toastKey);
|
||||
LegacyCallHandler.instance.pause(AudioID.Ring);
|
||||
}, [toastKey]);
|
||||
sdkContext.legacyCallHandler.pause(AudioID.Ring);
|
||||
}, [toastKey, sdkContext.legacyCallHandler]);
|
||||
|
||||
// Dismiss if the notification event or call event is redacted
|
||||
useTypedEventEmitter(room, MatrixEventEvent.BeforeRedaction, (ev: MatrixEvent) => {
|
||||
|
||||
@@ -19,12 +19,13 @@ import {
|
||||
VolumeOnSolidIcon,
|
||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../LegacyCallHandler";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { _t } from "../languageHandler";
|
||||
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||
import AccessibleButton, { type ButtonEvent } from "../components/views/elements/AccessibleButton";
|
||||
import { getCallStateIcon } from "../components/views/messages/LegacyCallEvent.tsx";
|
||||
import { SDKContext } from "../contexts/SDKContext.ts";
|
||||
|
||||
export const getIncomingLegacyCallToastKey = (callId: string): string => `call_${callId}`;
|
||||
|
||||
@@ -37,64 +38,67 @@ interface IState {
|
||||
}
|
||||
|
||||
export default class IncomingLegacyCallToast extends React.Component<IProps, IState> {
|
||||
public static contextType = SDKContext;
|
||||
declare public context: React.ContextType<typeof SDKContext>;
|
||||
|
||||
private readonly roomId: string;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props, context);
|
||||
|
||||
const roomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
||||
const roomId = context.legacyCallHandler.roomIdForCall(this.props.call);
|
||||
if (!roomId) {
|
||||
throw new Error("Unable to find room for incoming call");
|
||||
}
|
||||
this.roomId = roomId;
|
||||
|
||||
this.state = {
|
||||
silenced: LegacyCallHandler.instance.isCallSilenced(this.props.call.callId),
|
||||
silenced: context.legacyCallHandler.isCallSilenced(this.props.call.callId),
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount = (): void => {
|
||||
LegacyCallHandler.instance.addListener(
|
||||
this.context.legacyCallHandler.addListener(
|
||||
LegacyCallHandlerEvent.SilencedCallsChanged,
|
||||
this.onSilencedCallsChanged,
|
||||
);
|
||||
};
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
LegacyCallHandler.instance.removeListener(
|
||||
this.context.legacyCallHandler.removeListener(
|
||||
LegacyCallHandlerEvent.SilencedCallsChanged,
|
||||
this.onSilencedCallsChanged,
|
||||
);
|
||||
}
|
||||
|
||||
private onSilencedCallsChanged = (): void => {
|
||||
this.setState({ silenced: LegacyCallHandler.instance.isCallSilenced(this.props.call.callId) });
|
||||
this.setState({ silenced: this.context.legacyCallHandler.isCallSilenced(this.props.call.callId) });
|
||||
};
|
||||
|
||||
private onAnswerClick = (e: ButtonEvent): void => {
|
||||
e.stopPropagation();
|
||||
LegacyCallHandler.instance.answerCall(this.roomId);
|
||||
this.context.legacyCallHandler.answerCall(this.roomId);
|
||||
};
|
||||
|
||||
private onRejectClick = (e: ButtonEvent): void => {
|
||||
e.stopPropagation();
|
||||
LegacyCallHandler.instance.hangupOrReject(this.roomId, true);
|
||||
this.context.legacyCallHandler.hangupOrReject(this.roomId, true);
|
||||
};
|
||||
|
||||
private onSilenceClick = (e: ButtonEvent): void => {
|
||||
e.stopPropagation();
|
||||
const callId = this.props.call.callId;
|
||||
if (this.state.silenced) {
|
||||
LegacyCallHandler.instance.unSilenceCall(callId);
|
||||
this.context.legacyCallHandler.unSilenceCall(callId);
|
||||
} else {
|
||||
LegacyCallHandler.instance.silenceCall(callId);
|
||||
this.context.legacyCallHandler.silenceCall(callId);
|
||||
}
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const room = MatrixClientPeg.safeGet().getRoom(this.roomId);
|
||||
const isVoice = this.props.call.type === CallType.Voice;
|
||||
const callForcedSilent = LegacyCallHandler.instance.isForcedSilent();
|
||||
const callForcedSilent = this.context.legacyCallHandler.isForcedSilent();
|
||||
|
||||
let silenceButtonTooltip = this.state.silenced ? _t("voip|unsilence") : _t("voip|silence");
|
||||
if (callForcedSilent) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import { type ActionPayload } from "../dispatcher/payloads";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { CallStore } from "../stores/CallStore";
|
||||
import { type Call } from "../models/Call";
|
||||
import LegacyCallHandler from "../LegacyCallHandler";
|
||||
|
||||
vi.mock("../Modal.tsx");
|
||||
|
||||
@@ -84,7 +83,9 @@ describe("leaveRoomBehaviour", () => {
|
||||
};
|
||||
|
||||
it("hangs up legacy calls when leaving a room", async () => {
|
||||
const hangupSpy = vi.spyOn(LegacyCallHandler.instance, "hangupOrReject").mockImplementation(() => {});
|
||||
const hangupSpy = vi
|
||||
.spyOn(SDKContextClass.instance.legacyCallHandler, "hangupOrReject")
|
||||
.mockImplementation(() => {});
|
||||
|
||||
viewRoom(room);
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
|
||||
@@ -27,7 +27,6 @@ import { bulkSpaceBehaviour } from "./space";
|
||||
import { SDKContextClass } from "../contexts/SDKContextClass";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { CallStore } from "../stores/CallStore";
|
||||
import LegacyCallHandler from "../LegacyCallHandler";
|
||||
|
||||
export async function leaveRoomBehaviour(
|
||||
matrixClient: MatrixClient,
|
||||
@@ -64,7 +63,7 @@ export async function leaveRoomBehaviour(
|
||||
|
||||
// attempt to hang up legacy based calls
|
||||
try {
|
||||
LegacyCallHandler.instance.hangupOrReject(roomId);
|
||||
SDKContextClass.instance.legacyCallHandler.hangupOrReject(roomId);
|
||||
} catch (e) {
|
||||
logger.warn("Failed to hangup call before leaving room: ", e);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { type CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { getPlatformCallTypeProps, PlatformCallType } from "../../hooks/room/useRoomCall";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { SDKContextClass } from "../../contexts/SDKContextClass.ts";
|
||||
|
||||
/**
|
||||
* Helper to place a call in a room that works with all the legacy modes
|
||||
@@ -34,7 +34,7 @@ export const placeCall = async (
|
||||
PosthogTrackers.trackInteraction(analyticsName);
|
||||
|
||||
if (platformCallType == PlatformCallType.LegacyCall || platformCallType == PlatformCallType.JitsiCall) {
|
||||
await LegacyCallHandler.instance.placeCall(room.roomId, callType);
|
||||
await SDKContextClass.instance.legacyCallHandler.placeCall(room.roomId, callType);
|
||||
} else if (platformCallType == PlatformCallType.ElementCall) {
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
|
||||
@@ -21,7 +21,8 @@ import { MetaSpace } from "../../stores/spaces";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
import type LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
@@ -29,6 +30,11 @@ export interface Props {
|
||||
* The explore button is only displayed in the Home meta space
|
||||
*/
|
||||
activeSpace: string;
|
||||
|
||||
/**
|
||||
* Reference to the LegacyCallHandler instance
|
||||
*/
|
||||
legacyCallHandler: LegacyCallHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,13 +64,13 @@ export class RoomListSearchViewModel
|
||||
};
|
||||
|
||||
public constructor(props: Props) {
|
||||
const supportsPstn = LegacyCallHandler.instance.getSupportsPstnProtocol();
|
||||
const supportsPstn = props.legacyCallHandler.getSupportsPstnProtocol();
|
||||
super(props, RoomListSearchViewModel.computeSnapshot(props.activeSpace, supportsPstn));
|
||||
this.displayDialButton = supportsPstn;
|
||||
|
||||
// Listen for changes in PSTN protocol support
|
||||
this.disposables.trackListener(
|
||||
LegacyCallHandler.instance,
|
||||
props.legacyCallHandler,
|
||||
LegacyCallHandlerEvent.ProtocolSupport,
|
||||
this.onProtocolSupportChange,
|
||||
);
|
||||
@@ -74,7 +80,7 @@ export class RoomListSearchViewModel
|
||||
* Handles changes in protocol support (PSTN).
|
||||
*/
|
||||
private readonly onProtocolSupportChange = (): void => {
|
||||
const supportsPstn = LegacyCallHandler.instance.getSupportsPstnProtocol();
|
||||
const supportsPstn = this.props.legacyCallHandler.getSupportsPstnProtocol();
|
||||
this.displayDialButton = supportsPstn;
|
||||
this.snapshot.set(RoomListSearchViewModel.computeSnapshot(this.props.activeSpace, supportsPstn));
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { UIFeature } from "../../src/settings/UIFeature";
|
||||
import { createAudioContext } from "../../src/audio/compat";
|
||||
import * as ManagedHybrid from "../../src/widgets/ManagedHybrid";
|
||||
import { TestSDKContext } from "./TestSDKContext.ts";
|
||||
|
||||
jest.mock("../../src/Modal");
|
||||
|
||||
@@ -165,7 +166,7 @@ describe("LegacyCallHandler", () => {
|
||||
});
|
||||
};
|
||||
|
||||
callHandler = new LegacyCallHandler();
|
||||
callHandler = new LegacyCallHandler(new TestSDKContext());
|
||||
callHandler.start();
|
||||
|
||||
mocked(getFunctionalMembers).mockReturnValue([FUNCTIONAL_USER]);
|
||||
@@ -238,8 +239,6 @@ describe("LegacyCallHandler", () => {
|
||||
callHandler.stop();
|
||||
// @ts-ignore
|
||||
DMRoomMap.setShared(null);
|
||||
// @ts-ignore
|
||||
window.mxLegacyCallHandler = null;
|
||||
MatrixClientPeg.unset();
|
||||
|
||||
document.body.removeChild(audioElement);
|
||||
@@ -373,7 +372,7 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||
};
|
||||
|
||||
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
|
||||
callHandler = new LegacyCallHandler();
|
||||
callHandler = new LegacyCallHandler(new TestSDKContext());
|
||||
callHandler.start();
|
||||
|
||||
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
|
||||
@@ -423,8 +422,6 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||
callHandler.stop();
|
||||
// @ts-ignore
|
||||
DMRoomMap.setShared(null);
|
||||
// @ts-ignore
|
||||
window.mxLegacyCallHandler = null;
|
||||
MatrixClientPeg.unset();
|
||||
|
||||
document.body.removeChild(audioElement);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { type SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
|
||||
import { type WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
|
||||
import { type WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore";
|
||||
import type WidgetStore from "../../src/stores/WidgetStore";
|
||||
import type LegacyCallHandler from "../../src/LegacyCallHandler.tsx";
|
||||
|
||||
/**
|
||||
* A class which provides the same API as SDKContextClass but adds additional unsafe setters which can
|
||||
@@ -34,6 +35,7 @@ export class TestSDKContext extends SDKContextClass {
|
||||
declare public _PosthogAnalytics?: PosthogAnalytics;
|
||||
declare public _SlidingSyncManager?: SlidingSyncManager;
|
||||
declare public _SpaceStore?: SpaceStoreClass;
|
||||
declare public _LegacyCallHandler?: LegacyCallHandler;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -6,12 +6,13 @@ 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 MatrixClient, type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { type MatrixClient, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { CallState } from "matrix-js-sdk/src/webrtc/call";
|
||||
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import LegacyCallEventGrouper from "../../../../src/components/structures/LegacyCallEventGrouper";
|
||||
import { SDKContextClass } from "../../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
const MY_USER_ID = "@me:here";
|
||||
const THEIR_USER_ID = "@they:here";
|
||||
@@ -145,4 +146,83 @@ describe("LegacyCallEventGrouper", () => {
|
||||
|
||||
expect(grouper.isVoice).toBe(false);
|
||||
});
|
||||
|
||||
it("should be able to answer call", () => {
|
||||
const grouper = new LegacyCallEventGrouper();
|
||||
grouper.add(
|
||||
new MatrixEvent({
|
||||
content: {
|
||||
call_id: "callId",
|
||||
},
|
||||
type: EventType.CallInvite,
|
||||
sender: THEIR_USER_ID,
|
||||
room_id: "!room:server",
|
||||
}),
|
||||
);
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "answerCall");
|
||||
grouper.answerCall();
|
||||
expect(SDKContextClass.instance.legacyCallHandler.answerCall).toHaveBeenCalledWith("!room:server");
|
||||
});
|
||||
|
||||
it("should be able to reject call", () => {
|
||||
const grouper = new LegacyCallEventGrouper();
|
||||
grouper.add(
|
||||
new MatrixEvent({
|
||||
content: {
|
||||
call_id: "callId",
|
||||
},
|
||||
type: EventType.CallInvite,
|
||||
sender: THEIR_USER_ID,
|
||||
room_id: "!room:server",
|
||||
}),
|
||||
);
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "hangupOrReject");
|
||||
grouper.rejectCall();
|
||||
expect(SDKContextClass.instance.legacyCallHandler.hangupOrReject).toHaveBeenCalledWith("!room:server", true);
|
||||
});
|
||||
|
||||
it("should be able to callback call", () => {
|
||||
const grouper = new LegacyCallEventGrouper();
|
||||
grouper.add(
|
||||
new MatrixEvent({
|
||||
content: {
|
||||
call_id: "callId",
|
||||
},
|
||||
type: EventType.CallHangup,
|
||||
sender: THEIR_USER_ID,
|
||||
room_id: "!room:server",
|
||||
}),
|
||||
);
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
|
||||
grouper.callBack();
|
||||
expect(SDKContextClass.instance.legacyCallHandler.placeCall).toHaveBeenCalledWith("!room:server", "video");
|
||||
});
|
||||
|
||||
it("should be able to toggle call silenced", () => {
|
||||
const grouper = new LegacyCallEventGrouper();
|
||||
grouper.add(
|
||||
new MatrixEvent({
|
||||
content: {
|
||||
call_id: "callId",
|
||||
},
|
||||
type: EventType.CallHangup,
|
||||
sender: THEIR_USER_ID,
|
||||
room_id: "!room:server",
|
||||
}),
|
||||
);
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "unSilenceCall");
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "silenceCall");
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "isCallSilenced").mockReturnValue(false);
|
||||
grouper.toggleSilenced();
|
||||
expect(SDKContextClass.instance.legacyCallHandler.silenceCall).toHaveBeenCalledWith("callId");
|
||||
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "isCallSilenced").mockReturnValue(true);
|
||||
grouper.toggleSilenced();
|
||||
expect(SDKContextClass.instance.legacyCallHandler.unSilenceCall).toHaveBeenCalledWith("callId");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,7 +46,6 @@ import {
|
||||
} from "../../../test-utils";
|
||||
import * as leaveRoomUtils from "../../../../src/utils/leave-behaviour";
|
||||
import { OidcClientError } from "../../../../src/utils/oidc/error";
|
||||
import LegacyCallHandler from "../../../../src/LegacyCallHandler";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { type Call } from "../../../../src/models/Call";
|
||||
import { PosthogAnalytics } from "../../../../src/PosthogAnalytics";
|
||||
@@ -1109,7 +1108,7 @@ describe("<MatrixChat />", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// stub out various cleanup functions
|
||||
jest.spyOn(LegacyCallHandler.instance, "hangupAllCalls")
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "hangupAllCalls")
|
||||
.mockClear()
|
||||
.mockImplementation(() => {});
|
||||
jest.spyOn(PosthogAnalytics.instance, "logout").mockImplementation(() => {});
|
||||
@@ -1134,7 +1133,7 @@ describe("<MatrixChat />", () => {
|
||||
it("should hangup all legacy calls", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
expect(LegacyCallHandler.instance.hangupAllCalls).toHaveBeenCalled();
|
||||
expect(SDKContextClass.instance.legacyCallHandler.hangupAllCalls).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should disconnect all calls", async () => {
|
||||
|
||||
@@ -39,7 +39,7 @@ import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMe
|
||||
import { ModuleRunner } from "../../../../../src/modules/ModuleRunner";
|
||||
import { ModuleApi } from "../../../../../src/modules/Api";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
|
||||
import { TestSDKContext } from "../../../TestSDKContext.ts";
|
||||
|
||||
jest.mock("../../../../../src/stores/OwnProfileStore", () => ({
|
||||
OwnProfileStore: {
|
||||
@@ -56,7 +56,7 @@ const realGetValue = SettingsStore.getValue;
|
||||
|
||||
describe("AppTile", () => {
|
||||
let cli: MatrixClient;
|
||||
let sdkContext: SDKContextClass;
|
||||
let sdkContext: TestSDKContext;
|
||||
let r1: Room;
|
||||
let r2: Room;
|
||||
const resizeNotifier = new ResizeNotifier();
|
||||
@@ -118,18 +118,19 @@ describe("AppTile", () => {
|
||||
beforeEach(async () => {
|
||||
// Do not carry across settings from previous tests
|
||||
SettingsStore.reset();
|
||||
sdkContext = new SDKContextClass();
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = cli;
|
||||
// @ts-ignore
|
||||
await WidgetMessagingStore.instance.onReady();
|
||||
|
||||
// Wake up various stores we rely on
|
||||
WidgetLayoutStore.instance.useUnitTestClient(cli);
|
||||
sdkContext.widgetLayoutStore.useUnitTestClient(cli);
|
||||
// @ts-ignore
|
||||
await WidgetLayoutStore.instance.onReady();
|
||||
await sdkContext.widgetLayoutStore.onReady();
|
||||
|
||||
RightPanelStore.instance.useUnitTestClient(cli);
|
||||
sdkContext.rightPanelStore.useUnitTestClient(cli);
|
||||
// @ts-ignore
|
||||
await RightPanelStore.instance.onReady();
|
||||
await sdkContext.rightPanelStore.onReady();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -162,13 +163,12 @@ describe("AppTile", () => {
|
||||
|
||||
// Run initial render with room 1, and also running lifecycle methods
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<RightPanel
|
||||
room={r1}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
<RightPanel
|
||||
room={r1}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
|
||||
/>,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
@@ -232,13 +232,12 @@ describe("AppTile", () => {
|
||||
|
||||
// Run initial render with room 1, and also running lifecycle methods
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<RightPanel
|
||||
room={r1}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
<RightPanel
|
||||
room={r1}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
|
||||
/>,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
@@ -341,6 +340,47 @@ describe("AppTile", () => {
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should hangup Jitsi call when room is left", async () => {
|
||||
const app: IApp = {
|
||||
id: "3",
|
||||
eventId: "jitsi1",
|
||||
roomId: "r2",
|
||||
type: MatrixWidgetType.JitsiMeet,
|
||||
url: "https://jitsi.example.com",
|
||||
name: "Jitsi Conference",
|
||||
creatorUserId: cli.getSafeUserId(),
|
||||
avatar_url: undefined,
|
||||
};
|
||||
|
||||
const { queryByRole, getByText } = render(
|
||||
<AppTile key={app.id} app={app} room={r2} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => queryByRole("progressbar"));
|
||||
|
||||
expect(getByText("Jitsi Conference")).toBeInTheDocument();
|
||||
|
||||
// Switch to room 1
|
||||
dis.dispatch(
|
||||
{
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
},
|
||||
true,
|
||||
);
|
||||
jest.spyOn(ActiveWidgetStore.instance, "getWidgetPersistence").mockReturnValue(true);
|
||||
jest.spyOn(sdkContext.legacyCallHandler, "hangupCallApp");
|
||||
dis.dispatch(
|
||||
{
|
||||
action: Action.AfterLeaveRoom,
|
||||
room_id: "r2",
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(sdkContext.legacyCallHandler.hangupCallApp).toHaveBeenCalledWith(app.roomId);
|
||||
});
|
||||
|
||||
describe("for a pinned widget", () => {
|
||||
let moveToContainerSpy: jest.SpyInstance<void, [room: Room, widget: IWidget, toContainer: Container]>;
|
||||
beforeEach(async () => {
|
||||
@@ -349,9 +389,8 @@ describe("AppTile", () => {
|
||||
|
||||
it("should render", async () => {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
const { asFragment } = renderResult;
|
||||
@@ -361,9 +400,8 @@ describe("AppTile", () => {
|
||||
|
||||
it("should not display the »Popout widget« button", async () => {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
expect(renderResult.queryByLabelText("Popout widget")).not.toBeInTheDocument();
|
||||
@@ -371,20 +409,34 @@ describe("AppTile", () => {
|
||||
|
||||
it("clicking 'minimise' should send the widget to the right", async () => {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
await userEvent.click(renderResult.getByLabelText("Minimise"));
|
||||
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, "right");
|
||||
});
|
||||
|
||||
it("should close right panel timeline when minimising widget", async () => {
|
||||
const renderResult = render(
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
|
||||
jest.spyOn(sdkContext.rightPanelStore, "currentCardForRoom").mockReturnValue({
|
||||
phase: RightPanelPhases.Timeline,
|
||||
});
|
||||
jest.spyOn(sdkContext.rightPanelStore, "popCard");
|
||||
|
||||
await userEvent.click(renderResult.getByLabelText("Minimise"));
|
||||
expect(sdkContext.rightPanelStore.popCard).toHaveBeenCalledWith(r1.roomId);
|
||||
});
|
||||
|
||||
it("clicking 'maximise' should send the widget to the center", async () => {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
await userEvent.click(renderResult.getByLabelText("Maximise"));
|
||||
@@ -400,9 +452,8 @@ describe("AppTile", () => {
|
||||
|
||||
// userId and creatorUserId are different
|
||||
const { container, asFragment, queryByRole } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
expect(container.querySelector(".mx_Spinner")).toBeFalsy();
|
||||
expect(queryByRole("button", { name: "Continue" })).toBeInTheDocument();
|
||||
@@ -418,9 +469,8 @@ describe("AppTile", () => {
|
||||
|
||||
// userId and creatorUserId are different
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
|
||||
@@ -440,9 +490,8 @@ describe("AppTile", () => {
|
||||
|
||||
// userId and creatorUserId are different so legacy path would show "Continue"
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} userId="@user1" creatorUserId="@userAnother" />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
|
||||
// The new API runs async in componentDidMount, so wait for it to take effect
|
||||
@@ -466,9 +515,8 @@ describe("AppTile", () => {
|
||||
|
||||
it("clicking 'un-maximise' should send the widget to the top", async () => {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
await userEvent.click(renderResult.getByLabelText("Un-maximise"));
|
||||
@@ -496,9 +544,8 @@ describe("AppTile", () => {
|
||||
|
||||
it("should display the »Popout widget« button", async () => {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
expect(renderResult.getByLabelText("Popout widget")).toBeInTheDocument();
|
||||
@@ -509,9 +556,8 @@ describe("AppTile", () => {
|
||||
describe("for a persistent app", () => {
|
||||
it("should render", async () => {
|
||||
const { asFragment, queryByRole } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} fullWidth={true} miniMode={true} showMenubar={false} />
|
||||
</MatrixClientContext.Provider>,
|
||||
<AppTile key={app1.id} app={app1} room={r1} fullWidth={true} miniMode={true} showMenubar={false} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
await waitForElementToBeRemoved(() => queryByRole("progressbar"));
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
@@ -46,7 +46,6 @@ import { ScopedRoomContextProvider } from "../../../../../../src/contexts/Scoped
|
||||
import RoomContext, { type RoomContextType } from "../../../../../../src/contexts/RoomContext";
|
||||
import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
import SdkConfig from "../../../../../../src/SdkConfig";
|
||||
import dispatcher from "../../../../../../src/dispatcher/dispatcher";
|
||||
@@ -60,6 +59,7 @@ import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore
|
||||
import { UIFeature } from "../../../../../../src/settings/UIFeature";
|
||||
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
|
||||
import { SDKContextClass } from "../../../../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
jest.mock("../../../../../../src/utils/ShieldUtils");
|
||||
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
|
||||
@@ -365,7 +365,7 @@ describe("RoomHeader", () => {
|
||||
expect(voiceButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
|
||||
const placeCallSpy = jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
|
||||
|
||||
await user.click(voiceButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
|
||||
@@ -376,7 +376,7 @@ describe("RoomHeader", () => {
|
||||
|
||||
it("you can't call if there's already a call", () => {
|
||||
mockRoomMembers(room, 2);
|
||||
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue(
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "getCallForRoom").mockReturnValue(
|
||||
// The JS-SDK does not export the class `MatrixCall` only the type
|
||||
{} as MatrixCall,
|
||||
);
|
||||
@@ -508,7 +508,7 @@ describe("RoomHeader", () => {
|
||||
|
||||
it("disables calling if there's a jitsi call", () => {
|
||||
mockRoomMembers(room, 2);
|
||||
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue(
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "getCallForRoom").mockReturnValue(
|
||||
// The JS-SDK does not export the class `MatrixCall` only the type
|
||||
{} as MatrixCall,
|
||||
);
|
||||
@@ -532,7 +532,7 @@ describe("RoomHeader", () => {
|
||||
expect(voiceButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
|
||||
const placeCallSpy = jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
|
||||
await user.click(voiceButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Voice);
|
||||
|
||||
@@ -554,7 +554,7 @@ describe("RoomHeader", () => {
|
||||
const videoButton = screen.getByRole("button", { name: "Video call" });
|
||||
expect(videoButton).not.toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
const placeCallSpy = jest.spyOn(LegacyCallHandler.instance, "placeCall");
|
||||
const placeCallSpy = jest.spyOn(SDKContextClass.instance.legacyCallHandler, "placeCall");
|
||||
await user.click(videoButton);
|
||||
expect(placeCallSpy).toHaveBeenLastCalledWith(room.roomId, CallType.Video);
|
||||
});
|
||||
|
||||
+10
-1
@@ -15,6 +15,8 @@ import { shouldShowComponent } from "../../../../../../src/customisations/helper
|
||||
import { MetaSpace } from "../../../../../../src/stores/spaces";
|
||||
import { LandmarkNavigation } from "../../../../../../src/accessibility/LandmarkNavigation";
|
||||
import { ReleaseAnnouncementStore } from "../../../../../../src/stores/ReleaseAnnouncementStore";
|
||||
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
|
||||
import { TestSDKContext } from "../../../../TestSDKContext.ts";
|
||||
|
||||
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
|
||||
shouldShowComponent: jest.fn(),
|
||||
@@ -34,8 +36,15 @@ jest.mock("../../../../../../src/accessibility/LandmarkNavigation", () => ({
|
||||
jest.spyOn(ReleaseAnnouncementStore.instance, "getReleaseAnnouncement").mockReturnValue(null);
|
||||
|
||||
describe("<RoomListPanel />", () => {
|
||||
const client = createTestClient();
|
||||
const sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
|
||||
function renderComponent() {
|
||||
return render(<RoomListPanel activeSpace={MetaSpace.Home} />);
|
||||
return render(
|
||||
<RoomListPanel activeSpace={MetaSpace.Home} />,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
+7
-3
@@ -12,7 +12,8 @@ import { mocked } from "jest-mock";
|
||||
import { RoomListSearch } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListSearch";
|
||||
import { MetaSpace } from "../../../../../../src/stores/spaces";
|
||||
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
|
||||
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
||||
import { SDKContextClass } from "../../../../../../src/contexts/SDKContextClass.ts";
|
||||
import { clientAndSDKContextRenderOptions, createTestClient } from "../../../../../test-utils";
|
||||
|
||||
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
|
||||
shouldShowComponent: jest.fn(),
|
||||
@@ -20,13 +21,16 @@ jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
|
||||
|
||||
describe("<RoomListSearch />", () => {
|
||||
function renderComponent(activeSpace = MetaSpace.Home) {
|
||||
return render(<RoomListSearch activeSpace={activeSpace} />);
|
||||
return render(
|
||||
<RoomListSearch activeSpace={activeSpace} />,
|
||||
clientAndSDKContextRenderOptions(createTestClient(), SDKContextClass.instance),
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// By default, we consider shouldShowComponent(UIComponent.ExploreRooms) should return true
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
jest.spyOn(SDKContextClass.instance.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
MockedCall,
|
||||
setupAsyncStoreWithClient,
|
||||
useMockMediaDevices,
|
||||
clientAndSDKContextRenderOptions,
|
||||
} from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { CallView as _CallView } from "../../../../../src/components/views/voip/CallView";
|
||||
@@ -33,6 +34,7 @@ import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMe
|
||||
import { CallStore } from "../../../../../src/stores/CallStore";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { type WidgetMessaging } from "../../../../../src/stores/widgets/WidgetMessaging";
|
||||
import { TestSDKContext } from "../../../TestSDKContext.ts";
|
||||
|
||||
const CallView = wrapInMatrixClientContext(_CallView);
|
||||
|
||||
@@ -41,6 +43,7 @@ describe("CallView", () => {
|
||||
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let sdkContext: TestSDKContext;
|
||||
let room: Room;
|
||||
let alice: RoomMember;
|
||||
let call: MockedCall;
|
||||
@@ -51,6 +54,8 @@ describe("CallView", () => {
|
||||
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.safeGet());
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
DMRoomMap.makeShared(client);
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org", {
|
||||
@@ -88,7 +93,10 @@ describe("CallView", () => {
|
||||
});
|
||||
|
||||
const renderView = async (role: string | undefined = undefined): Promise<void> => {
|
||||
render(<CallView room={room} resizing={false} role={role} onClose={() => {}} />);
|
||||
render(
|
||||
<CallView room={room} resizing={false} role={role} onClose={() => {}} />,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
await act(() => Promise.resolve()); // Let effects settle
|
||||
};
|
||||
|
||||
|
||||
@@ -6,16 +6,21 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { render, fireEvent } from "jest-matrix-react";
|
||||
import { type MatrixCall } from "matrix-js-sdk/src/matrix";
|
||||
import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
|
||||
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
|
||||
|
||||
import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { clientAndSDKContextRenderOptions, createTestClient, stubClient } from "../../../../test-utils";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { TestSDKContext } from "../../../TestSDKContext.ts";
|
||||
|
||||
describe("LegacyCallView", () => {
|
||||
const cli = stubClient();
|
||||
const sdkContext = new TestSDKContext();
|
||||
sdkContext._client = cli;
|
||||
|
||||
it("should exit full screen on unmount", () => {
|
||||
const element = document.createElement("div");
|
||||
// @ts-expect-error
|
||||
@@ -35,7 +40,10 @@ describe("LegacyCallView", () => {
|
||||
isScreensharing: jest.fn().mockReturnValue(false),
|
||||
} as unknown as MatrixCall;
|
||||
|
||||
const { unmount } = render(<LegacyCallView call={call} sidebarShown={false} />);
|
||||
const { unmount } = render(
|
||||
<LegacyCallView call={call} sidebarShown={false} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
expect(document.exitFullscreen).not.toHaveBeenCalled();
|
||||
unmount();
|
||||
expect(document.exitFullscreen).toHaveBeenCalled();
|
||||
@@ -75,7 +83,10 @@ describe("LegacyCallView", () => {
|
||||
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
|
||||
} as unknown as DMRoomMap);
|
||||
|
||||
const { container, rerender } = render(<LegacyCallView call={call} sidebarShown={true} />);
|
||||
const { container, rerender } = render(
|
||||
<LegacyCallView call={call} sidebarShown={true} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
|
||||
rerender(<LegacyCallView call={call} sidebarShown={true} />);
|
||||
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
|
||||
@@ -98,7 +109,102 @@ describe("LegacyCallView", () => {
|
||||
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
|
||||
} as unknown as DMRoomMap);
|
||||
|
||||
const { container } = render(<LegacyCallView call={call} sidebarShown={false} pipMode={true} />);
|
||||
const { container } = render(
|
||||
<LegacyCallView call={call} sidebarShown={false} pipMode={true} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
expect(container.querySelector(".mx_LegacyCallViewButtons_button_sidebar")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should allow user to resume held call", async () => {
|
||||
const client = createTestClient();
|
||||
const sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
|
||||
const call = {
|
||||
roomId: "test-room",
|
||||
on: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
getFeeds: jest.fn().mockReturnValue(
|
||||
[{ local: true }, { local: false }, { local: true, screenshare: true }].map(
|
||||
(x, i) =>
|
||||
({
|
||||
stream: { id: "test-" + i },
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
getMember: jest.fn(),
|
||||
isAudioMuted: jest.fn().mockReturnValue(true),
|
||||
isVideoMuted: jest.fn().mockReturnValue(true),
|
||||
isLocal: jest.fn().mockReturnValue(x.local),
|
||||
purpose: x.screenshare && SDPStreamMetadataPurpose.Screenshare,
|
||||
}) as unknown as CallFeed,
|
||||
),
|
||||
),
|
||||
isLocalOnHold: jest.fn().mockReturnValue(false),
|
||||
isRemoteOnHold: jest.fn().mockReturnValue(true),
|
||||
isMicrophoneMuted: jest.fn().mockReturnValue(true),
|
||||
isLocalVideoMuted: jest.fn().mockReturnValue(true),
|
||||
isScreensharing: jest.fn().mockReturnValue(true),
|
||||
noIncomingFeeds: jest.fn().mockReturnValue(false),
|
||||
opponentSupportsSDPStreamMetadata: jest.fn().mockReturnValue(true),
|
||||
getOpponentMember: jest.fn(),
|
||||
} as unknown as MatrixCall;
|
||||
|
||||
jest.spyOn(sdkContext.legacyCallHandler, "roomIdForCall").mockReturnValue(call.roomId);
|
||||
jest.spyOn(sdkContext.legacyCallHandler, "setActiveCallRoomId");
|
||||
|
||||
const { getByText } = render(
|
||||
<LegacyCallView call={call} sidebarShown />,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
fireEvent.click(getByText("Resume"));
|
||||
|
||||
expect(sdkContext.legacyCallHandler.setActiveCallRoomId).toHaveBeenCalledWith(call.roomId);
|
||||
});
|
||||
|
||||
it("should allow user to hangup call", async () => {
|
||||
const client = createTestClient();
|
||||
const sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
|
||||
const call = {
|
||||
roomId: "test-room",
|
||||
on: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
getFeeds: jest.fn().mockReturnValue(
|
||||
[{ local: true }, { local: false }, { local: true, screenshare: true }].map(
|
||||
(x, i) =>
|
||||
({
|
||||
stream: { id: "test-" + i },
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
getMember: jest.fn(),
|
||||
isAudioMuted: jest.fn().mockReturnValue(true),
|
||||
isVideoMuted: jest.fn().mockReturnValue(true),
|
||||
isLocal: jest.fn().mockReturnValue(x.local),
|
||||
purpose: x.screenshare && SDPStreamMetadataPurpose.Screenshare,
|
||||
}) as unknown as CallFeed,
|
||||
),
|
||||
),
|
||||
isLocalOnHold: jest.fn().mockReturnValue(false),
|
||||
isRemoteOnHold: jest.fn().mockReturnValue(false),
|
||||
isMicrophoneMuted: jest.fn().mockReturnValue(true),
|
||||
isLocalVideoMuted: jest.fn().mockReturnValue(true),
|
||||
isScreensharing: jest.fn().mockReturnValue(true),
|
||||
noIncomingFeeds: jest.fn().mockReturnValue(false),
|
||||
opponentSupportsSDPStreamMetadata: jest.fn().mockReturnValue(true),
|
||||
getOpponentMember: jest.fn(),
|
||||
} as unknown as MatrixCall;
|
||||
|
||||
jest.spyOn(sdkContext.legacyCallHandler, "roomIdForCall").mockReturnValue(call.roomId);
|
||||
jest.spyOn(sdkContext.legacyCallHandler, "hangupOrReject");
|
||||
|
||||
const { getByLabelText } = render(
|
||||
<LegacyCallView call={call} sidebarShown />,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
fireEvent.click(getByLabelText("Hangup"));
|
||||
|
||||
expect(sdkContext.legacyCallHandler.hangupOrReject).toHaveBeenCalledWith(call.roomId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,29 +12,29 @@ import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler
|
||||
|
||||
import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
|
||||
import LegacyCallViewForRoom from "../../../../../src/components/views/voip/LegacyCallViewForRoom";
|
||||
import { mkStubRoom, stubClient } from "../../../../test-utils";
|
||||
import { clientAndSDKContextRenderOptions, mkStubRoom, stubClient } from "../../../../test-utils";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import LegacyCallHandler from "../../../../../src/LegacyCallHandler";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
|
||||
import { TestSDKContext } from "../../../TestSDKContext.ts";
|
||||
|
||||
jest.mock("../../../../../src/components/views/voip/LegacyCallView", () => jest.fn(() => "LegacyCallView"));
|
||||
|
||||
describe("LegacyCallViewForRoom", () => {
|
||||
const LegacyCallViewMock = LegacyCallView as unknown as jest.Mock;
|
||||
let sdkContext: SDKContextClass;
|
||||
let sdkContext: TestSDKContext;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
sdkContext = new SDKContextClass();
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = stubClient();
|
||||
LegacyCallViewMock.mockClear();
|
||||
});
|
||||
|
||||
it("should remember sidebar state, defaulting to shown", async () => {
|
||||
const callHandler = new LegacyCallHandler();
|
||||
const callHandler = new LegacyCallHandler(sdkContext);
|
||||
callHandler.start();
|
||||
jest.spyOn(LegacyCallHandler, "instance", "get").mockImplementation(() => callHandler);
|
||||
sdkContext._LegacyCallHandler = callHandler;
|
||||
|
||||
const call = new MatrixCall({
|
||||
client: MatrixClientPeg.safeGet(),
|
||||
@@ -49,7 +49,10 @@ describe("LegacyCallViewForRoom", () => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
cli.emit(CallEventHandlerEvent.Incoming, call);
|
||||
|
||||
const { rerender } = render(<LegacyCallViewForRoom roomId={call.roomId} />);
|
||||
const { rerender } = render(
|
||||
<LegacyCallViewForRoom roomId={call.roomId} />,
|
||||
clientAndSDKContextRenderOptions(cli, sdkContext),
|
||||
);
|
||||
|
||||
let props = LegacyCallViewMock.mock.lastCall![0];
|
||||
expect(props.sidebarShown).toBeTruthy(); // Sidebar defaults to shown
|
||||
@@ -84,9 +87,7 @@ describe("LegacyCallViewForRoom", () => {
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
};
|
||||
jest.spyOn(LegacyCallHandler, "instance", "get").mockImplementation(
|
||||
() => callHandler as unknown as LegacyCallHandler,
|
||||
);
|
||||
sdkContext._LegacyCallHandler = callHandler as unknown as LegacyCallHandler;
|
||||
|
||||
jest.spyOn(sdkContext.resizeNotifier, "startResizing");
|
||||
jest.spyOn(sdkContext.resizeNotifier, "stopResizing");
|
||||
|
||||
@@ -14,9 +14,9 @@ import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import * as AvatarModule from "../../../../../src/Avatar";
|
||||
import VideoFeed from "../../../../../src/components/views/voip/VideoFeed";
|
||||
import { stubClient, useMockedCalls } from "../../../../test-utils";
|
||||
import type LegacyCallHandler from "../../../../../src/LegacyCallHandler";
|
||||
import { clientAndSDKContextRenderOptions, stubClient, useMockedCalls } from "../../../../test-utils";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { TestSDKContext } from "../../../TestSDKContext.ts";
|
||||
|
||||
const FAKE_AVATAR_URL = "http://fakeurl.dummy/fake.png";
|
||||
|
||||
@@ -24,9 +24,12 @@ describe("VideoFeed", () => {
|
||||
useMockedCalls();
|
||||
|
||||
let client: MatrixClient;
|
||||
let sdkContext: TestSDKContext;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
(AvatarModule as any).avatarUrlForRoom = jest.fn().mockReturnValue(FAKE_AVATAR_URL);
|
||||
|
||||
const dmRoomMap = new DMRoomMap(client);
|
||||
@@ -39,9 +42,7 @@ describe("VideoFeed", () => {
|
||||
});
|
||||
|
||||
it("Displays the room avatar when no video is available", () => {
|
||||
window.mxLegacyCallHandler = {
|
||||
roomIdForCall: jest.fn().mockReturnValue("!this:room.here"),
|
||||
} as unknown as LegacyCallHandler;
|
||||
jest.spyOn(sdkContext.legacyCallHandler, "roomIdForCall").mockReturnValue("!this:room.here");
|
||||
|
||||
const mockCall = {
|
||||
room: new Room("!room:example.com", client, client.getSafeUserId()),
|
||||
@@ -53,7 +54,10 @@ describe("VideoFeed", () => {
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
};
|
||||
render(<VideoFeed feed={feed as unknown as CallFeed} call={mockCall as unknown as MatrixCall} />);
|
||||
render(
|
||||
<VideoFeed feed={feed as unknown as CallFeed} call={mockCall as unknown as MatrixCall} />,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
const avatarImg = screen.getByRole("presentation");
|
||||
expect(avatarImg).toHaveAttribute("src", FAKE_AVATAR_URL);
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
setupAsyncStoreWithClient,
|
||||
resetAsyncStoreWithClient,
|
||||
mkEvent,
|
||||
clientAndSDKContextRenderOptions,
|
||||
} from "../../test-utils";
|
||||
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
@@ -47,9 +48,10 @@ import {
|
||||
getNotificationEventSendTs,
|
||||
IncomingCallToast,
|
||||
} from "../../../src/toasts/IncomingCallToast";
|
||||
import LegacyCallHandler, { AudioID } from "../../../src/LegacyCallHandler";
|
||||
import { AudioID } from "../../../src/LegacyCallHandler";
|
||||
import { CallEvent } from "../../../src/models/Call";
|
||||
import { type WidgetMessaging } from "../../../src/stores/widgets/WidgetMessaging";
|
||||
import { TestSDKContext } from "../TestSDKContext.ts";
|
||||
|
||||
function makeNotificationEvent(room: Room, content: IContent = {}): MatrixEvent {
|
||||
const ts = Date.now();
|
||||
@@ -76,6 +78,7 @@ describe("IncomingCallToast", () => {
|
||||
useMockedCalls();
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let sdkContext: TestSDKContext;
|
||||
let room: Room;
|
||||
|
||||
let alice: RoomMember;
|
||||
@@ -92,6 +95,8 @@ describe("IncomingCallToast", () => {
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.safeGet());
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
|
||||
const audio = document.createElement("audio");
|
||||
audio.id = AudioID.Ring;
|
||||
@@ -146,6 +151,7 @@ describe("IncomingCallToast", () => {
|
||||
notificationEvent={notificationEvent}
|
||||
toastKey={getIncomingCallToastKey(callId, room.roomId)}
|
||||
/>,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
return callId;
|
||||
};
|
||||
@@ -196,8 +202,11 @@ describe("IncomingCallToast", () => {
|
||||
|
||||
it("start ringing on ring notify event", () => {
|
||||
const notificationEvent = makeNotificationEvent(room, { notification_type: "ring" });
|
||||
const playMock = jest.spyOn(LegacyCallHandler.instance, "play");
|
||||
render(<IncomingCallToast notificationEvent={notificationEvent} toastKey="" />);
|
||||
const playMock = jest.spyOn(sdkContext.legacyCallHandler, "play");
|
||||
render(
|
||||
<IncomingCallToast notificationEvent={notificationEvent} toastKey="" />,
|
||||
clientAndSDKContextRenderOptions(client, sdkContext),
|
||||
);
|
||||
expect(playMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -10,10 +10,15 @@ import { LOCAL_NOTIFICATION_SETTINGS_PREFIX, MatrixEvent, Room } from "matrix-js
|
||||
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import React from "react";
|
||||
|
||||
import LegacyCallHandler from "../../../src/LegacyCallHandler";
|
||||
import IncomingLegacyCallToast from "../../../src/toasts/IncomingLegacyCallToast";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsServer, mockClientMethodsUser } from "../../test-utils";
|
||||
import {
|
||||
clientAndSDKContextRenderOptions,
|
||||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsServer,
|
||||
mockClientMethodsUser,
|
||||
} from "../../test-utils";
|
||||
import { SDKContextClass } from "../../../src/contexts/SDKContextClass.ts";
|
||||
|
||||
describe("<IncomingLegacyCallToast />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -41,16 +46,24 @@ describe("<IncomingLegacyCallToast />", () => {
|
||||
jest.clearAllMocks();
|
||||
mockClient.getAccountData.mockReturnValue(undefined);
|
||||
mockClient.getRoom.mockReturnValue(mockRoom);
|
||||
// @ts-ignore
|
||||
SDKContextClass.instance._client = mockClient;
|
||||
});
|
||||
|
||||
it("renders when silence button when call is not silenced", () => {
|
||||
const { getByLabelText } = render(getComponent());
|
||||
const { getByLabelText } = render(
|
||||
getComponent(),
|
||||
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
|
||||
);
|
||||
expect(getByLabelText("Silence call")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders sound on button when call is silenced", () => {
|
||||
LegacyCallHandler.instance.silenceCall(call.callId);
|
||||
const { getByLabelText } = render(getComponent());
|
||||
SDKContextClass.instance.legacyCallHandler.silenceCall(call.callId);
|
||||
const { getByLabelText } = render(
|
||||
getComponent(),
|
||||
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
|
||||
);
|
||||
expect(getByLabelText("Sound on")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -66,7 +79,10 @@ describe("<IncomingLegacyCallToast />", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
const { getByLabelText } = render(getComponent());
|
||||
const { getByLabelText } = render(
|
||||
getComponent(),
|
||||
clientAndSDKContextRenderOptions(mockClient, SDKContextClass.instance),
|
||||
);
|
||||
expect(getByLabelText("Notifications silenced")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import { shouldShowComponent } from "../../../src/customisations/helpers/UICompo
|
||||
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../src/LegacyCallHandler";
|
||||
import { TestSDKContext } from "../../unit-tests/TestSDKContext.ts";
|
||||
|
||||
jest.mock("../../../src/customisations/helpers/UIComponents", () => ({
|
||||
shouldShowComponent: jest.fn(),
|
||||
@@ -23,9 +24,12 @@ jest.mock("../../../src/PosthogTrackers", () => ({
|
||||
}));
|
||||
|
||||
describe("RoomListSearchViewModel", () => {
|
||||
const context = new TestSDKContext();
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
context._LegacyCallHandler = new LegacyCallHandler(context);
|
||||
jest.spyOn(context._LegacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -35,35 +39,50 @@ describe("RoomListSearchViewModel", () => {
|
||||
describe("snapshot", () => {
|
||||
it("should show explore button in Home space when UIComponent.ExploreRooms is enabled", () => {
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().displayExploreButton).toBe(true);
|
||||
});
|
||||
|
||||
it("should hide explore button when not in Home space", () => {
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.VideoRooms });
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.VideoRooms,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().displayExploreButton).toBe(false);
|
||||
});
|
||||
|
||||
it("should hide explore button when UIComponent.ExploreRooms is disabled", () => {
|
||||
mocked(shouldShowComponent).mockReturnValue(false);
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().displayExploreButton).toBe(false);
|
||||
});
|
||||
|
||||
it("should show dial button when PSTN protocol is supported", () => {
|
||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(true);
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().displayDialButton).toBe(true);
|
||||
});
|
||||
|
||||
it("should hide dial button when PSTN protocol is not supported", () => {
|
||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().displayDialButton).toBe(false);
|
||||
});
|
||||
@@ -72,7 +91,10 @@ describe("RoomListSearchViewModel", () => {
|
||||
describe("actions", () => {
|
||||
it("should fire OpenSpotlight action when onSearchClick is called", () => {
|
||||
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
vm.onSearchClick();
|
||||
expect(fireSpy).toHaveBeenCalledWith(Action.OpenSpotlight);
|
||||
@@ -80,7 +102,10 @@ describe("RoomListSearchViewModel", () => {
|
||||
|
||||
it("should fire OpenDialPad action when onDialPadClick is called", () => {
|
||||
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
vm.onDialPadClick();
|
||||
expect(fireSpy).toHaveBeenCalledWith(Action.OpenDialPad);
|
||||
@@ -88,7 +113,10 @@ describe("RoomListSearchViewModel", () => {
|
||||
|
||||
it("should fire ViewRoomDirectory action and track interaction when onExploreClick is called", () => {
|
||||
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
const mockEvent = {} as React.MouseEvent<HTMLButtonElement>;
|
||||
vm.onExploreClick(mockEvent);
|
||||
@@ -98,14 +126,17 @@ describe("RoomListSearchViewModel", () => {
|
||||
});
|
||||
|
||||
it("should update snapshot when PSTN protocol support changes", () => {
|
||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||
const vm = new RoomListSearchViewModel({
|
||||
activeSpace: MetaSpace.Home,
|
||||
legacyCallHandler: context.legacyCallHandler,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().displayDialButton).toBe(false);
|
||||
|
||||
// Simulate PSTN protocol support change
|
||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
|
||||
LegacyCallHandler.instance.emit(LegacyCallHandlerEvent.ProtocolSupport);
|
||||
jest.spyOn(context.legacyCallHandler, "getSupportsPstnProtocol").mockReturnValue(true);
|
||||
context.legacyCallHandler.emit(LegacyCallHandlerEvent.ProtocolSupport);
|
||||
|
||||
expect(vm.getSnapshot().displayDialButton).toBe(true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user