Add ringing for matrixRTC (#11870)
* Add ringing for matrixRTC - since we are using m.mentions we start with the Notifier - an event in the Notifier will result in a IncomingCall toast - incomingCallToast is responsible for ringing (as long as one can see the toast it rings) This should make sure visual and audio signal are in sync. Signed-off-by: Timo K <toger5@hotmail.de> * use typed CallNotifyContent Signed-off-by: Timo K <toger5@hotmail.de> * update tests Signed-off-by: Timo K <toger5@hotmail.de> * change to callId Signed-off-by: Timo K <toger5@hotmail.de> * fix tests Signed-off-by: Timo K <toger5@hotmail.de> * only ring in 1:1 calls notify in rooms < 15 member Signed-off-by: Timo K <toger5@hotmail.de> * call_id fallback Signed-off-by: Timo K <toger5@hotmail.de> * Update src/Notifier.ts Co-authored-by: Robin <robin@robin.town> * review Signed-off-by: Timo K <toger5@hotmail.de> * add tests Signed-off-by: Timo K <toger5@hotmail.de> * more tests Signed-off-by: Timo K <toger5@hotmail.de> * unused import Signed-off-by: Timo K <toger5@hotmail.de> * String -> string Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de> Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
parent
7ca0cd13d0
commit
a26c2d3c78
7 changed files with 230 additions and 50 deletions
|
@ -13,7 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import {
|
||||
ClientEvent,
|
||||
|
@ -29,7 +28,6 @@ import {
|
|||
import { waitFor } from "@testing-library/react";
|
||||
|
||||
import BasePlatform from "../src/BasePlatform";
|
||||
import { ElementCall } from "../src/models/Call";
|
||||
import Notifier from "../src/Notifier";
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
import ToastStore from "../src/stores/ToastStore";
|
||||
|
@ -44,7 +42,7 @@ import {
|
|||
mockClientMethodsUser,
|
||||
mockPlatformPeg,
|
||||
} from "./test-utils";
|
||||
import { IncomingCallToast } from "../src/toasts/IncomingCallToast";
|
||||
import { getIncomingCallToastKey, IncomingCallToast } from "../src/toasts/IncomingCallToast";
|
||||
import { SdkContextClass } from "../src/contexts/SDKContext";
|
||||
import UserActivity from "../src/UserActivity";
|
||||
import Modal from "../src/Modal";
|
||||
|
@ -389,12 +387,17 @@ describe("Notifier", () => {
|
|||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const callOnEvent = (type?: string) => {
|
||||
const emitCallNotifyEvent = (type?: string, roomMention = true) => {
|
||||
const callEvent = mkEvent({
|
||||
type: type ?? ElementCall.CALL_EVENT_TYPE.name,
|
||||
type: type ?? EventType.CallNotify,
|
||||
user: "@alice:foo",
|
||||
room: roomId,
|
||||
content: {},
|
||||
content: {
|
||||
"application": "m.call",
|
||||
"m.mentions": { user_ids: [], room: roomMention },
|
||||
"notify_type": "ring",
|
||||
"call_id": "abc123",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
emitLiveEvent(callEvent);
|
||||
|
@ -410,15 +413,15 @@ describe("Notifier", () => {
|
|||
it("should show toast when group calls are supported", () => {
|
||||
setGroupCallsEnabled(true);
|
||||
|
||||
const callEvent = callOnEvent();
|
||||
const notifyEvent = emitCallNotifyEvent();
|
||||
|
||||
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
key: `call_${callEvent.getStateKey()}`,
|
||||
key: getIncomingCallToastKey(notifyEvent.getContent().call_id ?? "", roomId),
|
||||
priority: 100,
|
||||
component: IncomingCallToast,
|
||||
bodyClassName: "mx_IncomingCallToast",
|
||||
props: { callEvent },
|
||||
props: { notifyEvent },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -426,7 +429,7 @@ describe("Notifier", () => {
|
|||
it("should not show toast when group calls are not supported", () => {
|
||||
setGroupCallsEnabled(false);
|
||||
|
||||
callOnEvent();
|
||||
emitCallNotifyEvent();
|
||||
|
||||
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -434,7 +437,7 @@ describe("Notifier", () => {
|
|||
it("should not show toast when calling with non-group call event", () => {
|
||||
setGroupCallsEnabled(true);
|
||||
|
||||
callOnEvent("event_type");
|
||||
emitCallNotifyEvent("event_type");
|
||||
|
||||
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
|||
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { CryptoApi, MatrixClient, Device, Preset, RoomType } from "matrix-js-sdk/src/matrix";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
|
||||
import { stubClient, setupAsyncStoreWithClient, mockPlatformPeg, getMockClientWithEventEmitter } from "./test-utils";
|
||||
import { MatrixClientPeg } from "../src/MatrixClientPeg";
|
||||
|
@ -74,6 +76,9 @@ describe("createRoom", () => {
|
|||
it("sets up Element video rooms correctly", async () => {
|
||||
const userId = client.getUserId()!;
|
||||
const createCallSpy = jest.spyOn(ElementCall, "create");
|
||||
const callMembershipSpy = jest.spyOn(MatrixRTCSession, "callMembershipsForRoom");
|
||||
callMembershipSpy.mockReturnValue([]);
|
||||
|
||||
const roomId = await createRoom(client, { roomType: RoomType.UnstableCall });
|
||||
|
||||
const userPower = client.createRoom.mock.calls[0][0].power_level_content_override?.users?.[userId];
|
||||
|
|
|
@ -17,7 +17,15 @@ limitations under the License.
|
|||
import EventEmitter from "events";
|
||||
import { mocked } from "jest-mock";
|
||||
import { waitFor } from "@testing-library/react";
|
||||
import { RoomType, Room, RoomEvent, MatrixEvent, RoomStateEvent, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
RoomType,
|
||||
Room,
|
||||
RoomEvent,
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
PendingEventOrdering,
|
||||
UNSTABLE_ELEMENT_FUNCTIONAL_USERS,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
||||
|
@ -982,4 +990,51 @@ describe("ElementCall", () => {
|
|||
call.off(CallEvent.Destroy, onDestroy);
|
||||
});
|
||||
});
|
||||
describe("create call", () => {
|
||||
function setFunctionalMembers(members: string[]) {
|
||||
room.currentState.setStateEvents([
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name,
|
||||
user: "@user:example.com",
|
||||
room: room.roomId,
|
||||
skey: "",
|
||||
content: { service_members: members },
|
||||
}),
|
||||
]);
|
||||
}
|
||||
beforeEach(async () => {
|
||||
setFunctionalMembers(["@user:example.com", "@user2:example.com", "@user4:example.com"]);
|
||||
});
|
||||
it("sends notify event on create in a room with more than two members", async () => {
|
||||
const sendEventSpy = jest.spyOn(room.client, "sendEvent");
|
||||
await ElementCall.create(room);
|
||||
expect(sendEventSpy).toHaveBeenCalledWith("!1:example.org", "org.matrix.msc4075.call.notify", {
|
||||
"application": "m.call",
|
||||
"call_id": "",
|
||||
"m.mentions": { room: true, user_ids: [] },
|
||||
"notify_type": "notify",
|
||||
});
|
||||
});
|
||||
it("sends ring on create in a DM (two participants) room", async () => {
|
||||
setFunctionalMembers(["@user:example.com", "@user2:example.com"]);
|
||||
|
||||
const sendEventSpy = jest.spyOn(room.client, "sendEvent");
|
||||
await ElementCall.create(room);
|
||||
expect(sendEventSpy).toHaveBeenCalledWith("!1:example.org", "org.matrix.msc4075.call.notify", {
|
||||
"application": "m.call",
|
||||
"call_id": "",
|
||||
"m.mentions": { room: true, user_ids: [] },
|
||||
"notify_type": "ring",
|
||||
});
|
||||
});
|
||||
it("don't sent notify event if there are existing room call members", async () => {
|
||||
jest.spyOn(MatrixRTCSession, "callMembershipsForRoom").mockReturnValue([
|
||||
{ application: "m.call", callId: "" } as unknown as CallMembership,
|
||||
]);
|
||||
const sendEventSpy = jest.spyOn(room.client, "sendEvent");
|
||||
await ElementCall.create(room);
|
||||
expect(sendEventSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,6 +19,12 @@ import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/re
|
|||
import { mocked, Mocked } from "jest-mock";
|
||||
import { Room, RoomStateEvent, MatrixEvent, MatrixEventEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientWidgetApi, Widget } from "matrix-widget-api";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ICallNotifyContent } from "matrix-js-sdk/src/matrixrtc/types";
|
||||
|
||||
import type { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
|
@ -37,6 +43,7 @@ import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingSt
|
|||
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||
import ToastStore from "../../src/stores/ToastStore";
|
||||
import { getIncomingCallToastKey, IncomingCallToast } from "../../src/toasts/IncomingCallToast";
|
||||
import { AudioID } from "../../src/LegacyCallHandler";
|
||||
|
||||
describe("IncomingCallEvent", () => {
|
||||
useMockedCalls();
|
||||
|
@ -44,6 +51,7 @@ describe("IncomingCallEvent", () => {
|
|||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let notifyContent: ICallNotifyContent;
|
||||
let alice: RoomMember;
|
||||
let bob: RoomMember;
|
||||
let call: MockedCall;
|
||||
|
@ -59,8 +67,15 @@ describe("IncomingCallEvent", () => {
|
|||
stubClient();
|
||||
client = mocked(MatrixClientPeg.safeGet());
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org");
|
||||
const audio = document.createElement("audio");
|
||||
audio.id = AudioID.Ring;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org");
|
||||
notifyContent = {
|
||||
call_id: "",
|
||||
getRoomId: () => room.roomId,
|
||||
} as unknown as ICallNotifyContent;
|
||||
alice = mkRoomMember(room.roomId, "@alice:example.org");
|
||||
bob = mkRoomMember(room.roomId, "@bob:example.org");
|
||||
|
||||
|
@ -97,7 +112,8 @@ describe("IncomingCallEvent", () => {
|
|||
});
|
||||
|
||||
const renderToast = () => {
|
||||
render(<IncomingCallToast callEvent={call.event} />);
|
||||
call.event.getContent = () => notifyContent as any;
|
||||
render(<IncomingCallToast notifyEvent={call.event} />);
|
||||
};
|
||||
|
||||
it("correctly shows all the information", () => {
|
||||
|
@ -115,6 +131,20 @@ describe("IncomingCallEvent", () => {
|
|||
screen.getByRole("button", { name: "Close" });
|
||||
});
|
||||
|
||||
it("start ringing on ring notify event", () => {
|
||||
call.event.getContent = () =>
|
||||
({
|
||||
...notifyContent,
|
||||
notify_type: "ring",
|
||||
} as any);
|
||||
const playMock = jest.fn();
|
||||
const audio = { play: playMock, paused: true };
|
||||
|
||||
jest.spyOn(document, "getElementById").mockReturnValue(audio as any);
|
||||
render(<IncomingCallToast notifyEvent={call.event} />);
|
||||
expect(playMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("correctly renders toast without a call", () => {
|
||||
call.destroy();
|
||||
renderToast();
|
||||
|
@ -141,7 +171,9 @@ describe("IncomingCallEvent", () => {
|
|||
}),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(getIncomingCallToastKey(call.event.getStateKey()!)),
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
|
||||
),
|
||||
);
|
||||
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
|
@ -155,7 +187,9 @@ describe("IncomingCallEvent", () => {
|
|||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||
await waitFor(() =>
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(getIncomingCallToastKey(call.event.getStateKey()!)),
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
|
||||
),
|
||||
);
|
||||
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
|
@ -171,7 +205,9 @@ describe("IncomingCallEvent", () => {
|
|||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(getIncomingCallToastKey(call.event.getStateKey()!)),
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -182,7 +218,24 @@ describe("IncomingCallEvent", () => {
|
|||
event.emit(MatrixEventEvent.BeforeRedaction, event, {} as unknown as MatrixEvent);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(getIncomingCallToastKey(call.event.getStateKey()!)),
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("closes toast when the matrixRTC session has ended", async () => {
|
||||
renderToast();
|
||||
|
||||
client.matrixRTC.emit(MatrixRTCSessionManagerEvents.SessionEnded, room.roomId, {
|
||||
callId: notifyContent.call_id,
|
||||
room: room,
|
||||
} as unknown as MatrixRTCSession);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||
getIncomingCallToastKey(notifyContent.call_id, room.roomId),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue