@webex/plugin-meetings 3.0.0-beta.1 → 3.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/browser-detection.js.map +1 -1
- package/dist/common/collection.js.map +1 -1
- package/dist/common/config.js.map +1 -1
- package/dist/common/errors/captcha-error.js +7 -0
- package/dist/common/errors/captcha-error.js.map +1 -1
- package/dist/common/errors/intent-to-join.js +8 -0
- package/dist/common/errors/intent-to-join.js.map +1 -1
- package/dist/common/errors/join-meeting.js +8 -0
- package/dist/common/errors/join-meeting.js.map +1 -1
- package/dist/common/errors/media.js +7 -0
- package/dist/common/errors/media.js.map +1 -1
- package/dist/common/errors/parameter.js.map +1 -1
- package/dist/common/errors/password-error.js +7 -0
- package/dist/common/errors/password-error.js.map +1 -1
- package/dist/common/errors/permission.js +7 -0
- package/dist/common/errors/permission.js.map +1 -1
- package/dist/common/errors/reconnection-in-progress.js.map +1 -1
- package/dist/common/errors/reconnection.js +7 -0
- package/dist/common/errors/reconnection.js.map +1 -1
- package/dist/common/errors/stats.js +7 -0
- package/dist/common/errors/stats.js.map +1 -1
- package/dist/common/errors/webex-errors.js +5 -29
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/errors/webex-meetings-error.js +5 -2
- package/dist/common/errors/webex-meetings-error.js.map +1 -1
- package/dist/common/events/events-scope.js.map +1 -1
- package/dist/common/events/events.js.map +1 -1
- package/dist/common/events/trigger-proxy.js.map +1 -1
- package/dist/common/events/util.js.map +1 -1
- package/dist/common/logs/logger-config.js.map +1 -1
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/common/logs/request.js +3 -0
- package/dist/common/logs/request.js.map +1 -1
- package/dist/common/queue.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +15 -74
- package/dist/constants.js.map +1 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
- package/dist/locus-info/fullState.js.map +1 -1
- package/dist/locus-info/hostUtils.js.map +1 -1
- package/dist/locus-info/index.js +43 -5
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +4 -0
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +12 -3
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +71 -210
- package/dist/media/index.js.map +1 -1
- package/dist/media/internal-media-core-wrapper.js +22 -0
- package/dist/media/internal-media-core-wrapper.js.map +1 -0
- package/dist/media/properties.js +32 -25
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +0 -27
- package/dist/media/util.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/effectsState.js +8 -1
- package/dist/meeting/effectsState.js.map +1 -1
- package/dist/meeting/index.js +1146 -602
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +6 -0
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +83 -24
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/state.js.map +1 -1
- package/dist/meeting/util.js +5 -44
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/collection.js +4 -1
- package/dist/meeting-info/collection.js.map +1 -1
- package/dist/meeting-info/index.js +5 -0
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +14 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/request.js +3 -0
- package/dist/meeting-info/request.js.map +1 -1
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +4 -1
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +136 -25
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/request.js +4 -0
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +24 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +30 -7
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +2 -1
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +1 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +82 -1
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +19 -9
- package/dist/members/request.js.map +1 -1
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +8 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +133 -0
- package/dist/multistream/mediaRequestManager.js.map +1 -0
- package/dist/multistream/multistreamMedia.js +116 -0
- package/dist/multistream/multistreamMedia.js.map +1 -0
- package/dist/multistream/receiveSlot.js +209 -0
- package/dist/multistream/receiveSlot.js.map +1 -0
- package/dist/multistream/receiveSlotManager.js +195 -0
- package/dist/multistream/receiveSlotManager.js.map +1 -0
- package/dist/multistream/remoteMedia.js +289 -0
- package/dist/multistream/remoteMedia.js.map +1 -0
- package/dist/multistream/remoteMediaGroup.js +243 -0
- package/dist/multistream/remoteMediaGroup.js.map +1 -0
- package/dist/multistream/remoteMediaManager.js +1113 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -0
- package/dist/networkQualityMonitor/index.js +10 -2
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +11 -0
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/personal-meeting-room/request.js +2 -1
- package/dist/personal-meeting-room/request.js.map +1 -1
- package/dist/personal-meeting-room/util.js.map +1 -1
- package/dist/reachability/index.js +17 -7
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +1 -0
- package/dist/reachability/request.js.map +1 -1
- package/dist/reactions/reactions.js +111 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/reactions/reactions.type.js +40 -0
- package/dist/reactions/reactions.type.js.map +1 -0
- package/dist/reconnection-manager/index.js +130 -132
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +58 -231
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +7 -116
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +20 -6
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +2 -0
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +58 -37
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +9 -3
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/transcription/index.js +10 -3
- package/dist/transcription/index.js.map +1 -1
- package/package.json +21 -20
- package/src/common/{browser-detection.js → browser-detection.ts} +1 -1
- package/src/common/collection.ts +6 -6
- package/src/common/{config.js → config.ts} +1 -1
- package/src/common/errors/{captcha-error.js → captcha-error.ts} +5 -1
- package/src/common/errors/{intent-to-join.js → intent-to-join.ts} +6 -1
- package/src/common/errors/{join-meeting.js → join-meeting.ts} +6 -1
- package/src/common/errors/{media.js → media.ts} +5 -1
- package/src/common/errors/parameter.ts +3 -2
- package/src/common/errors/{password-error.js → password-error.ts} +5 -1
- package/src/common/errors/{permission.js → permission.ts} +5 -1
- package/src/common/errors/{reconnection-in-progress.js → reconnection-in-progress.ts} +0 -0
- package/src/common/errors/{reconnection.js → reconnection.ts} +5 -1
- package/src/common/errors/{stats.js → stats.ts} +5 -1
- package/src/common/errors/{webex-errors.js → webex-errors.ts} +1 -20
- package/src/common/errors/{webex-meetings-error.js → webex-meetings-error.ts} +3 -1
- package/src/common/events/{events-scope.js → events-scope.ts} +1 -1
- package/src/common/events/{events.js → events.ts} +0 -0
- package/src/common/events/{trigger-proxy.js → trigger-proxy.ts} +1 -2
- package/src/common/events/{util.js → util.ts} +1 -1
- package/src/common/logs/{logger-config.js → logger-config.ts} +1 -2
- package/src/common/logs/{logger-proxy.js → logger-proxy.ts} +1 -1
- package/src/common/logs/{request.js → request.ts} +12 -2
- package/src/common/queue.ts +1 -2
- package/src/{config.js → config.ts} +2 -0
- package/src/constants.ts +139 -179
- package/src/locus-info/{controlsUtils.js → controlsUtils.ts} +4 -4
- package/src/locus-info/{embeddedAppsUtils.js → embeddedAppsUtils.ts} +5 -6
- package/src/locus-info/{fullState.js → fullState.ts} +1 -1
- package/src/locus-info/{hostUtils.js → hostUtils.ts} +5 -5
- package/src/locus-info/{index.js → index.ts} +67 -32
- package/src/locus-info/{infoUtils.js → infoUtils.ts} +7 -4
- package/src/locus-info/{mediaSharesUtils.js → mediaSharesUtils.ts} +13 -13
- package/src/locus-info/{parser.js → parser.ts} +22 -12
- package/src/locus-info/{selfUtils.js → selfUtils.ts} +17 -19
- package/src/media/{index.js → index.ts} +130 -205
- package/src/media/internal-media-core-wrapper.ts +9 -0
- package/src/media/{properties.js → properties.ts} +35 -29
- package/src/media/util.ts +16 -0
- package/src/mediaQualityMetrics/{config.js → config.ts} +1 -1
- package/src/meeting/{effectsState.js → effectsState.ts} +12 -6
- package/src/meeting/{index.js → index.ts} +993 -474
- package/src/meeting/{muteState.js → muteState.ts} +16 -11
- package/src/meeting/{request.js → request.ts} +148 -36
- package/src/meeting/{state.js → state.ts} +6 -6
- package/src/meeting/{util.js → util.ts} +9 -51
- package/src/meeting-info/{collection.js → collection.ts} +4 -1
- package/src/meeting-info/{index.js → index.ts} +10 -6
- package/src/meeting-info/{meeting-info-v2.js → meeting-info-v2.ts} +28 -10
- package/src/meeting-info/{request.js → request.ts} +6 -2
- package/src/meeting-info/{util.js → util.ts} +6 -5
- package/src/meeting-info/{utilv2.js → utilv2.ts} +8 -7
- package/src/meetings/{collection.js → collection.ts} +5 -2
- package/src/meetings/{index.js → index.ts} +118 -22
- package/src/meetings/{request.js → request.ts} +6 -1
- package/src/meetings/{util.js → util.ts} +28 -5
- package/src/member/{index.js → index.ts} +46 -15
- package/src/member/{util.js → util.ts} +17 -16
- package/src/members/{collection.js → collection.ts} +2 -1
- package/src/members/{index.js → index.ts} +94 -26
- package/src/members/{request.js → request.ts} +16 -5
- package/src/members/{util.js → util.ts} +7 -7
- package/src/metrics/{config.js → config.ts} +0 -2
- package/src/metrics/{constants.js → constants.ts} +0 -0
- package/src/metrics/{index.js → index.ts} +27 -8
- package/src/multistream/mediaRequestManager.ts +166 -0
- package/src/multistream/multistreamMedia.ts +92 -0
- package/src/multistream/receiveSlot.ts +141 -0
- package/src/multistream/receiveSlotManager.ts +142 -0
- package/src/multistream/remoteMedia.ts +228 -0
- package/src/multistream/remoteMediaGroup.ts +224 -0
- package/src/multistream/remoteMediaManager.ts +911 -0
- package/src/networkQualityMonitor/{index.js → index.ts} +18 -3
- package/src/personal-meeting-room/{index.js → index.ts} +17 -4
- package/src/personal-meeting-room/{request.js → request.ts} +3 -1
- package/src/personal-meeting-room/{util.js → util.ts} +1 -1
- package/src/reachability/{index.js → index.ts} +28 -17
- package/src/reachability/request.ts +4 -2
- package/src/reactions/reactions.ts +104 -0
- package/src/reactions/reactions.type.ts +36 -0
- package/src/reconnection-manager/{index.js → index.ts} +81 -65
- package/src/roap/index.ts +229 -0
- package/src/roap/{request.js → request.ts} +15 -74
- package/src/roap/turnDiscovery.ts +26 -11
- package/src/statsAnalyzer/{global.js → global.ts} +2 -0
- package/src/statsAnalyzer/{index.js → index.ts} +66 -61
- package/src/statsAnalyzer/{mqaUtil.js → mqaUtil.ts} +6 -1
- package/src/transcription/{index.js → index.ts} +16 -11
- package/test/integration/spec/journey.js +1 -1
- package/test/integration/spec/space-meeting.js +1 -2
- package/test/unit/spec/locus-info/infoUtils.js +17 -1
- package/test/unit/spec/media/index.ts +207 -0
- package/test/unit/spec/media/properties.ts +73 -82
- package/test/unit/spec/meeting/effectsState.js +1 -3
- package/test/unit/spec/meeting/index.js +672 -245
- package/test/unit/spec/meeting/muteState.js +7 -0
- package/test/unit/spec/meeting/request.js +25 -1
- package/test/unit/spec/meeting/utils.js +63 -2
- package/test/unit/spec/meetings/index.js +0 -4
- package/test/unit/spec/members/index.js +164 -2
- package/test/unit/spec/multistream/mediaRequestManager.ts +515 -0
- package/test/unit/spec/multistream/receiveSlot.ts +104 -0
- package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
- package/test/unit/spec/multistream/remoteMedia.ts +225 -0
- package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
- package/test/unit/spec/multistream/remoteMediaManager.ts +1309 -0
- package/test/unit/spec/reconnection-manager/index.js +68 -2
- package/test/unit/spec/roap/index.ts +63 -35
- package/test/unit/spec/stats-analyzer/index.js +19 -22
- package/dist/peer-connection-manager/index.js +0 -794
- package/dist/peer-connection-manager/index.js.map +0 -1
- package/dist/peer-connection-manager/util.js +0 -124
- package/dist/peer-connection-manager/util.js.map +0 -1
- package/dist/roap/collection.js +0 -73
- package/dist/roap/collection.js.map +0 -1
- package/dist/roap/handler.js +0 -337
- package/dist/roap/handler.js.map +0 -1
- package/dist/roap/state.js +0 -164
- package/dist/roap/state.js.map +0 -1
- package/dist/roap/util.js +0 -102
- package/dist/roap/util.js.map +0 -1
- package/src/media/util.js +0 -38
- package/src/peer-connection-manager/index.js +0 -723
- package/src/peer-connection-manager/util.ts +0 -117
- package/src/roap/collection.js +0 -63
- package/src/roap/handler.js +0 -252
- package/src/roap/index.js +0 -380
- package/src/roap/state.js +0 -149
- package/src/roap/util.js +0 -93
- package/test/unit/spec/peerconnection-manager/index.js +0 -188
- package/test/unit/spec/peerconnection-manager/utils.js +0 -48
- package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -389
- package/test/unit/spec/roap/util.js +0 -30
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
_SIP_URI_,
|
|
21
21
|
_MEETING_ID_,
|
|
22
22
|
LOCUSINFO,
|
|
23
|
+
PC_BAIL_TIMEOUT,
|
|
23
24
|
} from '@webex/plugin-meetings/src/constants';
|
|
25
|
+
import {MediaConnection as MC} from '@webex/internal-media-core';
|
|
24
26
|
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
25
27
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
26
28
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
@@ -32,7 +34,6 @@ import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
|
32
34
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
|
33
35
|
import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
|
|
34
36
|
import Media from '@webex/plugin-meetings/src/media/index';
|
|
35
|
-
import PeerConnectionManager from '@webex/plugin-meetings/src/peer-connection-manager';
|
|
36
37
|
import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
|
|
37
38
|
import MediaUtil from '@webex/plugin-meetings/src/media/util';
|
|
38
39
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
@@ -178,7 +179,6 @@ describe('plugin-meetings', () => {
|
|
|
178
179
|
TriggerProxy.trigger = sinon.stub().returns(true);
|
|
179
180
|
Metrics.postEvent = sinon.stub();
|
|
180
181
|
Metrics.initialSetup(null, webex);
|
|
181
|
-
MediaUtil.createPeerConnection = sinon.stub().returns({});
|
|
182
182
|
MediaUtil.createMediaStream = sinon.stub().returns(true);
|
|
183
183
|
|
|
184
184
|
uuid1 = uuid.v4();
|
|
@@ -222,7 +222,6 @@ describe('plugin-meetings', () => {
|
|
|
222
222
|
assert.equal(meeting.userId, uuid1);
|
|
223
223
|
assert.equal(meeting.resource, uuid2);
|
|
224
224
|
assert.equal(meeting.deviceUrl, uuid3);
|
|
225
|
-
assert.equal(meeting.roapSeq, -1);
|
|
226
225
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
227
226
|
assert.instanceOf(meeting.members, Members);
|
|
228
227
|
assert.instanceOf(meeting.roap, Roap);
|
|
@@ -783,6 +782,7 @@ describe('plugin-meetings', () => {
|
|
|
783
782
|
});
|
|
784
783
|
describe('#join', () => {
|
|
785
784
|
let sandbox = null;
|
|
785
|
+
const joinMeetingResult = 'JOIN_MEETINGS_OPTION_RESULT';
|
|
786
786
|
|
|
787
787
|
beforeEach(() => {
|
|
788
788
|
sandbox = sinon.createSandbox();
|
|
@@ -800,10 +800,11 @@ describe('plugin-meetings', () => {
|
|
|
800
800
|
meeting.setCorrelationId = sinon.stub().returns(true);
|
|
801
801
|
meeting.setLocus = sinon.stub().returns(true);
|
|
802
802
|
webex.meetings.registered = true;
|
|
803
|
+
meeting.updateLLMConnection = sinon.stub();
|
|
803
804
|
});
|
|
804
805
|
describe('successful', () => {
|
|
805
806
|
beforeEach(() => {
|
|
806
|
-
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve());
|
|
807
|
+
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
|
807
808
|
});
|
|
808
809
|
|
|
809
810
|
it('should join the meeting and return promise', async () => {
|
|
@@ -812,10 +813,26 @@ describe('plugin-meetings', () => {
|
|
|
812
813
|
assert.calledWithMatch(Metrics.postEvent, {event: eventType.CALL_INITIATED, data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true}});
|
|
813
814
|
|
|
814
815
|
assert.exists(join.then);
|
|
815
|
-
await join;
|
|
816
|
+
const result = await join;
|
|
817
|
+
|
|
816
818
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
817
819
|
assert.calledOnce(meeting.setLocus);
|
|
820
|
+
assert.equal(result, joinMeetingResult);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it('should call updateLLMConnection upon joining if config value is set', async () => {
|
|
824
|
+
meeting.config.enableAutomaticLLM = true;
|
|
825
|
+
await meeting.join();
|
|
826
|
+
|
|
827
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
818
828
|
});
|
|
829
|
+
|
|
830
|
+
it('should not call updateLLMConnection upon joining if config value is not set', async () => {
|
|
831
|
+
await meeting.join();
|
|
832
|
+
|
|
833
|
+
assert.notCalled(meeting.updateLLMConnection);
|
|
834
|
+
});
|
|
835
|
+
|
|
819
836
|
it('should invoke `receiveTranscription()` if receiveTranscription is set to true', async () => {
|
|
820
837
|
meeting.isTranscriptionSupported = sinon.stub().returns(true);
|
|
821
838
|
meeting.receiveTranscription = sinon.stub().returns(Promise.resolve());
|
|
@@ -909,22 +926,25 @@ describe('plugin-meetings', () => {
|
|
|
909
926
|
applyClientStateLocally: sinon.stub().returns(Promise.resolve(true))
|
|
910
927
|
};
|
|
911
928
|
|
|
929
|
+
let fakeMediaConnection;
|
|
930
|
+
|
|
912
931
|
beforeEach(() => {
|
|
932
|
+
fakeMediaConnection = {
|
|
933
|
+
close: sinon.stub(),
|
|
934
|
+
getConnectionState: sinon.stub().returns(MC.ConnectionState.Connected),
|
|
935
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
936
|
+
on: sinon.stub(),
|
|
937
|
+
};
|
|
913
938
|
meeting.mediaProperties.setMediaDirection = sinon.stub().returns(true);
|
|
914
|
-
meeting.mediaProperties.
|
|
939
|
+
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
915
940
|
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
916
941
|
meeting.audio = muteStateStub;
|
|
917
942
|
meeting.video = muteStateStub;
|
|
918
|
-
Media.
|
|
943
|
+
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
919
944
|
meeting.setMercuryListener = sinon.stub().returns(true);
|
|
920
|
-
meeting.
|
|
945
|
+
meeting.setupMediaConnectionListeners = sinon.stub();
|
|
921
946
|
meeting.setMercuryListener = sinon.stub();
|
|
922
|
-
meeting.roap.sendRoapMediaRequest = sinon.stub().returns(new Promise((resolve) => {
|
|
923
|
-
meeting.mediaProperties.peerConnection.connectionState = CONSTANTS.CONNECTION_STATE.CONNECTED;
|
|
924
|
-
resolve();
|
|
925
|
-
}));
|
|
926
947
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: {}, turnDiscoverySkippedReason: undefined});
|
|
927
|
-
PeerConnectionManager.setContentSlides = sinon.stub().returns(true);
|
|
928
948
|
});
|
|
929
949
|
|
|
930
950
|
it('should have #addMedia', () => {
|
|
@@ -932,16 +952,17 @@ describe('plugin-meetings', () => {
|
|
|
932
952
|
});
|
|
933
953
|
|
|
934
954
|
it('should reject promise if meeting is not active', async () => {
|
|
935
|
-
await meeting.addMedia()
|
|
936
|
-
|
|
937
|
-
|
|
955
|
+
const result = await assert.isRejected(meeting.addMedia());
|
|
956
|
+
|
|
957
|
+
assert.instanceOf(result, MeetingNotActiveError);
|
|
938
958
|
});
|
|
939
959
|
|
|
940
960
|
it('should reject promise if user already in left state', async () => {
|
|
941
961
|
meeting.meetingState = 'ACTIVE';
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
962
|
+
meeting.locusInfo.parsedLocus = {self: {state: 'LEFT'}};
|
|
963
|
+
const result = await assert.isRejected(meeting.addMedia());
|
|
964
|
+
|
|
965
|
+
assert.instanceOf(result, UserNotJoinedError);
|
|
945
966
|
});
|
|
946
967
|
|
|
947
968
|
it('should reject promise if user is in lobby ', async () => {
|
|
@@ -960,24 +981,29 @@ describe('plugin-meetings', () => {
|
|
|
960
981
|
|
|
961
982
|
it('should reset the statsAnalyzer to null if addMedia throws an error', async () => {
|
|
962
983
|
meeting.meetingState = 'ACTIVE';
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
968
|
-
assert.calledWith(
|
|
969
|
-
Metrics.sendBehavioralMetric,
|
|
970
|
-
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
971
|
-
correlation_id: meeting.correlationId,
|
|
972
|
-
locus_id: meeting.locusUrl.split('/').pop(),
|
|
973
|
-
reason: err.message,
|
|
974
|
-
stack: err.stack,
|
|
975
|
-
code: err.code,
|
|
976
|
-
turnDiscoverySkippedReason: undefined,
|
|
977
|
-
turnServerUsed: true
|
|
978
|
-
}
|
|
979
|
-
);
|
|
984
|
+
// setup the mock to return an incomplete object - this will cause addMedia to fail
|
|
985
|
+
// because some methods (like on() or initiateOffer()) are missing
|
|
986
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
987
|
+
close: sinon.stub(),
|
|
980
988
|
});
|
|
989
|
+
// set a statsAnalyzer on the meeting so that we can check that it gets reset to null
|
|
990
|
+
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
991
|
+
const error = await assert.isRejected(meeting.addMedia());
|
|
992
|
+
|
|
993
|
+
assert.isNull(meeting.statsAnalyzer);
|
|
994
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
995
|
+
assert.calledWith(
|
|
996
|
+
Metrics.sendBehavioralMetric,
|
|
997
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
998
|
+
correlation_id: meeting.correlationId,
|
|
999
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1000
|
+
reason: error.message,
|
|
1001
|
+
stack: error.stack,
|
|
1002
|
+
code: error.code,
|
|
1003
|
+
turnDiscoverySkippedReason: undefined,
|
|
1004
|
+
turnServerUsed: true
|
|
1005
|
+
}
|
|
1006
|
+
);
|
|
981
1007
|
});
|
|
982
1008
|
|
|
983
1009
|
it('checks metrics called with skipped reason config', async () => {
|
|
@@ -1000,37 +1026,44 @@ describe('plugin-meetings', () => {
|
|
|
1000
1026
|
});
|
|
1001
1027
|
});
|
|
1002
1028
|
|
|
1003
|
-
it('should reset the
|
|
1029
|
+
it('should reset the webrtcMediaConnection to null if addMedia throws an error', async () => {
|
|
1004
1030
|
meeting.meetingState = 'ACTIVE';
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
1010
|
-
assert.calledWith(
|
|
1011
|
-
Metrics.sendBehavioralMetric,
|
|
1012
|
-
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
1013
|
-
correlation_id: meeting.correlationId,
|
|
1014
|
-
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1015
|
-
reason: err.message,
|
|
1016
|
-
stack: err.stack,
|
|
1017
|
-
turnDiscoverySkippedReason: undefined,
|
|
1018
|
-
turnServerUsed: true
|
|
1019
|
-
}
|
|
1020
|
-
);
|
|
1031
|
+
// setup the mock so that a media connection is created, but its initiateOffer() method fails
|
|
1032
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
1033
|
+
initiateOffer: sinon.stub().throws(new Error('fake error')),
|
|
1034
|
+
close: sinon.stub(),
|
|
1021
1035
|
});
|
|
1036
|
+
const result = await assert.isRejected(meeting.addMedia());
|
|
1037
|
+
|
|
1038
|
+
assert.instanceOf(result, Error);
|
|
1039
|
+
assert.isNull(meeting.mediaProperties.webrtcMediaConnection);
|
|
1040
|
+
|
|
1041
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
1042
|
+
assert.calledWith(
|
|
1043
|
+
Metrics.sendBehavioralMetric,
|
|
1044
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, sinon.match({
|
|
1045
|
+
correlation_id: meeting.correlationId,
|
|
1046
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1047
|
+
reason: result.message,
|
|
1048
|
+
turnDiscoverySkippedReason: undefined,
|
|
1049
|
+
turnServerUsed: true
|
|
1050
|
+
})
|
|
1051
|
+
);
|
|
1022
1052
|
});
|
|
1023
1053
|
|
|
1024
1054
|
it('should work the second time addMedia is called in case the first time fails', async () => {
|
|
1025
1055
|
meeting.meetingState = 'ACTIVE';
|
|
1026
1056
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1057
|
+
// setup the mock to cause addMedia() to fail
|
|
1058
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
1059
|
+
initiateOffer: sinon.stub().throws(new Error('fake error')),
|
|
1060
|
+
close: sinon.stub(),
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
await assert.isRejected(meeting.addMedia());
|
|
1064
|
+
|
|
1065
|
+
// reset the mock to a good one, that doesn't fail
|
|
1066
|
+
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
1034
1067
|
|
|
1035
1068
|
try {
|
|
1036
1069
|
await meeting.addMedia({
|
|
@@ -1044,12 +1077,11 @@ describe('plugin-meetings', () => {
|
|
|
1044
1077
|
|
|
1045
1078
|
it('if an error occurs after media request has already been sent, and the user waits until the server kicks them out, a UserNotJoinedError should be thrown when attempting to addMedia again', async () => {
|
|
1046
1079
|
meeting.meetingState = 'ACTIVE';
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
}));
|
|
1080
|
+
// setup the mock to cause addMedia() to fail
|
|
1081
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
1082
|
+
initiateOffer: sinon.stub().throws(new Error('fake error')),
|
|
1083
|
+
close: sinon.stub(),
|
|
1084
|
+
});
|
|
1053
1085
|
await meeting.addMedia().catch((err) => {
|
|
1054
1086
|
assert.exists(err);
|
|
1055
1087
|
});
|
|
@@ -1062,21 +1094,16 @@ describe('plugin-meetings', () => {
|
|
|
1062
1094
|
|
|
1063
1095
|
it('if an error occurs after media request has already been sent, and the user does NOT wait until the server kicks them out, the user should be able to addMedia successfully', async () => {
|
|
1064
1096
|
meeting.meetingState = 'ACTIVE';
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}));
|
|
1097
|
+
// setup the mock to cause addMedia() to fail
|
|
1098
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
1099
|
+
initiateOffer: sinon.stub().throws(new Error('fake error')),
|
|
1100
|
+
close: sinon.stub(),
|
|
1101
|
+
});
|
|
1071
1102
|
await meeting.addMedia().catch((err) => {
|
|
1072
1103
|
assert.exists(err);
|
|
1073
1104
|
});
|
|
1074
1105
|
|
|
1075
|
-
|
|
1076
|
-
meeting.roap.sendRoapMediaRequest = sinon.stub().returns(new Promise((resolve) => {
|
|
1077
|
-
meeting.mediaProperties.peerConnection.connectionState = CONSTANTS.CONNECTION_STATE.CONNECTED;
|
|
1078
|
-
resolve();
|
|
1079
|
-
}));
|
|
1106
|
+
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
1080
1107
|
await meeting.addMedia().catch((err) => {
|
|
1081
1108
|
assert.fail('No error should appear: ', err);
|
|
1082
1109
|
});
|
|
@@ -1086,7 +1113,6 @@ describe('plugin-meetings', () => {
|
|
|
1086
1113
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
1087
1114
|
|
|
1088
1115
|
meeting.meetingState = 'ACTIVE';
|
|
1089
|
-
MediaUtil.createPeerConnection.resetHistory();
|
|
1090
1116
|
const media = meeting.addMedia({
|
|
1091
1117
|
mediaSettings: {}
|
|
1092
1118
|
});
|
|
@@ -1096,12 +1122,10 @@ describe('plugin-meetings', () => {
|
|
|
1096
1122
|
assert.calledOnce(meeting.roap.doTurnDiscovery);
|
|
1097
1123
|
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
|
|
1098
1124
|
assert.calledOnce(meeting.mediaProperties.setMediaDirection);
|
|
1099
|
-
assert.calledOnce(Media.
|
|
1125
|
+
assert.calledOnce(Media.createMediaConnection);
|
|
1126
|
+
assert.calledWith(Media.createMediaConnection, false, meeting.getMediaConnectionDebugId(), sinon.match({turnServerInfo: undefined}));
|
|
1100
1127
|
assert.calledOnce(meeting.setMercuryListener);
|
|
1101
|
-
assert.calledOnce(
|
|
1102
|
-
assert.calledOnce(meeting.roap.sendRoapMediaRequest);
|
|
1103
|
-
assert.calledOnce(MediaUtil.createPeerConnection);
|
|
1104
|
-
assert.calledWith(MediaUtil.createPeerConnection, undefined);
|
|
1128
|
+
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
1105
1129
|
/* statsAnalyzer is initiated inside of addMedia so there isn't
|
|
1106
1130
|
* a good way to mock it without mocking the constructor
|
|
1107
1131
|
*/
|
|
@@ -1113,7 +1137,7 @@ describe('plugin-meetings', () => {
|
|
|
1113
1137
|
const FAKE_TURN_PASSWORD = 'some-password';
|
|
1114
1138
|
|
|
1115
1139
|
meeting.meetingState = 'ACTIVE';
|
|
1116
|
-
|
|
1140
|
+
Media.createMediaConnection.resetHistory();
|
|
1117
1141
|
|
|
1118
1142
|
|
|
1119
1143
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
@@ -1132,31 +1156,39 @@ describe('plugin-meetings', () => {
|
|
|
1132
1156
|
await media;
|
|
1133
1157
|
assert.calledOnce(meeting.roap.doTurnDiscovery);
|
|
1134
1158
|
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
|
|
1135
|
-
assert.calledOnce(
|
|
1136
|
-
assert.calledWith(
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1159
|
+
assert.calledOnce(Media.createMediaConnection);
|
|
1160
|
+
assert.calledWith(Media.createMediaConnection, false, meeting.getMediaConnectionDebugId(), sinon.match({
|
|
1161
|
+
turnServerInfo: {
|
|
1162
|
+
url: FAKE_TURN_URL,
|
|
1163
|
+
username: FAKE_TURN_USER,
|
|
1164
|
+
password: FAKE_TURN_PASSWORD
|
|
1165
|
+
}
|
|
1166
|
+
}));
|
|
1167
|
+
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
1141
1168
|
});
|
|
1142
1169
|
|
|
1143
|
-
it('should attach the media and return
|
|
1144
|
-
meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
1170
|
+
it('should attach the media and return WebExMeetingsErrors when connection does not reach CONNECTED state', async () => {
|
|
1145
1171
|
meeting.meetingState = 'ACTIVE';
|
|
1146
|
-
|
|
1172
|
+
fakeMediaConnection.getConnectionState = sinon.stub().returns(MC.ConnectionState.Connecting);
|
|
1173
|
+
const clock = sinon.useFakeTimers();
|
|
1147
1174
|
const media = meeting.addMedia({
|
|
1148
1175
|
mediaSettings: {}
|
|
1149
1176
|
});
|
|
1150
1177
|
|
|
1178
|
+
await clock.tickAsync(4000 /* meetingState timer, hardcoded inside addMedia */ + PC_BAIL_TIMEOUT /* connection state timer */);
|
|
1179
|
+
await testUtils.flushPromises();
|
|
1180
|
+
|
|
1151
1181
|
assert.exists(media);
|
|
1152
1182
|
await media.catch((err) => {
|
|
1153
1183
|
assert.instanceOf(err, WebExMeetingsErrors);
|
|
1154
1184
|
});
|
|
1185
|
+
|
|
1186
|
+
clock.restore();
|
|
1155
1187
|
});
|
|
1156
1188
|
|
|
1157
|
-
it('should reject if
|
|
1189
|
+
it('should reject if waitForMediaConnectionConnected() rejects', async () => {
|
|
1158
1190
|
meeting.meetingState = 'ACTIVE';
|
|
1159
|
-
meeting.mediaProperties.
|
|
1191
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(new Error('fake error'));
|
|
1160
1192
|
|
|
1161
1193
|
let errorThrown = false;
|
|
1162
1194
|
|
|
@@ -1346,8 +1378,8 @@ describe('plugin-meetings', () => {
|
|
|
1346
1378
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
1347
1379
|
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
1348
1380
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
1349
|
-
meeting.roap.stop = sinon.stub().returns(Promise.resolve());
|
|
1350
1381
|
meeting.logger.error = sinon.stub().returns(true);
|
|
1382
|
+
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
1351
1383
|
|
|
1352
1384
|
// A meeting needs to be joined to leave
|
|
1353
1385
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -1371,7 +1403,6 @@ describe('plugin-meetings', () => {
|
|
|
1371
1403
|
assert.calledOnce(meeting.unsetLocalShareTrack);
|
|
1372
1404
|
assert.calledOnce(meeting.unsetRemoteTracks);
|
|
1373
1405
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
1374
|
-
assert.calledOnce(meeting.roap.stop);
|
|
1375
1406
|
});
|
|
1376
1407
|
describe('after audio/video is defined', () => {
|
|
1377
1408
|
let handleClientRequest;
|
|
@@ -1420,9 +1451,9 @@ describe('plugin-meetings', () => {
|
|
|
1420
1451
|
});
|
|
1421
1452
|
});
|
|
1422
1453
|
});
|
|
1423
|
-
describe('#
|
|
1424
|
-
it('should have #
|
|
1425
|
-
assert.exists(meeting.
|
|
1454
|
+
describe('#requestScreenShareFloor', () => {
|
|
1455
|
+
it('should have #requestScreenShareFloor', () => {
|
|
1456
|
+
assert.exists(meeting.requestScreenShareFloor);
|
|
1426
1457
|
});
|
|
1427
1458
|
beforeEach(() => {
|
|
1428
1459
|
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
@@ -1430,7 +1461,7 @@ describe('plugin-meetings', () => {
|
|
|
1430
1461
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
1431
1462
|
});
|
|
1432
1463
|
it('should send the share', async () => {
|
|
1433
|
-
const share = meeting.
|
|
1464
|
+
const share = meeting.requestScreenShareFloor();
|
|
1434
1465
|
|
|
1435
1466
|
assert.exists(share.then);
|
|
1436
1467
|
await share;
|
|
@@ -1484,61 +1515,6 @@ describe('plugin-meetings', () => {
|
|
|
1484
1515
|
});
|
|
1485
1516
|
});
|
|
1486
1517
|
|
|
1487
|
-
describe('getter: isLocalShareLive', () => {
|
|
1488
|
-
const LIVE = 'live';
|
|
1489
|
-
const ENDED = 'ended';
|
|
1490
|
-
const SENDRECV = 'sendrecv';
|
|
1491
|
-
const RECVONLY = 'reconly';
|
|
1492
|
-
let sandbox;
|
|
1493
|
-
let _direction;
|
|
1494
|
-
let _trackReadyState = ENDED;
|
|
1495
|
-
|
|
1496
|
-
beforeEach(() => {
|
|
1497
|
-
sandbox = sinon.createSandbox();
|
|
1498
|
-
sandbox.stub(meeting.mediaProperties, 'shareTrack').value(true);
|
|
1499
|
-
sandbox.stub(meeting.mediaProperties, 'peerConnection').value({
|
|
1500
|
-
shareTransceiver: {
|
|
1501
|
-
get direction() {
|
|
1502
|
-
return _direction;
|
|
1503
|
-
},
|
|
1504
|
-
sender: {
|
|
1505
|
-
track: {
|
|
1506
|
-
get readyState() {
|
|
1507
|
-
return _trackReadyState;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
});
|
|
1513
|
-
});
|
|
1514
|
-
|
|
1515
|
-
afterEach(() => {
|
|
1516
|
-
sandbox.restore();
|
|
1517
|
-
sandbox = null;
|
|
1518
|
-
});
|
|
1519
|
-
|
|
1520
|
-
it('sets isLocalShareLive to true when sharing screen', () => {
|
|
1521
|
-
_direction = SENDRECV;
|
|
1522
|
-
_trackReadyState = LIVE;
|
|
1523
|
-
|
|
1524
|
-
assert.isTrue(meeting.isLocalShareLive);
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
it('sets isLocalShareLive to false when not sharing screen', () => {
|
|
1528
|
-
_direction = RECVONLY;
|
|
1529
|
-
_trackReadyState = ENDED;
|
|
1530
|
-
|
|
1531
|
-
assert.isFalse(meeting.isLocalShareLive);
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
it('sets isLocalShareLive to false when track is live but share direction is recv only', () => {
|
|
1535
|
-
_direction = RECVONLY;
|
|
1536
|
-
_trackReadyState = LIVE;
|
|
1537
|
-
|
|
1538
|
-
assert.isFalse(meeting.isLocalShareLive);
|
|
1539
|
-
});
|
|
1540
|
-
});
|
|
1541
|
-
|
|
1542
1518
|
describe('stops share immediately', () => {
|
|
1543
1519
|
let sandbox;
|
|
1544
1520
|
|
|
@@ -1556,7 +1532,6 @@ describe('plugin-meetings', () => {
|
|
|
1556
1532
|
const receiveShare = false;
|
|
1557
1533
|
const stream = 'stream';
|
|
1558
1534
|
|
|
1559
|
-
sandbox.stub(meeting.mediaProperties, 'peerConnection').value({shareTransceiver: true});
|
|
1560
1535
|
sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: true});
|
|
1561
1536
|
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
|
|
1562
1537
|
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
@@ -1620,42 +1595,6 @@ describe('plugin-meetings', () => {
|
|
|
1620
1595
|
sandbox = null;
|
|
1621
1596
|
});
|
|
1622
1597
|
|
|
1623
|
-
it('calls handleShareTrackEnded if sharing is out of sync', async () => {
|
|
1624
|
-
const sendShare = true;
|
|
1625
|
-
const receiveShare = false;
|
|
1626
|
-
const stream = 'stream';
|
|
1627
|
-
const SENDRECV = 'sendrecv';
|
|
1628
|
-
const delay = 1e3;
|
|
1629
|
-
|
|
1630
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
|
|
1631
|
-
MeetingUtil.updateTransceiver = sinon.stub().returns(Promise.resolve(true));
|
|
1632
|
-
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
1633
|
-
sandbox.stub(MeetingUtil, 'getTrack').returns({videoTrack: null});
|
|
1634
|
-
sandbox.stub(meeting, 'setLocalShareTrack');
|
|
1635
|
-
sandbox.stub(meeting, 'unsetLocalShareTrack');
|
|
1636
|
-
sandbox.stub(meeting, 'checkForStopShare').returns(false);
|
|
1637
|
-
|
|
1638
|
-
sandbox.stub(meeting, 'isLocalShareLive').value(false);
|
|
1639
|
-
sandbox.stub(meeting, 'handleShareTrackEnded');
|
|
1640
|
-
sandbox.stub(meeting.mediaProperties, 'peerConnection').value({
|
|
1641
|
-
shareTransceiver: {
|
|
1642
|
-
direction: SENDRECV
|
|
1643
|
-
}
|
|
1644
|
-
});
|
|
1645
|
-
sandbox.useFakeTimers();
|
|
1646
|
-
|
|
1647
|
-
await meeting.updateShare({
|
|
1648
|
-
sendShare,
|
|
1649
|
-
receiveShare,
|
|
1650
|
-
stream,
|
|
1651
|
-
skipSignalingCheck: true
|
|
1652
|
-
});
|
|
1653
|
-
// simulate the setTimeout in code
|
|
1654
|
-
sandbox.clock.tick(delay);
|
|
1655
|
-
|
|
1656
|
-
assert.calledOnce(meeting.handleShareTrackEnded);
|
|
1657
|
-
});
|
|
1658
|
-
|
|
1659
1598
|
it('handleShareTrackEnded triggers an event', () => {
|
|
1660
1599
|
const stream = 'stream';
|
|
1661
1600
|
const {EVENT_TYPES} = CONSTANTS;
|
|
@@ -1917,6 +1856,11 @@ describe('plugin-meetings', () => {
|
|
|
1917
1856
|
});
|
|
1918
1857
|
|
|
1919
1858
|
describe('#updateAudio', () => {
|
|
1859
|
+
const FAKE_AUDIO_TRACK = {
|
|
1860
|
+
id: 'fake audio track',
|
|
1861
|
+
getSettings: sinon.stub().returns({}),
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1920
1864
|
describe('when canUpdateMedia is true', () => {
|
|
1921
1865
|
beforeEach(() => {
|
|
1922
1866
|
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
@@ -1924,20 +1868,33 @@ describe('plugin-meetings', () => {
|
|
|
1924
1868
|
describe('when options are valid', () => {
|
|
1925
1869
|
beforeEach(() => {
|
|
1926
1870
|
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
1871
|
+
meeting.mediaProperties.mediaDirection = {
|
|
1872
|
+
sendAudio: false,
|
|
1873
|
+
sendVideo: true,
|
|
1874
|
+
sendShare: false,
|
|
1875
|
+
receiveAudio: false,
|
|
1876
|
+
receiveVideo: true,
|
|
1877
|
+
receiveShare: true,
|
|
1878
|
+
};
|
|
1879
|
+
meeting.mediaProperties.webrtcMediaConnection = {updateSendReceiveOptions: sinon.stub()};
|
|
1880
|
+
sinon.stub(MeetingUtil, 'getTrack').returns({audioTrack: FAKE_AUDIO_TRACK});
|
|
1927
1881
|
});
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1882
|
+
it('calls this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions', () => meeting.updateAudio({
|
|
1883
|
+
sendAudio: true,
|
|
1884
|
+
receiveAudio: true,
|
|
1885
|
+
stream: {id: 'fake stream'}
|
|
1886
|
+
}).then(() => {
|
|
1887
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
|
|
1888
|
+
assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions, {
|
|
1889
|
+
send: {audio: FAKE_AUDIO_TRACK},
|
|
1890
|
+
receive: {
|
|
1891
|
+
audio: true, video: true, screenShareVideo: true, remoteQualityLevel: 'HIGH'
|
|
1892
|
+
}
|
|
1932
1893
|
});
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
}).then(() => {
|
|
1938
|
-
assert.calledOnce(MeetingUtil.updateTransceiver);
|
|
1939
|
-
}));
|
|
1940
|
-
});
|
|
1894
|
+
}));
|
|
1895
|
+
});
|
|
1896
|
+
afterEach(() => {
|
|
1897
|
+
sinon.restore();
|
|
1941
1898
|
});
|
|
1942
1899
|
});
|
|
1943
1900
|
});
|
|
@@ -1988,10 +1945,36 @@ describe('plugin-meetings', () => {
|
|
|
1988
1945
|
let sandbox;
|
|
1989
1946
|
const mockLocalStream = {id: 'mock local stream'};
|
|
1990
1947
|
const mockLocalShare = {id: 'mock local share stream'};
|
|
1948
|
+
const FAKE_TRACKS = {
|
|
1949
|
+
audio: {
|
|
1950
|
+
id: 'fake audio track',
|
|
1951
|
+
getSettings: sinon.stub().returns({}),
|
|
1952
|
+
},
|
|
1953
|
+
video: {
|
|
1954
|
+
id: 'fake video track',
|
|
1955
|
+
getSettings: sinon.stub().returns({}),
|
|
1956
|
+
},
|
|
1957
|
+
screenshareVideo: {
|
|
1958
|
+
id: 'fake share track',
|
|
1959
|
+
getSettings: sinon.stub().returns({}),
|
|
1960
|
+
},
|
|
1961
|
+
|
|
1962
|
+
};
|
|
1991
1963
|
|
|
1992
1964
|
beforeEach(() => {
|
|
1993
1965
|
sandbox = sinon.createSandbox();
|
|
1994
1966
|
meeting.mediaProperties.mediaDirection = {sendShare: true};
|
|
1967
|
+
// setup the stub to return the right tracks
|
|
1968
|
+
sandbox.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
|
|
1969
|
+
if (stream === mockLocalStream) {
|
|
1970
|
+
return {audioTrack: FAKE_TRACKS.audio, videoTrack: FAKE_TRACKS.video};
|
|
1971
|
+
}
|
|
1972
|
+
if (stream === mockLocalShare) {
|
|
1973
|
+
return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
return {audioTrack: null, videoTrack: null};
|
|
1977
|
+
});
|
|
1995
1978
|
});
|
|
1996
1979
|
|
|
1997
1980
|
afterEach(() => {
|
|
@@ -2011,7 +1994,9 @@ describe('plugin-meetings', () => {
|
|
|
2011
1994
|
};
|
|
2012
1995
|
|
|
2013
1996
|
sandbox.stub(meeting, 'canUpdateMedia').returns(false);
|
|
2014
|
-
|
|
1997
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
1998
|
+
updateSendReceiveOptions: sinon.stub().resolves({})
|
|
1999
|
+
};
|
|
2015
2000
|
|
|
2016
2001
|
let myPromiseResolved = false;
|
|
2017
2002
|
|
|
@@ -2025,18 +2010,30 @@ describe('plugin-meetings', () => {
|
|
|
2025
2010
|
});
|
|
2026
2011
|
|
|
2027
2012
|
// verify that nothing was done
|
|
2028
|
-
assert.notCalled(
|
|
2013
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
|
|
2029
2014
|
|
|
2030
2015
|
// now trigger processing of the queue
|
|
2031
2016
|
meeting.canUpdateMedia.restore();
|
|
2032
2017
|
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
2033
|
-
meeting.updateMedia = sinon.stub().returns(Promise.resolve());
|
|
2034
2018
|
|
|
2035
2019
|
meeting.processNextQueuedMediaUpdate();
|
|
2036
2020
|
await testUtils.flushPromises();
|
|
2037
2021
|
|
|
2038
|
-
// and check that
|
|
2039
|
-
assert.
|
|
2022
|
+
// and check that updateSendReceiveOptions is called with the original args
|
|
2023
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
|
|
2024
|
+
assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions, {
|
|
2025
|
+
send: {
|
|
2026
|
+
audio: FAKE_TRACKS.audio,
|
|
2027
|
+
video: FAKE_TRACKS.video,
|
|
2028
|
+
screenShareVideo: FAKE_TRACKS.screenshareVideo,
|
|
2029
|
+
},
|
|
2030
|
+
receive: {
|
|
2031
|
+
audio: true,
|
|
2032
|
+
video: true,
|
|
2033
|
+
screenShareVideo: true,
|
|
2034
|
+
remoteQualityLevel: 'HIGH'
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2040
2037
|
assert.isTrue(myPromiseResolved);
|
|
2041
2038
|
});
|
|
2042
2039
|
});
|
|
@@ -2282,7 +2279,7 @@ describe('plugin-meetings', () => {
|
|
|
2282
2279
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2283
2280
|
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
2284
2281
|
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
2285
|
-
|
|
2282
|
+
meeting.updateVideo = sinon.stub().resolves();
|
|
2286
2283
|
sinon.stub(MeetingUtil, 'getTrack').returns({videoTrack: fakeTrack});
|
|
2287
2284
|
});
|
|
2288
2285
|
|
|
@@ -2842,8 +2839,8 @@ describe('plugin-meetings', () => {
|
|
|
2842
2839
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2843
2840
|
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
2844
2841
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
2845
|
-
meeting.roap.stop = sinon.stub().returns(Promise.resolve());
|
|
2846
2842
|
meeting.logger.error = sinon.stub().returns(true);
|
|
2843
|
+
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
2847
2844
|
|
|
2848
2845
|
// A meeting needs to be joined to end
|
|
2849
2846
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -2867,7 +2864,6 @@ describe('plugin-meetings', () => {
|
|
|
2867
2864
|
assert.calledOnce(meeting?.unsetLocalShareTrack);
|
|
2868
2865
|
assert.calledOnce(meeting?.unsetRemoteTracks);
|
|
2869
2866
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
2870
|
-
assert.calledOnce(meeting?.roap?.stop);
|
|
2871
2867
|
});
|
|
2872
2868
|
});
|
|
2873
2869
|
|
|
@@ -3295,31 +3291,254 @@ describe('plugin-meetings', () => {
|
|
|
3295
3291
|
assert.calledOnce(meeting.stopShare);
|
|
3296
3292
|
});
|
|
3297
3293
|
});
|
|
3298
|
-
describe('#
|
|
3294
|
+
describe('#setupMediaConnectionListeners', () => {
|
|
3295
|
+
let eventListeners;
|
|
3296
|
+
|
|
3299
3297
|
beforeEach(() => {
|
|
3298
|
+
eventListeners = {};
|
|
3300
3299
|
meeting.statsAnalyzer = {startAnalyzer: sinon.stub()};
|
|
3300
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
3301
|
+
// mock the on() method and store all the listeners
|
|
3302
|
+
on: sinon.stub().callsFake((event, listener) => {
|
|
3303
|
+
eventListeners[event] = listener;
|
|
3304
|
+
})
|
|
3305
|
+
};
|
|
3301
3306
|
});
|
|
3302
|
-
it('should trigger a media:ready event when remote stream track ontrack is fired', () => {
|
|
3303
|
-
const pc = {};
|
|
3304
3307
|
|
|
3305
|
-
|
|
3306
|
-
|
|
3308
|
+
it('should register for all the correct RoapMediaConnection events', () => {
|
|
3309
|
+
meeting.setupMediaConnectionListeners();
|
|
3310
|
+
assert.isFunction(eventListeners[MC.Event.ROAP_STARTED]);
|
|
3311
|
+
assert.isFunction(eventListeners[MC.Event.ROAP_DONE]);
|
|
3312
|
+
assert.isFunction(eventListeners[MC.Event.ROAP_FAILURE]);
|
|
3313
|
+
assert.isFunction(eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]);
|
|
3314
|
+
assert.isFunction(eventListeners[MC.Event.REMOTE_TRACK_ADDED]);
|
|
3315
|
+
assert.isFunction(eventListeners[MC.Event.CONNECTION_STATE_CHANGED]);
|
|
3316
|
+
});
|
|
3317
|
+
|
|
3318
|
+
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
3319
|
+
meeting.setupMediaConnectionListeners();
|
|
3320
|
+
eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.AUDIO});
|
|
3307
3321
|
assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
|
|
3308
3322
|
assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'remoteAudio', stream: true});
|
|
3309
3323
|
|
|
3310
|
-
|
|
3324
|
+
eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.VIDEO});
|
|
3311
3325
|
assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
|
|
3312
3326
|
assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {type: 'remoteVideo', stream: true});
|
|
3313
3327
|
|
|
3314
|
-
|
|
3328
|
+
eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.SCREENSHARE_VIDEO});
|
|
3315
3329
|
assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
|
|
3316
3330
|
assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {type: 'remoteShare', stream: true});
|
|
3331
|
+
});
|
|
3317
3332
|
|
|
3333
|
+
describe('should send correct metrics for ROAP_FAILURE event', () => {
|
|
3334
|
+
const fakeErrorMessage = 'test error';
|
|
3335
|
+
const fakeRootCauseName = 'root cause name';
|
|
3336
|
+
const fakeErrorName = 'test error name';
|
|
3318
3337
|
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3338
|
+
beforeEach(() => {
|
|
3339
|
+
meeting.setupMediaConnectionListeners();
|
|
3340
|
+
});
|
|
3341
|
+
|
|
3342
|
+
const checkMetricSent = (event) => {
|
|
3343
|
+
assert.calledOnce(Metrics.postEvent);
|
|
3344
|
+
assert.calledWithMatch(Metrics.postEvent, {event, meetingId: meeting.id, data: {canProceed: false}});
|
|
3345
|
+
};
|
|
3346
|
+
|
|
3347
|
+
const checkBehavioralMetricSent = (metricName, expectedCode, expectedReason, expectedMetadataType) => {
|
|
3348
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3349
|
+
assert.calledWith(
|
|
3350
|
+
Metrics.sendBehavioralMetric,
|
|
3351
|
+
metricName,
|
|
3352
|
+
{
|
|
3353
|
+
code: expectedCode,
|
|
3354
|
+
correlation_id: meeting.correlationId,
|
|
3355
|
+
reason: expectedReason,
|
|
3356
|
+
stack: sinon.match.any
|
|
3357
|
+
},
|
|
3358
|
+
{
|
|
3359
|
+
type: expectedMetadataType
|
|
3360
|
+
}
|
|
3361
|
+
);
|
|
3362
|
+
};
|
|
3363
|
+
|
|
3364
|
+
it('should send metrics for SdpOfferCreationError error', () => {
|
|
3365
|
+
const fakeError = new MC.Errors.SdpOfferCreationError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
|
|
3366
|
+
|
|
3367
|
+
eventListeners[MC.Event.ROAP_FAILURE](fakeError);
|
|
3368
|
+
|
|
3369
|
+
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3370
|
+
checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpOfferCreationError, fakeErrorMessage, fakeRootCauseName);
|
|
3371
|
+
});
|
|
3372
|
+
|
|
3373
|
+
it('should send metrics for SdpOfferHandlingError error', () => {
|
|
3374
|
+
const fakeError = new MC.Errors.SdpOfferHandlingError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
|
|
3375
|
+
|
|
3376
|
+
eventListeners[MC.Event.ROAP_FAILURE](fakeError);
|
|
3377
|
+
|
|
3378
|
+
checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
|
|
3379
|
+
checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpOfferHandlingError, fakeErrorMessage, fakeRootCauseName);
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3382
|
+
it('should send metrics for SdpAnswerHandlingError error', () => {
|
|
3383
|
+
const fakeError = new MC.Errors.SdpAnswerHandlingError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
|
|
3384
|
+
|
|
3385
|
+
eventListeners[MC.Event.ROAP_FAILURE](fakeError);
|
|
3386
|
+
|
|
3387
|
+
checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
|
|
3388
|
+
checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpAnswerHandlingError, fakeErrorMessage, fakeRootCauseName);
|
|
3389
|
+
});
|
|
3390
|
+
|
|
3391
|
+
it('should send metrics for SdpError error', () => {
|
|
3392
|
+
// SdpError is usually without a cause
|
|
3393
|
+
const fakeError = new MC.Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
|
|
3394
|
+
|
|
3395
|
+
eventListeners[MC.Event.ROAP_FAILURE](fakeError);
|
|
3396
|
+
|
|
3397
|
+
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3398
|
+
// expectedMetadataType is the error name in this case
|
|
3399
|
+
checkBehavioralMetricSent(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, MC.Errors.ErrorCode.SdpError, fakeErrorMessage, fakeErrorName);
|
|
3400
|
+
});
|
|
3401
|
+
|
|
3402
|
+
it('should send metrics for IceGatheringError error', () => {
|
|
3403
|
+
// IceGatheringError is usually without a cause
|
|
3404
|
+
const fakeError = new MC.Errors.IceGatheringError(fakeErrorMessage, {name: fakeErrorName});
|
|
3405
|
+
|
|
3406
|
+
eventListeners[MC.Event.ROAP_FAILURE](fakeError);
|
|
3407
|
+
|
|
3408
|
+
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3409
|
+
// expectedMetadataType is the error name in this case
|
|
3410
|
+
checkBehavioralMetricSent(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, MC.Errors.ErrorCode.IceGatheringError, fakeErrorMessage, fakeErrorName);
|
|
3411
|
+
});
|
|
3412
|
+
});
|
|
3413
|
+
|
|
3414
|
+
describe('handles MC.Event.ROAP_MESSAGE_TO_SEND correctly', () => {
|
|
3415
|
+
let sendRoapOKStub;
|
|
3416
|
+
let sendRoapMediaRequestStub;
|
|
3417
|
+
let sendRoapAnswerStub;
|
|
3418
|
+
let sendRoapErrorStub;
|
|
3419
|
+
|
|
3420
|
+
beforeEach(() => {
|
|
3421
|
+
sendRoapOKStub = sinon.stub(meeting.roap, 'sendRoapOK').resolves({});
|
|
3422
|
+
sendRoapMediaRequestStub = sinon.stub(meeting.roap, 'sendRoapMediaRequest').resolves({});
|
|
3423
|
+
sendRoapAnswerStub = sinon.stub(meeting.roap, 'sendRoapAnswer').resolves({});
|
|
3424
|
+
sendRoapErrorStub = sinon.stub(meeting.roap, 'sendRoapError').resolves({});
|
|
3425
|
+
|
|
3426
|
+
meeting.setupMediaConnectionListeners();
|
|
3427
|
+
});
|
|
3428
|
+
|
|
3429
|
+
it('handles OK message correctly', () => {
|
|
3430
|
+
eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({roapMessage: {messageType: 'OK', seq: 1}});
|
|
3431
|
+
|
|
3432
|
+
assert.calledOnce(Metrics.postEvent);
|
|
3433
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.REMOTE_SDP_RECEIVED, meetingId: meeting.id});
|
|
3434
|
+
|
|
3435
|
+
assert.calledOnce(sendRoapOKStub);
|
|
3436
|
+
assert.calledWith(sendRoapOKStub, {seq: 1, mediaId: meeting.mediaId, correlationId: meeting.correlationId});
|
|
3437
|
+
});
|
|
3438
|
+
|
|
3439
|
+
it('handles OFFER message correctly', () => {
|
|
3440
|
+
eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
|
|
3441
|
+
roapMessage: {
|
|
3442
|
+
messageType: 'OFFER',
|
|
3443
|
+
seq: 1,
|
|
3444
|
+
sdp: 'fake sdp',
|
|
3445
|
+
tieBreaker: 12345,
|
|
3446
|
+
}
|
|
3447
|
+
});
|
|
3448
|
+
|
|
3449
|
+
assert.calledOnce(Metrics.postEvent);
|
|
3450
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.LOCAL_SDP_GENERATED, meetingId: meeting.id});
|
|
3451
|
+
|
|
3452
|
+
assert.calledOnce(sendRoapMediaRequestStub);
|
|
3453
|
+
assert.calledWith(sendRoapMediaRequestStub, {
|
|
3454
|
+
seq: 1, sdp: 'fake sdp', tieBreaker: 12345, meeting, reconnect: false
|
|
3455
|
+
});
|
|
3456
|
+
});
|
|
3457
|
+
|
|
3458
|
+
it('handles ANSWER message correctly', () => {
|
|
3459
|
+
eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
|
|
3460
|
+
roapMessage: {
|
|
3461
|
+
messageType: 'ANSWER',
|
|
3462
|
+
seq: 10,
|
|
3463
|
+
sdp: 'fake sdp answer',
|
|
3464
|
+
tieBreaker: 12345,
|
|
3465
|
+
}
|
|
3466
|
+
});
|
|
3467
|
+
|
|
3468
|
+
assert.calledOnce(Metrics.postEvent);
|
|
3469
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.REMOTE_SDP_RECEIVED, meetingId: meeting.id});
|
|
3470
|
+
|
|
3471
|
+
assert.calledOnce(sendRoapAnswerStub);
|
|
3472
|
+
assert.calledWith(sendRoapAnswerStub, {
|
|
3473
|
+
seq: 10, sdp: 'fake sdp answer', mediaId: meeting.mediaId, correlationId: meeting.correlationId
|
|
3474
|
+
});
|
|
3475
|
+
});
|
|
3476
|
+
|
|
3477
|
+
it('sends metrics if fails to send roap ANSWER message', async () => {
|
|
3478
|
+
sendRoapAnswerStub.rejects(new Error('sending answer failed'));
|
|
3479
|
+
|
|
3480
|
+
await eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
|
|
3481
|
+
roapMessage: {
|
|
3482
|
+
messageType: 'ANSWER',
|
|
3483
|
+
seq: 10,
|
|
3484
|
+
sdp: 'fake sdp answer',
|
|
3485
|
+
tieBreaker: 12345,
|
|
3486
|
+
}
|
|
3487
|
+
});
|
|
3488
|
+
await testUtils.flushPromises();
|
|
3489
|
+
|
|
3490
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3491
|
+
assert.calledWithMatch(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE, {
|
|
3492
|
+
correlation_id: meeting.correlationId,
|
|
3493
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3494
|
+
reason: 'sending answer failed'
|
|
3495
|
+
});
|
|
3496
|
+
});
|
|
3497
|
+
|
|
3498
|
+
[MC.ErrorType.CONFLICT, MC.ErrorType.DOUBLECONFLICT].forEach((errorType) =>
|
|
3499
|
+
it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
|
|
3500
|
+
eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
|
|
3501
|
+
roapMessage: {
|
|
3502
|
+
messageType: 'ERROR',
|
|
3503
|
+
seq: 10,
|
|
3504
|
+
errorType,
|
|
3505
|
+
tieBreaker: 12345,
|
|
3506
|
+
}
|
|
3507
|
+
});
|
|
3508
|
+
|
|
3509
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3510
|
+
assert.calledWithMatch(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
|
|
3511
|
+
correlation_id: meeting.correlationId,
|
|
3512
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3513
|
+
sequence: 10
|
|
3514
|
+
});
|
|
3515
|
+
|
|
3516
|
+
assert.calledOnce(sendRoapErrorStub);
|
|
3517
|
+
assert.calledWith(sendRoapErrorStub, {
|
|
3518
|
+
seq: 10, errorType, mediaId: meeting.mediaId, correlationId: meeting.correlationId
|
|
3519
|
+
});
|
|
3520
|
+
}));
|
|
3521
|
+
|
|
3522
|
+
it('handles ERROR message indicating other errors correctly', () => {
|
|
3523
|
+
eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
|
|
3524
|
+
roapMessage: {
|
|
3525
|
+
messageType: 'ERROR',
|
|
3526
|
+
seq: 10,
|
|
3527
|
+
errorType: MC.ErrorType.FAILED,
|
|
3528
|
+
tieBreaker: 12345,
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
|
|
3532
|
+
assert.notCalled(Metrics.sendBehavioralMetric);
|
|
3533
|
+
|
|
3534
|
+
assert.calledOnce(sendRoapErrorStub);
|
|
3535
|
+
assert.calledWith(sendRoapErrorStub, {
|
|
3536
|
+
seq: 10,
|
|
3537
|
+
errorType: MC.ErrorType.FAILED,
|
|
3538
|
+
mediaId: meeting.mediaId,
|
|
3539
|
+
correlationId: meeting.correlationId
|
|
3540
|
+
});
|
|
3541
|
+
});
|
|
3323
3542
|
});
|
|
3324
3543
|
});
|
|
3325
3544
|
describe('#setUpLocusInfoSelfListener', () => {
|
|
@@ -3457,22 +3676,34 @@ describe('plugin-meetings', () => {
|
|
|
3457
3676
|
sandbox = null;
|
|
3458
3677
|
});
|
|
3459
3678
|
|
|
3460
|
-
describe('#
|
|
3461
|
-
it('should have #
|
|
3462
|
-
assert.exists(meeting.
|
|
3679
|
+
describe('#releaseScreenShareFloor', () => {
|
|
3680
|
+
it('should have #releaseScreenShareFloor', () => {
|
|
3681
|
+
assert.exists(meeting.releaseScreenShareFloor);
|
|
3463
3682
|
});
|
|
3464
3683
|
beforeEach(() => {
|
|
3465
|
-
meeting.
|
|
3684
|
+
meeting.selfId = 'some self id';
|
|
3685
|
+
meeting.locusInfo.mediaShares = [{name: 'content', url: url1, floor: {beneficiary: {id: meeting.selfId}}}];
|
|
3466
3686
|
meeting.locusInfo.self = {url: url2};
|
|
3687
|
+
meeting.mediaProperties = {mediaDirection: {sendShare: true}};
|
|
3467
3688
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
3468
3689
|
});
|
|
3469
|
-
it('should call
|
|
3470
|
-
const share = meeting.
|
|
3690
|
+
it('should call changeMeetingFloor()', async () => {
|
|
3691
|
+
const share = meeting.releaseScreenShareFloor();
|
|
3471
3692
|
|
|
3472
3693
|
assert.exists(share.then);
|
|
3473
3694
|
await share;
|
|
3474
3695
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
3475
3696
|
});
|
|
3697
|
+
it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
|
|
3698
|
+
// change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
|
|
3699
|
+
meeting.selfId = 'new self id';
|
|
3700
|
+
|
|
3701
|
+
const share = meeting.releaseScreenShareFloor();
|
|
3702
|
+
|
|
3703
|
+
assert.exists(share.then);
|
|
3704
|
+
await share;
|
|
3705
|
+
assert.notCalled(meeting.meetingRequest.changeMeetingFloor);
|
|
3706
|
+
});
|
|
3476
3707
|
});
|
|
3477
3708
|
|
|
3478
3709
|
describe('#setSipUri', () => {
|
|
@@ -3514,13 +3745,13 @@ describe('plugin-meetings', () => {
|
|
|
3514
3745
|
});
|
|
3515
3746
|
});
|
|
3516
3747
|
describe('#closePeerConnections', () => {
|
|
3517
|
-
it('should close the
|
|
3518
|
-
|
|
3748
|
+
it('should close the webrtc media connection, and return a promise', async () => {
|
|
3749
|
+
meeting.mediaProperties.webrtcMediaConnection = {close: sinon.stub()};
|
|
3519
3750
|
const pcs = meeting.closePeerConnections();
|
|
3520
3751
|
|
|
3521
3752
|
assert.exists(pcs.then);
|
|
3522
3753
|
await pcs;
|
|
3523
|
-
assert.calledOnce(
|
|
3754
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.close);
|
|
3524
3755
|
});
|
|
3525
3756
|
});
|
|
3526
3757
|
describe('#unsetPeerConnections', () => {
|
|
@@ -3687,13 +3918,6 @@ describe('plugin-meetings', () => {
|
|
|
3687
3918
|
});
|
|
3688
3919
|
});
|
|
3689
3920
|
});
|
|
3690
|
-
describe('#setRoapSeq', () => {
|
|
3691
|
-
it('should set the roap seq and return null', () => {
|
|
3692
|
-
assert.equal(-1, meeting.roapSeq);
|
|
3693
|
-
meeting.setRoapSeq(1);
|
|
3694
|
-
assert.equal(meeting.roapSeq, 1);
|
|
3695
|
-
});
|
|
3696
|
-
});
|
|
3697
3921
|
describe('#setCorrelationId', () => {
|
|
3698
3922
|
it('should set the correlationId and return undefined', () => {
|
|
3699
3923
|
assert.ok(meeting.correlationId);
|
|
@@ -3763,6 +3987,7 @@ describe('plugin-meetings', () => {
|
|
|
3763
3987
|
let canUserLowerAllHandsSpy;
|
|
3764
3988
|
let canUserLowerSomeoneElsesHandSpy;
|
|
3765
3989
|
let waitingForOthersToJoinSpy;
|
|
3990
|
+
let handleDataChannelUrlChangeSpy;
|
|
3766
3991
|
|
|
3767
3992
|
beforeEach(() => {
|
|
3768
3993
|
locusInfoOnSpy = sinon.spy(meeting.locusInfo, 'on');
|
|
@@ -3778,6 +4003,7 @@ describe('plugin-meetings', () => {
|
|
|
3778
4003
|
bothLeaveAndEndMeetingAvailableSpy = sinon.spy(MeetingUtil, 'bothLeaveAndEndMeetingAvailable');
|
|
3779
4004
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
3780
4005
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
4006
|
+
handleDataChannelUrlChangeSpy = sinon.spy(meeting, 'handleDataChannelUrlChange');
|
|
3781
4007
|
});
|
|
3782
4008
|
|
|
3783
4009
|
afterEach(() => {
|
|
@@ -3799,7 +4025,8 @@ describe('plugin-meetings', () => {
|
|
|
3799
4025
|
|
|
3800
4026
|
const payload = {
|
|
3801
4027
|
info: {
|
|
3802
|
-
userDisplayHints: ['LOCK_CONTROL_UNLOCK']
|
|
4028
|
+
userDisplayHints: ['LOCK_CONTROL_UNLOCK'],
|
|
4029
|
+
datachannelUrl: 'some url'
|
|
3803
4030
|
}
|
|
3804
4031
|
};
|
|
3805
4032
|
|
|
@@ -3816,6 +4043,7 @@ describe('plugin-meetings', () => {
|
|
|
3816
4043
|
assert.calledWith(canUserLowerAllHandsSpy, payload.info.userDisplayHints);
|
|
3817
4044
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, payload.info.userDisplayHints);
|
|
3818
4045
|
assert.calledWith(waitingForOthersToJoinSpy, payload.info.userDisplayHints);
|
|
4046
|
+
assert.calledWith(handleDataChannelUrlChangeSpy, payload.info.datachannelUrl);
|
|
3819
4047
|
|
|
3820
4048
|
assert.calledWith(
|
|
3821
4049
|
TriggerProxy.trigger,
|
|
@@ -3836,6 +4064,118 @@ describe('plugin-meetings', () => {
|
|
|
3836
4064
|
});
|
|
3837
4065
|
});
|
|
3838
4066
|
|
|
4067
|
+
describe('#handleDataChannelUrlChange', () => {
|
|
4068
|
+
let updateLLMConnectionSpy;
|
|
4069
|
+
|
|
4070
|
+
beforeEach(() => {
|
|
4071
|
+
updateLLMConnectionSpy = sinon.spy(meeting, 'updateLLMConnection');
|
|
4072
|
+
});
|
|
4073
|
+
|
|
4074
|
+
const check = async (url, expectedCalled) => {
|
|
4075
|
+
meeting.handleDataChannelUrlChange(url);
|
|
4076
|
+
|
|
4077
|
+
assert.notCalled(updateLLMConnectionSpy);
|
|
4078
|
+
|
|
4079
|
+
await testUtils.waitUntil(0);
|
|
4080
|
+
|
|
4081
|
+
if (expectedCalled) {
|
|
4082
|
+
assert.calledWith(updateLLMConnectionSpy);
|
|
4083
|
+
}
|
|
4084
|
+
else {
|
|
4085
|
+
assert.notCalled(updateLLMConnectionSpy);
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
4088
|
+
|
|
4089
|
+
it('calls deferred updateLLMConnection if datachannelURL is set and the enableAutomaticLLM is true', async () => {
|
|
4090
|
+
meeting.config.enableAutomaticLLM = true;
|
|
4091
|
+
check('some url', true);
|
|
4092
|
+
});
|
|
4093
|
+
|
|
4094
|
+
it('does not call updateLLMConnection if datachannelURL is undefined', async () => {
|
|
4095
|
+
meeting.config.enableAutomaticLLM = true;
|
|
4096
|
+
check(undefined, false);
|
|
4097
|
+
});
|
|
4098
|
+
|
|
4099
|
+
it('does not call updateLLMConnection if enableAutomaticLLM is false', async () => {
|
|
4100
|
+
check('some url', false);
|
|
4101
|
+
});
|
|
4102
|
+
});
|
|
4103
|
+
|
|
4104
|
+
describe('#updateLLMConnection', () => {
|
|
4105
|
+
beforeEach(() => {
|
|
4106
|
+
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
4107
|
+
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
4108
|
+
webex.internal.llm.registerAndConnect = sinon.stub().returns(Promise.resolve('something'));
|
|
4109
|
+
webex.internal.llm.disconnectLLM = sinon.stub().returns(Promise.resolve());
|
|
4110
|
+
});
|
|
4111
|
+
|
|
4112
|
+
it('does not connect if the call is not joined yet', async () => {
|
|
4113
|
+
meeting.joinedWith = {state: 'any other state'};
|
|
4114
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
4115
|
+
|
|
4116
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4117
|
+
|
|
4118
|
+
const result = await meeting.updateLLMConnection();
|
|
4119
|
+
|
|
4120
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4121
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4122
|
+
assert.equal(result, undefined);
|
|
4123
|
+
});
|
|
4124
|
+
|
|
4125
|
+
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
4126
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
4127
|
+
webex.internal.llm.isConnected.returns(true);
|
|
4128
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
4129
|
+
|
|
4130
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4131
|
+
|
|
4132
|
+
const result = await meeting.updateLLMConnection();
|
|
4133
|
+
|
|
4134
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4135
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4136
|
+
assert.equal(result, undefined);
|
|
4137
|
+
});
|
|
4138
|
+
|
|
4139
|
+
it('connects if not already connected', async () => {
|
|
4140
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
4141
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4142
|
+
|
|
4143
|
+
const result = await meeting.updateLLMConnection();
|
|
4144
|
+
|
|
4145
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4146
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a datachannel url');
|
|
4147
|
+
assert.equal(result, 'something');
|
|
4148
|
+
});
|
|
4149
|
+
|
|
4150
|
+
it('disconnects if first if the locus url has changed', async () => {
|
|
4151
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
4152
|
+
webex.internal.llm.isConnected.returns(true);
|
|
4153
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
4154
|
+
|
|
4155
|
+
meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4156
|
+
|
|
4157
|
+
const result = await meeting.updateLLMConnection();
|
|
4158
|
+
|
|
4159
|
+
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
4160
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a different url', 'a datachannel url');
|
|
4161
|
+
assert.equal(result, 'something');
|
|
4162
|
+
});
|
|
4163
|
+
|
|
4164
|
+
it('disconnects when the state is not JOINED', async () => {
|
|
4165
|
+
meeting.joinedWith = {state: 'any other state'};
|
|
4166
|
+
webex.internal.llm.isConnected.returns(true);
|
|
4167
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
4168
|
+
|
|
4169
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4170
|
+
|
|
4171
|
+
const result = await meeting.updateLLMConnection();
|
|
4172
|
+
|
|
4173
|
+
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
4174
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4175
|
+
assert.equal(result, undefined);
|
|
4176
|
+
});
|
|
4177
|
+
});
|
|
4178
|
+
|
|
3839
4179
|
describe('#setLocus', () => {
|
|
3840
4180
|
beforeEach(() => {
|
|
3841
4181
|
meeting.locusInfo.initialSetup = sinon.stub().returns(true);
|
|
@@ -4003,7 +4343,7 @@ describe('plugin-meetings', () => {
|
|
|
4003
4343
|
if (newPayload.previous.content.beneficiaryId === USER_IDS.ME) {
|
|
4004
4344
|
eventTrigger.share.push({
|
|
4005
4345
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
4006
|
-
functionName: '
|
|
4346
|
+
functionName: 'localShare'
|
|
4007
4347
|
});
|
|
4008
4348
|
}
|
|
4009
4349
|
else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
|
|
@@ -4054,7 +4394,7 @@ describe('plugin-meetings', () => {
|
|
|
4054
4394
|
if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
|
|
4055
4395
|
eventTrigger.share.push({
|
|
4056
4396
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
4057
|
-
functionName: '
|
|
4397
|
+
functionName: 'localShare'
|
|
4058
4398
|
});
|
|
4059
4399
|
}
|
|
4060
4400
|
else {
|
|
@@ -4073,7 +4413,7 @@ describe('plugin-meetings', () => {
|
|
|
4073
4413
|
if (newPayload.previous.content.beneficiaryId === USER_IDS.ME) {
|
|
4074
4414
|
eventTrigger.share.push({
|
|
4075
4415
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
4076
|
-
functionName: '
|
|
4416
|
+
functionName: 'localShare'
|
|
4077
4417
|
});
|
|
4078
4418
|
}
|
|
4079
4419
|
else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
|
|
@@ -4116,7 +4456,7 @@ describe('plugin-meetings', () => {
|
|
|
4116
4456
|
if (beneficiaryId === USER_IDS.ME) {
|
|
4117
4457
|
eventTrigger.share.push({
|
|
4118
4458
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
4119
|
-
functionName: '
|
|
4459
|
+
functionName: 'localShare'
|
|
4120
4460
|
});
|
|
4121
4461
|
}
|
|
4122
4462
|
else {
|
|
@@ -4582,6 +4922,93 @@ describe('plugin-meetings', () => {
|
|
|
4582
4922
|
meeting.stopKeepAlive();
|
|
4583
4923
|
});
|
|
4584
4924
|
});
|
|
4925
|
+
|
|
4926
|
+
describe('#sendReaction', () => {
|
|
4927
|
+
it('should have #sendReaction', () => {
|
|
4928
|
+
assert.exists(meeting.sendReaction);
|
|
4929
|
+
});
|
|
4930
|
+
|
|
4931
|
+
beforeEach(() => {
|
|
4932
|
+
meeting.meetingRequest.sendReaction = sinon.stub().returns(Promise.resolve());
|
|
4933
|
+
});
|
|
4934
|
+
|
|
4935
|
+
it('should send reaction with the right data and return a promise', async () => {
|
|
4936
|
+
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
4937
|
+
|
|
4938
|
+
const reactionPromise = meeting.sendReaction('thumbs_down', 'light');
|
|
4939
|
+
|
|
4940
|
+
assert.exists(reactionPromise.then);
|
|
4941
|
+
await reactionPromise;
|
|
4942
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
|
|
4943
|
+
reactionChannelUrl: 'Fake URL',
|
|
4944
|
+
reaction: {
|
|
4945
|
+
type: 'thumb_down',
|
|
4946
|
+
codepoints: '1F44E',
|
|
4947
|
+
shortcodes: ':thumbsdown:',
|
|
4948
|
+
tone: {
|
|
4949
|
+
type: 'light_skin_tone',
|
|
4950
|
+
codepoints: '1F3FB',
|
|
4951
|
+
shortcodes: ':skin-tone-2:'
|
|
4952
|
+
}
|
|
4953
|
+
},
|
|
4954
|
+
participantId: meeting.members.selfId,
|
|
4955
|
+
});
|
|
4956
|
+
});
|
|
4957
|
+
|
|
4958
|
+
it('should fail sending a reaction if data channel is undefined', async () => {
|
|
4959
|
+
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: undefined}};
|
|
4960
|
+
|
|
4961
|
+
await assert.isRejected(meeting.sendReaction('thumbs_down', 'light'), Error, 'Error sending reaction, service url not found.');
|
|
4962
|
+
|
|
4963
|
+
assert.notCalled(meeting.meetingRequest.sendReaction);
|
|
4964
|
+
});
|
|
4965
|
+
|
|
4966
|
+
it('should fail sending a reaction if reactionType is invalid ', async () => {
|
|
4967
|
+
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
4968
|
+
|
|
4969
|
+
await assert.isRejected(meeting.sendReaction('invalid_reaction', 'light'), Error, 'invalid_reaction is not a valid reaction.');
|
|
4970
|
+
|
|
4971
|
+
assert.notCalled(meeting.meetingRequest.sendReaction);
|
|
4972
|
+
});
|
|
4973
|
+
|
|
4974
|
+
it('should send a reaction with default skin tone if provided skinToneType is invalid ', async () => {
|
|
4975
|
+
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
4976
|
+
|
|
4977
|
+
const reactionPromise = meeting.sendReaction('thumbs_down', 'invalid_skin_tone');
|
|
4978
|
+
|
|
4979
|
+
assert.exists(reactionPromise.then);
|
|
4980
|
+
await reactionPromise;
|
|
4981
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
|
|
4982
|
+
reactionChannelUrl: 'Fake URL',
|
|
4983
|
+
reaction: {
|
|
4984
|
+
type: 'thumb_down',
|
|
4985
|
+
codepoints: '1F44E',
|
|
4986
|
+
shortcodes: ':thumbsdown:',
|
|
4987
|
+
tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''}
|
|
4988
|
+
},
|
|
4989
|
+
participantId: meeting.members.selfId,
|
|
4990
|
+
});
|
|
4991
|
+
});
|
|
4992
|
+
|
|
4993
|
+
it('should send a reaction with default skin tone if none provided', async () => {
|
|
4994
|
+
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
4995
|
+
|
|
4996
|
+
const reactionPromise = meeting.sendReaction('thumbs_down');
|
|
4997
|
+
|
|
4998
|
+
assert.exists(reactionPromise.then);
|
|
4999
|
+
await reactionPromise;
|
|
5000
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
|
|
5001
|
+
reactionChannelUrl: 'Fake URL',
|
|
5002
|
+
reaction: {
|
|
5003
|
+
type: 'thumb_down',
|
|
5004
|
+
codepoints: '1F44E',
|
|
5005
|
+
shortcodes: ':thumbsdown:',
|
|
5006
|
+
tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''}
|
|
5007
|
+
},
|
|
5008
|
+
participantId: meeting.members.selfId,
|
|
5009
|
+
});
|
|
5010
|
+
});
|
|
5011
|
+
});
|
|
4585
5012
|
});
|
|
4586
5013
|
});
|
|
4587
5014
|
});
|