Handle starting a call while listen to a broadcast (#9764)
This commit is contained in:
parent
af3715821b
commit
35a187a231
8 changed files with 312 additions and 29 deletions
|
@ -63,6 +63,8 @@ import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogP
|
||||||
import { findDMForUser } from "./utils/dm/findDMForUser";
|
import { findDMForUser } from "./utils/dm/findDMForUser";
|
||||||
import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers";
|
import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers";
|
||||||
import { localNotificationsAreSilenced } from "./utils/notifications";
|
import { localNotificationsAreSilenced } from "./utils/notifications";
|
||||||
|
import { SdkContextClass } from "./contexts/SDKContext";
|
||||||
|
import { showCantStartACallDialog } from "./voice-broadcast/utils/showCantStartACallDialog";
|
||||||
|
|
||||||
export const PROTOCOL_PSTN = "m.protocol.pstn";
|
export const PROTOCOL_PSTN = "m.protocol.pstn";
|
||||||
export const PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn";
|
export const PROTOCOL_PSTN_PREFIXED = "im.vector.protocol.pstn";
|
||||||
|
@ -932,6 +934,15 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async placeCall(roomId: string, type?: CallType, transferee?: MatrixCall): Promise<void> {
|
public async placeCall(roomId: string, type?: CallType, transferee?: MatrixCall): Promise<void> {
|
||||||
|
// Pause current broadcast, if any
|
||||||
|
SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.pause();
|
||||||
|
|
||||||
|
if (SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()) {
|
||||||
|
// Do not start a call, if recording a broadcast
|
||||||
|
showCantStartACallDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We might be using managed hybrid widgets
|
// We might be using managed hybrid widgets
|
||||||
if (isManagedHybridWidgetEnabled()) {
|
if (isManagedHybridWidgetEnabled()) {
|
||||||
await addManagedHybridWidget(roomId);
|
await addManagedHybridWidget(roomId);
|
||||||
|
|
|
@ -648,6 +648,8 @@
|
||||||
"You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.",
|
"You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.",
|
||||||
"You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.",
|
"You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.",
|
||||||
"Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.",
|
"Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.",
|
||||||
|
"Can’t start a call": "Can’t start a call",
|
||||||
|
"You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.",
|
||||||
"You ended a <a>voice broadcast</a>": "You ended a <a>voice broadcast</a>",
|
"You ended a <a>voice broadcast</a>": "You ended a <a>voice broadcast</a>",
|
||||||
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s ended a <a>voice broadcast</a>",
|
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s ended a <a>voice broadcast</a>",
|
||||||
"You ended a voice broadcast": "You ended a voice broadcast",
|
"You ended a voice broadcast": "You ended a voice broadcast",
|
||||||
|
|
|
@ -56,6 +56,7 @@ import {
|
||||||
doMaybeSetCurrentVoiceBroadcastPlayback,
|
doMaybeSetCurrentVoiceBroadcastPlayback,
|
||||||
} from "../voice-broadcast";
|
} from "../voice-broadcast";
|
||||||
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators";
|
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators";
|
||||||
|
import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog";
|
||||||
|
|
||||||
const NUM_JOIN_RETRY = 5;
|
const NUM_JOIN_RETRY = 5;
|
||||||
|
|
||||||
|
@ -180,6 +181,16 @@ export class RoomViewStore extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newState.viewingCall) {
|
||||||
|
// Pause current broadcast, if any
|
||||||
|
this.stores.voiceBroadcastPlaybacksStore.getCurrent()?.pause();
|
||||||
|
|
||||||
|
if (this.stores.voiceBroadcastRecordingsStore.getCurrent()) {
|
||||||
|
showCantStartACallDialog();
|
||||||
|
newState.viewingCall = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const lastRoomId = this.state.roomId;
|
const lastRoomId = this.state.roomId;
|
||||||
this.state = Object.assign(this.state, newState);
|
this.state = Object.assign(this.state, newState);
|
||||||
if (lastRoomId !== this.state.roomId) {
|
if (lastRoomId !== this.state.roomId) {
|
||||||
|
|
36
src/voice-broadcast/utils/showCantStartACallDialog.tsx
Normal file
36
src/voice-broadcast/utils/showCantStartACallDialog.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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 React from "react";
|
||||||
|
|
||||||
|
import InfoDialog from "../../components/views/dialogs/InfoDialog";
|
||||||
|
import { _t } from "../../languageHandler";
|
||||||
|
import Modal from "../../Modal";
|
||||||
|
|
||||||
|
export const showCantStartACallDialog = () => {
|
||||||
|
Modal.createDialog(InfoDialog, {
|
||||||
|
title: _t("Can’t start a call"),
|
||||||
|
description: (
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
"You can’t start a call as you are currently recording a live broadcast. " +
|
||||||
|
"Please end your live broadcast in order to start a call.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
hasCloseButton: true,
|
||||||
|
});
|
||||||
|
};
|
|
@ -19,6 +19,7 @@ import {
|
||||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
PushRuleKind,
|
PushRuleKind,
|
||||||
|
Room,
|
||||||
RuleId,
|
RuleId,
|
||||||
TweakName,
|
TweakName,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
@ -43,6 +44,28 @@ import { Action } from "../src/dispatcher/actions";
|
||||||
import { getFunctionalMembers } from "../src/utils/room/getFunctionalMembers";
|
import { getFunctionalMembers } from "../src/utils/room/getFunctionalMembers";
|
||||||
import SettingsStore from "../src/settings/SettingsStore";
|
import SettingsStore from "../src/settings/SettingsStore";
|
||||||
import { UIFeature } from "../src/settings/UIFeature";
|
import { UIFeature } from "../src/settings/UIFeature";
|
||||||
|
import { VoiceBroadcastInfoState, VoiceBroadcastPlayback, VoiceBroadcastRecording } from "../src/voice-broadcast";
|
||||||
|
import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils";
|
||||||
|
import { SdkContextClass } from "../src/contexts/SDKContext";
|
||||||
|
import Modal from "../src/Modal";
|
||||||
|
|
||||||
|
jest.mock("../src/Modal");
|
||||||
|
|
||||||
|
// mock VoiceRecording because it contains all the audio APIs
|
||||||
|
jest.mock("../src/audio/VoiceRecording", () => ({
|
||||||
|
VoiceRecording: jest.fn().mockReturnValue({
|
||||||
|
disableMaxLength: jest.fn(),
|
||||||
|
liveData: {
|
||||||
|
onUpdate: jest.fn(),
|
||||||
|
},
|
||||||
|
off: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
start: jest.fn(),
|
||||||
|
stop: jest.fn(),
|
||||||
|
destroy: jest.fn(),
|
||||||
|
contentType: "audio/ogg",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock("../src/utils/room/getFunctionalMembers", () => ({
|
jest.mock("../src/utils/room/getFunctionalMembers", () => ({
|
||||||
getFunctionalMembers: jest.fn(),
|
getFunctionalMembers: jest.fn(),
|
||||||
|
@ -71,7 +94,7 @@ const VIRTUAL_ROOM_BOB = "$virtual_bob_room:example.org";
|
||||||
// Bob's phone number
|
// Bob's phone number
|
||||||
const BOB_PHONE_NUMBER = "01818118181";
|
const BOB_PHONE_NUMBER = "01818118181";
|
||||||
|
|
||||||
function mkStubDM(roomId, userId) {
|
function mkStubDM(roomId: string, userId: string) {
|
||||||
const room = mkStubRoom(roomId, "room", MatrixClientPeg.get());
|
const room = mkStubRoom(roomId, "room", MatrixClientPeg.get());
|
||||||
room.getJoinedMembers = jest.fn().mockReturnValue([
|
room.getJoinedMembers = jest.fn().mockReturnValue([
|
||||||
{
|
{
|
||||||
|
@ -134,23 +157,24 @@ function untilCallHandlerEvent(callHandler: LegacyCallHandler, event: LegacyCall
|
||||||
|
|
||||||
describe("LegacyCallHandler", () => {
|
describe("LegacyCallHandler", () => {
|
||||||
let dmRoomMap;
|
let dmRoomMap;
|
||||||
let callHandler;
|
let callHandler: LegacyCallHandler;
|
||||||
let audioElement: HTMLAudioElement;
|
let audioElement: HTMLAudioElement;
|
||||||
let fakeCall;
|
let fakeCall: MatrixCall | null;
|
||||||
|
|
||||||
// what addresses the app has looked up via pstn and native lookup
|
// what addresses the app has looked up via pstn and native lookup
|
||||||
let pstnLookup: string;
|
let pstnLookup: string | null;
|
||||||
let nativeLookup: string;
|
let nativeLookup: string | null;
|
||||||
const deviceId = "my-device";
|
const deviceId = "my-device";
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
stubClient();
|
stubClient();
|
||||||
MatrixClientPeg.get().createCall = (roomId) => {
|
fakeCall = null;
|
||||||
|
MatrixClientPeg.get().createCall = (roomId: string): MatrixCall | null => {
|
||||||
if (fakeCall && fakeCall.roomId !== roomId) {
|
if (fakeCall && fakeCall.roomId !== roomId) {
|
||||||
throw new Error("Only one call is supported!");
|
throw new Error("Only one call is supported!");
|
||||||
}
|
}
|
||||||
fakeCall = new FakeCall(roomId);
|
fakeCall = new FakeCall(roomId) as unknown as MatrixCall;
|
||||||
return fakeCall;
|
return fakeCall as unknown as MatrixCall;
|
||||||
};
|
};
|
||||||
MatrixClientPeg.get().deviceId = deviceId;
|
MatrixClientPeg.get().deviceId = deviceId;
|
||||||
|
|
||||||
|
@ -172,7 +196,7 @@ describe("LegacyCallHandler", () => {
|
||||||
const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);
|
const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);
|
||||||
const virtualBobRoom = mkStubDM(VIRTUAL_ROOM_BOB, VIRTUAL_BOB);
|
const virtualBobRoom = mkStubDM(VIRTUAL_ROOM_BOB, VIRTUAL_BOB);
|
||||||
|
|
||||||
MatrixClientPeg.get().getRoom = (roomId) => {
|
MatrixClientPeg.get().getRoom = (roomId: string): Room | null => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
case NATIVE_ROOM_ALICE:
|
case NATIVE_ROOM_ALICE:
|
||||||
return nativeRoomAlice;
|
return nativeRoomAlice;
|
||||||
|
@ -183,6 +207,8 @@ describe("LegacyCallHandler", () => {
|
||||||
case VIRTUAL_ROOM_BOB:
|
case VIRTUAL_ROOM_BOB:
|
||||||
return virtualBobRoom;
|
return virtualBobRoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
dmRoomMap = {
|
dmRoomMap = {
|
||||||
|
@ -212,13 +238,13 @@ describe("LegacyCallHandler", () => {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
} as DMRoomMap;
|
||||||
DMRoomMap.setShared(dmRoomMap);
|
DMRoomMap.setShared(dmRoomMap);
|
||||||
|
|
||||||
pstnLookup = null;
|
pstnLookup = null;
|
||||||
nativeLookup = null;
|
nativeLookup = null;
|
||||||
|
|
||||||
MatrixClientPeg.get().getThirdpartyUser = (proto, params) => {
|
MatrixClientPeg.get().getThirdpartyUser = (proto: string, params: any) => {
|
||||||
if ([PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED].includes(proto)) {
|
if ([PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED].includes(proto)) {
|
||||||
pstnLookup = params["m.id.phone"];
|
pstnLookup = params["m.id.phone"];
|
||||||
return Promise.resolve([
|
return Promise.resolve([
|
||||||
|
@ -261,6 +287,8 @@ describe("LegacyCallHandler", () => {
|
||||||
}
|
}
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
audioElement = document.createElement("audio");
|
audioElement = document.createElement("audio");
|
||||||
|
@ -270,10 +298,10 @@ describe("LegacyCallHandler", () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
callHandler.stop();
|
callHandler.stop();
|
||||||
|
// @ts-ignore
|
||||||
DMRoomMap.setShared(null);
|
DMRoomMap.setShared(null);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.mxLegacyCallHandler = null;
|
window.mxLegacyCallHandler = null;
|
||||||
fakeCall = null;
|
|
||||||
MatrixClientPeg.unset();
|
MatrixClientPeg.unset();
|
||||||
|
|
||||||
document.body.removeChild(audioElement);
|
document.body.removeChild(audioElement);
|
||||||
|
@ -292,25 +320,27 @@ describe("LegacyCallHandler", () => {
|
||||||
|
|
||||||
// Check that a call was started: its room on the protocol level
|
// Check that a call was started: its room on the protocol level
|
||||||
// should be the virtual room
|
// should be the virtual room
|
||||||
expect(fakeCall.roomId).toEqual(VIRTUAL_ROOM_BOB);
|
expect(fakeCall).not.toBeNull();
|
||||||
|
expect(fakeCall?.roomId).toEqual(VIRTUAL_ROOM_BOB);
|
||||||
|
|
||||||
// but it should appear to the user to be in thw native room for Bob
|
// but it should appear to the user to be in thw native room for Bob
|
||||||
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_BOB);
|
expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_BOB);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should look up the correct user and start a call in the room when a call is transferred", async () => {
|
it("should look up the correct user and start a call in the room when a call is transferred", async () => {
|
||||||
// we can pass a very minimal object as as the call since we pass consultFirst=true:
|
// we can pass a very minimal object as as the call since we pass consultFirst=true:
|
||||||
// we don't need to actually do any transferring
|
// we don't need to actually do any transferring
|
||||||
const mockTransferreeCall = { type: CallType.Voice };
|
const mockTransferreeCall = { type: CallType.Voice } as unknown as MatrixCall;
|
||||||
await callHandler.startTransferToPhoneNumber(mockTransferreeCall, BOB_PHONE_NUMBER, true);
|
await callHandler.startTransferToPhoneNumber(mockTransferreeCall, BOB_PHONE_NUMBER, true);
|
||||||
|
|
||||||
// same checks as above
|
// same checks as above
|
||||||
const viewRoomPayload = await untilDispatch(Action.ViewRoom);
|
const viewRoomPayload = await untilDispatch(Action.ViewRoom);
|
||||||
expect(viewRoomPayload.room_id).toEqual(NATIVE_ROOM_BOB);
|
expect(viewRoomPayload.room_id).toEqual(NATIVE_ROOM_BOB);
|
||||||
|
|
||||||
expect(fakeCall.roomId).toEqual(VIRTUAL_ROOM_BOB);
|
expect(fakeCall).not.toBeNull();
|
||||||
|
expect(fakeCall!.roomId).toEqual(VIRTUAL_ROOM_BOB);
|
||||||
|
|
||||||
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_BOB);
|
expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_BOB);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should move calls between rooms when remote asserted identity changes", async () => {
|
it("should move calls between rooms when remote asserted identity changes", async () => {
|
||||||
|
@ -331,10 +361,11 @@ describe("LegacyCallHandler", () => {
|
||||||
|
|
||||||
// Now emit an asserted identity for Bob: this should be ignored
|
// Now emit an asserted identity for Bob: this should be ignored
|
||||||
// because we haven't set the config option to obey asserted identity
|
// because we haven't set the config option to obey asserted identity
|
||||||
fakeCall.getRemoteAssertedIdentity = jest.fn().mockReturnValue({
|
expect(fakeCall).not.toBeNull();
|
||||||
|
fakeCall!.getRemoteAssertedIdentity = jest.fn().mockReturnValue({
|
||||||
id: NATIVE_BOB,
|
id: NATIVE_BOB,
|
||||||
});
|
});
|
||||||
fakeCall.emit(CallEvent.AssertedIdentityChanged);
|
fakeCall!.emit(CallEvent.AssertedIdentityChanged);
|
||||||
|
|
||||||
// Now set the config option
|
// Now set the config option
|
||||||
SdkConfig.add({
|
SdkConfig.add({
|
||||||
|
@ -344,10 +375,10 @@ describe("LegacyCallHandler", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ...and send another asserted identity event for a different user
|
// ...and send another asserted identity event for a different user
|
||||||
fakeCall.getRemoteAssertedIdentity = jest.fn().mockReturnValue({
|
fakeCall!.getRemoteAssertedIdentity = jest.fn().mockReturnValue({
|
||||||
id: NATIVE_CHARLIE,
|
id: NATIVE_CHARLIE,
|
||||||
});
|
});
|
||||||
fakeCall.emit(CallEvent.AssertedIdentityChanged);
|
fakeCall!.emit(CallEvent.AssertedIdentityChanged);
|
||||||
|
|
||||||
await roomChangePromise;
|
await roomChangePromise;
|
||||||
callHandler.removeAllListeners();
|
callHandler.removeAllListeners();
|
||||||
|
@ -362,21 +393,68 @@ describe("LegacyCallHandler", () => {
|
||||||
expect(callHandler.getCallForRoom(NATIVE_ROOM_BOB)).toBeNull();
|
expect(callHandler.getCallForRoom(NATIVE_ROOM_BOB)).toBeNull();
|
||||||
expect(callHandler.getCallForRoom(NATIVE_ROOM_CHARLIE)).toBe(fakeCall);
|
expect(callHandler.getCallForRoom(NATIVE_ROOM_CHARLIE)).toBe(fakeCall);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when listening to a voice broadcast", () => {
|
||||||
|
let voiceBroadcastPlayback: VoiceBroadcastPlayback;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
voiceBroadcastPlayback = new VoiceBroadcastPlayback(
|
||||||
|
mkVoiceBroadcastInfoStateEvent(
|
||||||
|
"!room:example.com",
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
MatrixClientPeg.get().getSafeUserId(),
|
||||||
|
"d42",
|
||||||
|
),
|
||||||
|
MatrixClientPeg.get(),
|
||||||
|
);
|
||||||
|
SdkContextClass.instance.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
|
||||||
|
jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("and placing a call should pause the broadcast", async () => {
|
||||||
|
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||||
|
await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
|
||||||
|
|
||||||
|
expect(voiceBroadcastPlayback.pause).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when recording a voice broadcast", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(
|
||||||
|
new VoiceBroadcastRecording(
|
||||||
|
mkVoiceBroadcastInfoStateEvent(
|
||||||
|
"!room:example.com",
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
MatrixClientPeg.get().getSafeUserId(),
|
||||||
|
"d42",
|
||||||
|
),
|
||||||
|
MatrixClientPeg.get(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("and placing a call should show the info dialog", async () => {
|
||||||
|
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||||
|
expect(Modal.createDialog).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("LegacyCallHandler without third party protocols", () => {
|
describe("LegacyCallHandler without third party protocols", () => {
|
||||||
let dmRoomMap;
|
let dmRoomMap;
|
||||||
let callHandler: LegacyCallHandler;
|
let callHandler: LegacyCallHandler;
|
||||||
let audioElement: HTMLAudioElement;
|
let audioElement: HTMLAudioElement;
|
||||||
let fakeCall;
|
let fakeCall: MatrixCall | null;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubClient();
|
stubClient();
|
||||||
|
fakeCall = null;
|
||||||
MatrixClientPeg.get().createCall = (roomId) => {
|
MatrixClientPeg.get().createCall = (roomId) => {
|
||||||
if (fakeCall && fakeCall.roomId !== roomId) {
|
if (fakeCall && fakeCall.roomId !== roomId) {
|
||||||
throw new Error("Only one call is supported!");
|
throw new Error("Only one call is supported!");
|
||||||
}
|
}
|
||||||
fakeCall = new FakeCall(roomId);
|
fakeCall = new FakeCall(roomId) as unknown as MatrixCall;
|
||||||
return fakeCall;
|
return fakeCall;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -389,11 +467,13 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||||
|
|
||||||
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
|
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
|
||||||
|
|
||||||
MatrixClientPeg.get().getRoom = (roomId) => {
|
MatrixClientPeg.get().getRoom = (roomId: string): Room | null => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
case NATIVE_ROOM_ALICE:
|
case NATIVE_ROOM_ALICE:
|
||||||
return nativeRoomAlice;
|
return nativeRoomAlice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
dmRoomMap = {
|
dmRoomMap = {
|
||||||
|
@ -411,7 +491,7 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
} as DMRoomMap;
|
||||||
DMRoomMap.setShared(dmRoomMap);
|
DMRoomMap.setShared(dmRoomMap);
|
||||||
|
|
||||||
MatrixClientPeg.get().getThirdpartyUser = (_proto, _params) => {
|
MatrixClientPeg.get().getThirdpartyUser = (_proto, _params) => {
|
||||||
|
@ -421,14 +501,17 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||||
audioElement = document.createElement("audio");
|
audioElement = document.createElement("audio");
|
||||||
audioElement.id = "remoteAudio";
|
audioElement.id = "remoteAudio";
|
||||||
document.body.appendChild(audioElement);
|
document.body.appendChild(audioElement);
|
||||||
|
|
||||||
|
SdkContextClass.instance.voiceBroadcastPlaybacksStore.clearCurrent();
|
||||||
|
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
callHandler.stop();
|
callHandler.stop();
|
||||||
|
// @ts-ignore
|
||||||
DMRoomMap.setShared(null);
|
DMRoomMap.setShared(null);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.mxLegacyCallHandler = null;
|
window.mxLegacyCallHandler = null;
|
||||||
fakeCall = null;
|
|
||||||
MatrixClientPeg.unset();
|
MatrixClientPeg.unset();
|
||||||
|
|
||||||
document.body.removeChild(audioElement);
|
document.body.removeChild(audioElement);
|
||||||
|
@ -442,10 +525,11 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||||
|
|
||||||
// Check that a call was started: its room on the protocol level
|
// Check that a call was started: its room on the protocol level
|
||||||
// should be the virtual room
|
// should be the virtual room
|
||||||
expect(fakeCall.roomId).toEqual(NATIVE_ROOM_ALICE);
|
expect(fakeCall).not.toBeNull();
|
||||||
|
expect(fakeCall!.roomId).toEqual(NATIVE_ROOM_ALICE);
|
||||||
|
|
||||||
// but it should appear to the user to be in thw native room for Bob
|
// but it should appear to the user to be in thw native room for Bob
|
||||||
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_ALICE);
|
expect(callHandler.roomIdForCall(fakeCall!)).toEqual(NATIVE_ROOM_ALICE);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("incoming calls", () => {
|
describe("incoming calls", () => {
|
||||||
|
|
24
test/__snapshots__/LegacyCallHandler-test.ts.snap
Normal file
24
test/__snapshots__/LegacyCallHandler-test.ts.snap
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`LegacyCallHandler when recording a voice broadcast and placing a call should show the info dialog 1`] = `
|
||||||
|
[MockFunction] {
|
||||||
|
"calls": [
|
||||||
|
[
|
||||||
|
[Function],
|
||||||
|
{
|
||||||
|
"description": <p>
|
||||||
|
You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.
|
||||||
|
</p>,
|
||||||
|
"hasCloseButton": true,
|
||||||
|
"title": "Can’t start a call",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
|
@ -28,6 +28,17 @@ import { UPDATE_EVENT } from "../../src/stores/AsyncStore";
|
||||||
import { ActiveRoomChangedPayload } from "../../src/dispatcher/payloads/ActiveRoomChangedPayload";
|
import { ActiveRoomChangedPayload } from "../../src/dispatcher/payloads/ActiveRoomChangedPayload";
|
||||||
import { SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
|
import { SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
|
||||||
import { TestSdkContext } from "../TestSdkContext";
|
import { TestSdkContext } from "../TestSdkContext";
|
||||||
|
import { ViewRoomPayload } from "../../src/dispatcher/payloads/ViewRoomPayload";
|
||||||
|
import {
|
||||||
|
VoiceBroadcastInfoState,
|
||||||
|
VoiceBroadcastPlayback,
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
|
VoiceBroadcastRecording,
|
||||||
|
} from "../../src/voice-broadcast";
|
||||||
|
import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils";
|
||||||
|
import Modal from "../../src/Modal";
|
||||||
|
|
||||||
|
jest.mock("../../src/Modal");
|
||||||
|
|
||||||
// mock out the injected classes
|
// mock out the injected classes
|
||||||
jest.mock("../../src/PosthogAnalytics");
|
jest.mock("../../src/PosthogAnalytics");
|
||||||
|
@ -37,6 +48,22 @@ const MockSlidingSyncManager = <jest.Mock<SlidingSyncManager>>(<unknown>SlidingS
|
||||||
jest.mock("../../src/stores/spaces/SpaceStore");
|
jest.mock("../../src/stores/spaces/SpaceStore");
|
||||||
const MockSpaceStore = <jest.Mock<SpaceStoreClass>>(<unknown>SpaceStoreClass);
|
const MockSpaceStore = <jest.Mock<SpaceStoreClass>>(<unknown>SpaceStoreClass);
|
||||||
|
|
||||||
|
// mock VoiceRecording because it contains all the audio APIs
|
||||||
|
jest.mock("../../src/audio/VoiceRecording", () => ({
|
||||||
|
VoiceRecording: jest.fn().mockReturnValue({
|
||||||
|
disableMaxLength: jest.fn(),
|
||||||
|
liveData: {
|
||||||
|
onUpdate: jest.fn(),
|
||||||
|
},
|
||||||
|
off: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
start: jest.fn(),
|
||||||
|
stop: jest.fn(),
|
||||||
|
destroy: jest.fn(),
|
||||||
|
contentType: "audio/ogg",
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock("../../src/utils/DMRoomMap", () => {
|
jest.mock("../../src/utils/DMRoomMap", () => {
|
||||||
const mock = {
|
const mock = {
|
||||||
getUserIdForRoomId: jest.fn(),
|
getUserIdForRoomId: jest.fn(),
|
||||||
|
@ -60,12 +87,24 @@ describe("RoomViewStore", function () {
|
||||||
getRoom: jest.fn(),
|
getRoom: jest.fn(),
|
||||||
getRoomIdForAlias: jest.fn(),
|
getRoomIdForAlias: jest.fn(),
|
||||||
isGuest: jest.fn(),
|
isGuest: jest.fn(),
|
||||||
|
getSafeUserId: jest.fn(),
|
||||||
});
|
});
|
||||||
const room = new Room(roomId, mockClient, userId);
|
const room = new Room(roomId, mockClient, userId);
|
||||||
|
|
||||||
|
const viewCall = async (): Promise<void> => {
|
||||||
|
dis.dispatch<ViewRoomPayload>({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: roomId,
|
||||||
|
view_call: true,
|
||||||
|
metricsTrigger: undefined,
|
||||||
|
});
|
||||||
|
await untilDispatch(Action.ViewRoom, dis);
|
||||||
|
};
|
||||||
|
|
||||||
let roomViewStore: RoomViewStore;
|
let roomViewStore: RoomViewStore;
|
||||||
let slidingSyncManager: SlidingSyncManager;
|
let slidingSyncManager: SlidingSyncManager;
|
||||||
let dis: MatrixDispatcher;
|
let dis: MatrixDispatcher;
|
||||||
|
let stores: TestSdkContext;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
@ -73,14 +112,16 @@ describe("RoomViewStore", function () {
|
||||||
mockClient.joinRoom.mockResolvedValue(room);
|
mockClient.joinRoom.mockResolvedValue(room);
|
||||||
mockClient.getRoom.mockReturnValue(room);
|
mockClient.getRoom.mockReturnValue(room);
|
||||||
mockClient.isGuest.mockReturnValue(false);
|
mockClient.isGuest.mockReturnValue(false);
|
||||||
|
mockClient.getSafeUserId.mockReturnValue(userId);
|
||||||
|
|
||||||
// Make the RVS to test
|
// Make the RVS to test
|
||||||
dis = new MatrixDispatcher();
|
dis = new MatrixDispatcher();
|
||||||
slidingSyncManager = new MockSlidingSyncManager();
|
slidingSyncManager = new MockSlidingSyncManager();
|
||||||
const stores = new TestSdkContext();
|
stores = new TestSdkContext();
|
||||||
stores._SlidingSyncManager = slidingSyncManager;
|
stores._SlidingSyncManager = slidingSyncManager;
|
||||||
stores._PosthogAnalytics = new MockPosthogAnalytics();
|
stores._PosthogAnalytics = new MockPosthogAnalytics();
|
||||||
stores._SpaceStore = new MockSpaceStore();
|
stores._SpaceStore = new MockSpaceStore();
|
||||||
|
stores._VoiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore();
|
||||||
roomViewStore = new RoomViewStore(dis, stores);
|
roomViewStore = new RoomViewStore(dis, stores);
|
||||||
stores._RoomViewStore = roomViewStore;
|
stores._RoomViewStore = roomViewStore;
|
||||||
});
|
});
|
||||||
|
@ -206,6 +247,56 @@ describe("RoomViewStore", function () {
|
||||||
expect(roomViewStore.getRoomId()).toBeNull();
|
expect(roomViewStore.getRoomId()).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("when viewing a call without a broadcast, it should not raise an error", async () => {
|
||||||
|
await viewCall();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when listening to a voice broadcast", () => {
|
||||||
|
let voiceBroadcastPlayback: VoiceBroadcastPlayback;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
voiceBroadcastPlayback = new VoiceBroadcastPlayback(
|
||||||
|
mkVoiceBroadcastInfoStateEvent(
|
||||||
|
roomId,
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
mockClient.getSafeUserId(),
|
||||||
|
"d42",
|
||||||
|
),
|
||||||
|
mockClient,
|
||||||
|
);
|
||||||
|
stores.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
|
||||||
|
jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("and viewing a call it should pause the current broadcast", async () => {
|
||||||
|
await viewCall();
|
||||||
|
expect(voiceBroadcastPlayback.pause).toHaveBeenCalled();
|
||||||
|
expect(roomViewStore.isViewingCall()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when recording a voice broadcast", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
stores.voiceBroadcastRecordingsStore.setCurrent(
|
||||||
|
new VoiceBroadcastRecording(
|
||||||
|
mkVoiceBroadcastInfoStateEvent(
|
||||||
|
roomId,
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
mockClient.getSafeUserId(),
|
||||||
|
"d42",
|
||||||
|
),
|
||||||
|
mockClient,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("and trying to view a call, it should not actually view it and show the info dialog", async () => {
|
||||||
|
await viewCall();
|
||||||
|
expect(Modal.createDialog).toMatchSnapshot();
|
||||||
|
expect(roomViewStore.isViewingCall()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Sliding Sync", function () {
|
describe("Sliding Sync", function () {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
|
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
|
||||||
|
|
24
test/stores/__snapshots__/RoomViewStore-test.ts.snap
Normal file
24
test/stores/__snapshots__/RoomViewStore-test.ts.snap
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`RoomViewStore when recording a voice broadcast and trying to view a call, it should not actually view it and show the info dialog 1`] = `
|
||||||
|
[MockFunction] {
|
||||||
|
"calls": [
|
||||||
|
[
|
||||||
|
[Function],
|
||||||
|
{
|
||||||
|
"description": <p>
|
||||||
|
You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.
|
||||||
|
</p>,
|
||||||
|
"hasCloseButton": true,
|
||||||
|
"title": "Can’t start a call",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
Loading…
Reference in a new issue