@webex/plugin-meetings 3.7.0 → 3.8.0-next.10
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/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/join-forbidden-error.js +52 -0
- package/dist/common/errors/join-forbidden-error.js.map +1 -0
- package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
- package/dist/common/errors/join-webinar-error.js.map +1 -0
- package/dist/common/errors/multistream-not-supported-error.js +53 -0
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +69 -6
- package/dist/constants.js.map +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +4 -4
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +14 -3
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +35 -17
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +1 -0
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +30 -16
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/brbState.js +167 -0
- package/dist/meeting/brbState.js.map +1 -0
- package/dist/meeting/in-meeting-actions.js +13 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1373 -1052
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +32 -11
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +1 -6
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +51 -29
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +103 -67
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +115 -45
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +6 -2
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/index.js +107 -55
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +2 -0
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +39 -28
- package/dist/member/util.js.map +1 -1
- package/dist/members/util.js +4 -2
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +6 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/remoteMedia.js +30 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +40 -8
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +24 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/clusterReachability.js +12 -15
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +471 -140
- package/dist/reachability/index.js.map +1 -1
- package/dist/{rtcMetrics/constants.js → reachability/reachability.types.js} +1 -5
- package/dist/reachability/reachability.types.js.map +1 -0
- package/dist/reachability/request.js +21 -8
- package/dist/reachability/request.js.map +1 -1
- package/dist/recording-controller/enums.js +8 -4
- package/dist/recording-controller/enums.js.map +1 -1
- package/dist/recording-controller/index.js +18 -9
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/recording-controller/util.js +13 -9
- package/dist/recording-controller/util.js.map +1 -1
- package/dist/roap/index.js +15 -15
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +45 -79
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +3 -6
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/types/annotation/index.d.ts +5 -0
- package/dist/types/common/errors/join-forbidden-error.d.ts +15 -0
- package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
- package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/constants.d.ts +54 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/meeting/brbState.d.ts +54 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +12 -0
- package/dist/types/meeting/index.d.ts +86 -14
- package/dist/types/meeting/locusMediaRequest.d.ts +6 -3
- package/dist/types/meeting/request.d.ts +14 -3
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +3 -3
- package/dist/types/meeting-info/meeting-info-v2.d.ts +30 -5
- package/dist/types/meetings/index.d.ts +20 -2
- package/dist/types/meetings/meetings.types.d.ts +8 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +7 -0
- package/dist/types/members/util.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +6 -1
- package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
- package/dist/types/multistream/sendSlotManager.d.ts +8 -1
- package/dist/types/reachability/clusterReachability.d.ts +1 -10
- package/dist/types/reachability/index.d.ts +83 -36
- package/dist/types/reachability/reachability.types.d.ts +64 -0
- package/dist/types/reachability/request.d.ts +5 -1
- package/dist/types/recording-controller/enums.d.ts +5 -2
- package/dist/types/recording-controller/index.d.ts +1 -0
- package/dist/types/recording-controller/util.d.ts +2 -1
- package/dist/types/roap/request.d.ts +1 -13
- package/dist/webinar/index.js +390 -7
- package/dist/webinar/index.js.map +1 -1
- package/package.json +23 -22
- package/src/annotation/index.ts +16 -0
- package/src/common/errors/join-forbidden-error.ts +26 -0
- package/src/common/errors/join-webinar-error.ts +24 -0
- package/src/common/errors/multistream-not-supported-error.ts +30 -0
- package/src/config.ts +2 -0
- package/src/constants.ts +62 -3
- package/src/index.ts +5 -3
- package/src/interpretation/index.ts +3 -3
- package/src/locus-info/index.ts +20 -3
- package/src/locus-info/selfUtils.ts +24 -6
- package/src/media/MediaConnectionAwaiter.ts +2 -0
- package/src/media/properties.ts +34 -13
- package/src/meeting/brbState.ts +169 -0
- package/src/meeting/in-meeting-actions.ts +25 -0
- package/src/meeting/index.ts +485 -88
- package/src/meeting/locusMediaRequest.ts +38 -12
- package/src/meeting/muteState.ts +1 -6
- package/src/meeting/request.ts +30 -12
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +32 -13
- package/src/meeting-info/meeting-info-v2.ts +83 -12
- package/src/meeting-info/utilv2.ts +17 -3
- package/src/meetings/index.ts +79 -20
- package/src/meetings/meetings.types.ts +10 -0
- package/src/meetings/util.ts +2 -1
- package/src/member/index.ts +9 -0
- package/src/member/types.ts +8 -0
- package/src/member/util.ts +34 -24
- package/src/members/util.ts +1 -0
- package/src/metrics/constants.ts +6 -1
- package/src/multistream/remoteMedia.ts +28 -15
- package/src/multistream/remoteMediaManager.ts +32 -10
- package/src/multistream/sendSlotManager.ts +31 -0
- package/src/reachability/clusterReachability.ts +5 -15
- package/src/reachability/index.ts +315 -75
- package/src/reachability/reachability.types.ts +85 -0
- package/src/reachability/request.ts +55 -31
- package/src/recording-controller/enums.ts +5 -2
- package/src/recording-controller/index.ts +17 -4
- package/src/recording-controller/util.ts +28 -9
- package/src/roap/index.ts +14 -13
- package/src/roap/request.ts +30 -44
- package/src/roap/turnDiscovery.ts +2 -4
- package/src/webinar/index.ts +235 -9
- package/test/unit/spec/annotation/index.ts +46 -1
- package/test/unit/spec/interpretation/index.ts +39 -1
- package/test/unit/spec/locus-info/index.js +292 -60
- package/test/unit/spec/locus-info/selfConstant.js +7 -0
- package/test/unit/spec/locus-info/selfUtils.js +101 -1
- package/test/unit/spec/media/properties.ts +15 -0
- package/test/unit/spec/meeting/brbState.ts +114 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +15 -1
- package/test/unit/spec/meeting/index.js +908 -124
- package/test/unit/spec/meeting/locusMediaRequest.ts +111 -66
- package/test/unit/spec/meeting/muteState.js +0 -24
- package/test/unit/spec/meeting/request.js +3 -26
- package/test/unit/spec/meeting/utils.js +73 -28
- package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
- package/test/unit/spec/meeting-info/utilv2.js +26 -0
- package/test/unit/spec/meetings/index.js +172 -18
- package/test/unit/spec/meetings/utils.js +10 -0
- package/test/unit/spec/member/util.js +52 -11
- package/test/unit/spec/members/utils.js +95 -0
- package/test/unit/spec/multistream/remoteMedia.ts +11 -7
- package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
- package/test/unit/spec/reachability/clusterReachability.ts +7 -0
- package/test/unit/spec/reachability/index.ts +391 -9
- package/test/unit/spec/reachability/request.js +48 -12
- package/test/unit/spec/recording-controller/index.js +61 -5
- package/test/unit/spec/recording-controller/util.js +39 -3
- package/test/unit/spec/roap/index.ts +48 -1
- package/test/unit/spec/roap/request.ts +51 -109
- package/test/unit/spec/roap/turnDiscovery.ts +202 -147
- package/test/unit/spec/webinar/index.ts +509 -0
- package/dist/common/errors/webinar-registration-error.js.map +0 -1
- package/dist/networkQualityMonitor/index.js +0 -227
- package/dist/networkQualityMonitor/index.js.map +0 -1
- package/dist/rtcMetrics/constants.js.map +0 -1
- package/dist/rtcMetrics/index.js +0 -197
- package/dist/rtcMetrics/index.js.map +0 -1
- package/dist/types/networkQualityMonitor/index.d.ts +0 -70
- package/dist/types/rtcMetrics/constants.d.ts +0 -4
- package/dist/types/rtcMetrics/index.d.ts +0 -71
- package/src/common/errors/webinar-registration-error.ts +0 -27
|
@@ -91,14 +91,16 @@ import ParameterError from '../../../../src/common/errors/parameter';
|
|
|
91
91
|
import PasswordError from '../../../../src/common/errors/password-error';
|
|
92
92
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
93
93
|
import PermissionError from '../../../../src/common/errors/permission';
|
|
94
|
-
import
|
|
94
|
+
import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
|
|
95
95
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
96
|
+
import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';
|
|
96
97
|
import testUtils from '../../../utils/testUtils';
|
|
97
98
|
import {
|
|
98
99
|
MeetingInfoV2CaptchaError,
|
|
99
100
|
MeetingInfoV2PasswordError,
|
|
100
101
|
MeetingInfoV2PolicyError,
|
|
101
|
-
|
|
102
|
+
MeetingInfoV2JoinWebinarError,
|
|
103
|
+
MeetingInfoV2JoinForbiddenError,
|
|
102
104
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
103
105
|
import {
|
|
104
106
|
DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
|
|
@@ -113,6 +115,9 @@ import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagno
|
|
|
113
115
|
import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
|
|
114
116
|
|
|
115
117
|
import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
|
118
|
+
import { createBrbState } from '@webex/plugin-meetings/src/meeting/brbState';
|
|
119
|
+
import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
|
|
120
|
+
import { EventEmitter } from 'stream';
|
|
116
121
|
|
|
117
122
|
describe('plugin-meetings', () => {
|
|
118
123
|
const logger = {
|
|
@@ -205,6 +210,7 @@ describe('plugin-meetings', () => {
|
|
|
205
210
|
let membersSpy;
|
|
206
211
|
let meetingRequestSpy;
|
|
207
212
|
let correlationId;
|
|
213
|
+
let uploadEvent;
|
|
208
214
|
|
|
209
215
|
beforeEach(() => {
|
|
210
216
|
webex = new MockWebex({
|
|
@@ -244,6 +250,7 @@ describe('plugin-meetings', () => {
|
|
|
244
250
|
isAnyPublicClusterReachable: sinon.stub().resolves(true),
|
|
245
251
|
getReachabilityResults: sinon.stub().resolves(undefined),
|
|
246
252
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
253
|
+
stopReachability: sinon.stub(),
|
|
247
254
|
};
|
|
248
255
|
webex.internal.llm.on = sinon.stub();
|
|
249
256
|
webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
|
|
@@ -273,6 +280,8 @@ describe('plugin-meetings', () => {
|
|
|
273
280
|
test4 = `test4-${uuid.v4()}`;
|
|
274
281
|
testDestination = `testDestination-${uuid.v4()}`;
|
|
275
282
|
correlationId = uuid.v4();
|
|
283
|
+
uploadEvent = new EventEmitter();
|
|
284
|
+
uploadEvent.addListener('progress', () => {})
|
|
276
285
|
|
|
277
286
|
meeting = new Meeting(
|
|
278
287
|
{
|
|
@@ -649,11 +658,10 @@ describe('plugin-meetings', () => {
|
|
|
649
658
|
});
|
|
650
659
|
|
|
651
660
|
const fakeRoapMessage = {id: 'fake TURN discovery message'};
|
|
652
|
-
const fakeReachabilityResults = {id: 'fake reachability'};
|
|
653
661
|
const fakeTurnServerInfo = {id: 'fake turn info'};
|
|
654
662
|
const fakeJoinResult = {id: 'join result'};
|
|
655
663
|
|
|
656
|
-
const joinOptions = {correlationId: '12345'};
|
|
664
|
+
const joinOptions = {correlationId: '12345', enableMultistream: true};
|
|
657
665
|
const mediaOptions = {audioEnabled: true, allowMediaInLobby: true};
|
|
658
666
|
|
|
659
667
|
let generateTurnDiscoveryRequestMessageStub;
|
|
@@ -662,13 +670,14 @@ describe('plugin-meetings', () => {
|
|
|
662
670
|
let addMediaInternalStub;
|
|
663
671
|
|
|
664
672
|
beforeEach(() => {
|
|
665
|
-
meeting.join = sinon.stub().
|
|
673
|
+
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
|
674
|
+
meeting.isMultistream = joinOptions.enableMultistream;
|
|
675
|
+
return Promise.resolve(fakeJoinResult);
|
|
676
|
+
});
|
|
666
677
|
addMediaInternalStub = sinon
|
|
667
678
|
.stub(meeting, 'addMediaInternal')
|
|
668
679
|
.returns(Promise.resolve(test4));
|
|
669
680
|
|
|
670
|
-
webex.meetings.reachability.getReachabilityResults.resolves(fakeReachabilityResults);
|
|
671
|
-
|
|
672
681
|
generateTurnDiscoveryRequestMessageStub = sinon
|
|
673
682
|
.stub(meeting.roap, 'generateTurnDiscoveryRequestMessage')
|
|
674
683
|
.resolves({roapMessage: fakeRoapMessage});
|
|
@@ -688,7 +697,6 @@ describe('plugin-meetings', () => {
|
|
|
688
697
|
assert.calledOnceWithExactly(meeting.join, {
|
|
689
698
|
...joinOptions,
|
|
690
699
|
roapMessage: fakeRoapMessage,
|
|
691
|
-
reachability: fakeReachabilityResults,
|
|
692
700
|
});
|
|
693
701
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
694
702
|
assert.calledOnceWithExactly(
|
|
@@ -704,7 +712,7 @@ describe('plugin-meetings', () => {
|
|
|
704
712
|
mediaOptions
|
|
705
713
|
);
|
|
706
714
|
|
|
707
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
715
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
708
716
|
|
|
709
717
|
// resets joinWithMediaRetryInfo
|
|
710
718
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
@@ -725,7 +733,6 @@ describe('plugin-meetings', () => {
|
|
|
725
733
|
assert.calledOnceWithExactly(meeting.join, {
|
|
726
734
|
...joinOptions,
|
|
727
735
|
roapMessage: undefined,
|
|
728
|
-
reachability: fakeReachabilityResults,
|
|
729
736
|
});
|
|
730
737
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
731
738
|
assert.notCalled(handleTurnDiscoveryHttpResponseStub);
|
|
@@ -738,7 +745,7 @@ describe('plugin-meetings', () => {
|
|
|
738
745
|
mediaOptions
|
|
739
746
|
);
|
|
740
747
|
|
|
741
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
748
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
742
749
|
assert.equal(meeting.turnServerUsed, false);
|
|
743
750
|
});
|
|
744
751
|
|
|
@@ -757,7 +764,6 @@ describe('plugin-meetings', () => {
|
|
|
757
764
|
assert.calledOnceWithExactly(meeting.join, {
|
|
758
765
|
...joinOptions,
|
|
759
766
|
roapMessage: fakeRoapMessage,
|
|
760
|
-
reachability: fakeReachabilityResults,
|
|
761
767
|
});
|
|
762
768
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
763
769
|
assert.calledOnceWithExactly(
|
|
@@ -774,7 +780,7 @@ describe('plugin-meetings', () => {
|
|
|
774
780
|
mediaOptions
|
|
775
781
|
);
|
|
776
782
|
|
|
777
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
783
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
778
784
|
});
|
|
779
785
|
|
|
780
786
|
it('should reject if join() fails', async () => {
|
|
@@ -861,7 +867,8 @@ describe('plugin-meetings', () => {
|
|
|
861
867
|
}
|
|
862
868
|
);
|
|
863
869
|
|
|
864
|
-
|
|
870
|
+
// expect multistreamEnabled: false, because this test overrides the join meeting.join stub so it doesn't set the isMultistream flag
|
|
871
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: false});
|
|
865
872
|
|
|
866
873
|
// resets joinWithMediaRetryInfo
|
|
867
874
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
@@ -950,7 +957,7 @@ describe('plugin-meetings', () => {
|
|
|
950
957
|
mediaOptions,
|
|
951
958
|
});
|
|
952
959
|
|
|
953
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
960
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
954
961
|
|
|
955
962
|
assert.calledOnce(meeting.join);
|
|
956
963
|
assert.notCalled(leaveStub);
|
|
@@ -1044,6 +1051,7 @@ describe('plugin-meetings', () => {
|
|
|
1044
1051
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1045
1052
|
initiateOffer: sinon.stub().resolves({}),
|
|
1046
1053
|
on: sinon.stub(),
|
|
1054
|
+
createSendSlot: sinon.stub(),
|
|
1047
1055
|
};
|
|
1048
1056
|
|
|
1049
1057
|
/* Setup the stubs so that the first call to addMediaInternal() fails
|
|
@@ -1060,12 +1068,18 @@ describe('plugin-meetings', () => {
|
|
|
1060
1068
|
|
|
1061
1069
|
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
1062
1070
|
|
|
1071
|
+
// calling joinWithMedia() with enableMultistream=false, because this test uses real addMediaInternal() implementation
|
|
1072
|
+
// and it requires less stubs when it's without multistream
|
|
1063
1073
|
const result = await meeting.joinWithMedia({
|
|
1064
|
-
joinOptions,
|
|
1074
|
+
joinOptions: {...joinOptions, enableMultistream: false},
|
|
1065
1075
|
mediaOptions,
|
|
1066
1076
|
});
|
|
1067
1077
|
|
|
1068
|
-
assert.deepEqual(result, {
|
|
1078
|
+
assert.deepEqual(result, {
|
|
1079
|
+
join: fakeJoinResult,
|
|
1080
|
+
media: undefined,
|
|
1081
|
+
multistreamEnabled: false,
|
|
1082
|
+
});
|
|
1069
1083
|
|
|
1070
1084
|
assert.calledOnce(meeting.join);
|
|
1071
1085
|
|
|
@@ -1140,6 +1154,7 @@ describe('plugin-meetings', () => {
|
|
|
1140
1154
|
addMediaError.name = 'SdpOfferCreationError';
|
|
1141
1155
|
|
|
1142
1156
|
meeting.addMediaInternal.rejects(addMediaError);
|
|
1157
|
+
sinon.stub(meeting, 'leave').resolves();
|
|
1143
1158
|
|
|
1144
1159
|
await assert.isRejected(
|
|
1145
1160
|
meeting.joinWithMedia({
|
|
@@ -1168,6 +1183,10 @@ describe('plugin-meetings', () => {
|
|
|
1168
1183
|
type: addMediaError.name,
|
|
1169
1184
|
}
|
|
1170
1185
|
);
|
|
1186
|
+
assert.calledOnceWithExactly(meeting.leave, {
|
|
1187
|
+
resourceId: undefined,
|
|
1188
|
+
reason: 'joinWithMedia failure',
|
|
1189
|
+
});
|
|
1171
1190
|
});
|
|
1172
1191
|
});
|
|
1173
1192
|
|
|
@@ -1244,6 +1263,7 @@ describe('plugin-meetings', () => {
|
|
|
1244
1263
|
webex.internal.voicea.off = sinon.stub();
|
|
1245
1264
|
webex.internal.voicea.listenToEvents = sinon.stub();
|
|
1246
1265
|
webex.internal.voicea.turnOnCaptions = sinon.stub();
|
|
1266
|
+
webex.internal.voicea.deregisterEvents = sinon.stub();
|
|
1247
1267
|
});
|
|
1248
1268
|
|
|
1249
1269
|
it('should stop listening to voicea events and also trigger a stop event', () => {
|
|
@@ -1572,6 +1592,55 @@ describe('plugin-meetings', () => {
|
|
|
1572
1592
|
fakeProcessedReaction
|
|
1573
1593
|
);
|
|
1574
1594
|
});
|
|
1595
|
+
|
|
1596
|
+
it('should fail quietly if participantId does not exist in membersCollection', () => {
|
|
1597
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1598
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1599
|
+
meeting.config.receiveReactions = true;
|
|
1600
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1601
|
+
const fakeReactionPayload = {
|
|
1602
|
+
type: 'fake_type',
|
|
1603
|
+
codepoints: 'fake_codepoints',
|
|
1604
|
+
shortcodes: 'fake_shortcodes',
|
|
1605
|
+
tone: {
|
|
1606
|
+
type: 'fake_tone_type',
|
|
1607
|
+
codepoints: 'fake_tone_codepoints',
|
|
1608
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1609
|
+
},
|
|
1610
|
+
};
|
|
1611
|
+
const fakeSenderPayload = {
|
|
1612
|
+
participantId: 'fake_participant_id',
|
|
1613
|
+
};
|
|
1614
|
+
const fakeProcessedReaction = {
|
|
1615
|
+
reaction: fakeReactionPayload,
|
|
1616
|
+
sender: {
|
|
1617
|
+
id: fakeSenderPayload.participantId,
|
|
1618
|
+
name: fakeSendersName,
|
|
1619
|
+
},
|
|
1620
|
+
};
|
|
1621
|
+
const fakeRelayEvent = {
|
|
1622
|
+
data: {
|
|
1623
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1624
|
+
reaction: fakeReactionPayload,
|
|
1625
|
+
sender: fakeSenderPayload,
|
|
1626
|
+
},
|
|
1627
|
+
};
|
|
1628
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1629
|
+
assert.calledWith(
|
|
1630
|
+
LoggerProxy.logger.warn,
|
|
1631
|
+
`Meeting:index#processRelayEvent --> Skipping handling of react for ${meeting.id}. participantId fake_participant_id does not exist in membersCollection.`
|
|
1632
|
+
);
|
|
1633
|
+
assert.neverCalledWith(
|
|
1634
|
+
TriggerProxy.trigger,
|
|
1635
|
+
sinon.match.instanceOf(Meeting),
|
|
1636
|
+
{
|
|
1637
|
+
file: 'meeting/index',
|
|
1638
|
+
function: 'join',
|
|
1639
|
+
},
|
|
1640
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1641
|
+
fakeProcessedReaction
|
|
1642
|
+
);
|
|
1643
|
+
});
|
|
1575
1644
|
});
|
|
1576
1645
|
|
|
1577
1646
|
describe('#handleLLMOnline', () => {
|
|
@@ -1711,6 +1780,12 @@ describe('plugin-meetings', () => {
|
|
|
1711
1780
|
sinon.assert.called(setCorrelationIdSpy);
|
|
1712
1781
|
assert.equal(meeting.correlationId, '123');
|
|
1713
1782
|
});
|
|
1783
|
+
|
|
1784
|
+
it('should not send client.call.initiated if told not to', async () => {
|
|
1785
|
+
await meeting.join({sendCallInitiated: false});
|
|
1786
|
+
|
|
1787
|
+
sinon.assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
1788
|
+
});
|
|
1714
1789
|
});
|
|
1715
1790
|
|
|
1716
1791
|
describe('failure', () => {
|
|
@@ -2034,6 +2109,7 @@ describe('plugin-meetings', () => {
|
|
|
2034
2109
|
someReachabilityMetric1: 'some value1',
|
|
2035
2110
|
someReachabilityMetric2: 'some value2',
|
|
2036
2111
|
}),
|
|
2112
|
+
stopReachability: sinon.stub(),
|
|
2037
2113
|
};
|
|
2038
2114
|
|
|
2039
2115
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
@@ -2453,6 +2529,7 @@ describe('plugin-meetings', () => {
|
|
|
2453
2529
|
assert.calledOnce(meeting.setMercuryListener);
|
|
2454
2530
|
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
2455
2531
|
assert.equal(meeting.allowMediaInLobby, allowMediaInLobby);
|
|
2532
|
+
assert.calledOnce(webex.meetings.reachability.stopReachability);
|
|
2456
2533
|
};
|
|
2457
2534
|
|
|
2458
2535
|
it('should attach the media and return promise', async () => {
|
|
@@ -2471,6 +2548,61 @@ describe('plugin-meetings', () => {
|
|
|
2471
2548
|
checkWorking();
|
|
2472
2549
|
});
|
|
2473
2550
|
|
|
2551
|
+
it('should upload logs periodically', async () => {
|
|
2552
|
+
const clock = sinon.useFakeTimers();
|
|
2553
|
+
|
|
2554
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
2555
|
+
.stub()
|
|
2556
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
2557
|
+
|
|
2558
|
+
let logUploadCounter = 0;
|
|
2559
|
+
|
|
2560
|
+
TriggerProxy.trigger.callsFake((meetingObject, options, event) => {
|
|
2561
|
+
if (
|
|
2562
|
+
meetingObject === meeting &&
|
|
2563
|
+
options.file === 'meeting/index' &&
|
|
2564
|
+
options.function === 'uploadLogs' &&
|
|
2565
|
+
event === 'REQUEST_UPLOAD_LOGS'
|
|
2566
|
+
) {
|
|
2567
|
+
logUploadCounter += 1;
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2571
|
+
meeting.config.logUploadIntervalMultiplicationFactor = 1;
|
|
2572
|
+
meeting.meetingState = 'ACTIVE';
|
|
2573
|
+
|
|
2574
|
+
await meeting.addMedia({
|
|
2575
|
+
mediaSettings: {},
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2578
|
+
const checkLogCounter = (delayInMinutes, expectedCounter) => {
|
|
2579
|
+
const delayInMilliseconds = delayInMinutes * 60 * 1000;
|
|
2580
|
+
|
|
2581
|
+
// first check that the counter is not increased just before the delay
|
|
2582
|
+
clock.tick(delayInMilliseconds - 50);
|
|
2583
|
+
assert.equal(logUploadCounter, expectedCounter - 1);
|
|
2584
|
+
|
|
2585
|
+
// and now check that it has reached expected value after the delay
|
|
2586
|
+
clock.tick(50);
|
|
2587
|
+
assert.equal(logUploadCounter, expectedCounter);
|
|
2588
|
+
};
|
|
2589
|
+
|
|
2590
|
+
checkLogCounter(0.1, 1);
|
|
2591
|
+
checkLogCounter(15, 2);
|
|
2592
|
+
checkLogCounter(30, 3);
|
|
2593
|
+
checkLogCounter(60, 4);
|
|
2594
|
+
checkLogCounter(60, 5);
|
|
2595
|
+
|
|
2596
|
+
// simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
|
|
2597
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
2598
|
+
checkLogCounter(60, 6);
|
|
2599
|
+
|
|
2600
|
+
clock.tick(120 * 1000 * 60);
|
|
2601
|
+
assert.equal(logUploadCounter, 6);
|
|
2602
|
+
|
|
2603
|
+
clock.restore();
|
|
2604
|
+
});
|
|
2605
|
+
|
|
2474
2606
|
it('should attach the media and return promise when in the lobby if allowMediaInLobby is set', async () => {
|
|
2475
2607
|
meeting.roap.doTurnDiscovery = sinon
|
|
2476
2608
|
.stub()
|
|
@@ -2593,6 +2725,7 @@ describe('plugin-meetings', () => {
|
|
|
2593
2725
|
webex.meetings.reachability = {
|
|
2594
2726
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
|
|
2595
2727
|
getReachabilityMetrics: sinon.stub().resolves(),
|
|
2728
|
+
stopReachability: sinon.stub(),
|
|
2596
2729
|
};
|
|
2597
2730
|
const MOCK_CLIENT_ERROR_CODE = 2004;
|
|
2598
2731
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
@@ -2801,6 +2934,7 @@ describe('plugin-meetings', () => {
|
|
|
2801
2934
|
.onCall(2)
|
|
2802
2935
|
.resolves(false),
|
|
2803
2936
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
2937
|
+
stopReachability: sinon.stub(),
|
|
2804
2938
|
};
|
|
2805
2939
|
const getErrorPayloadForClientErrorCodeStub =
|
|
2806
2940
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
@@ -3095,6 +3229,7 @@ describe('plugin-meetings', () => {
|
|
|
3095
3229
|
someReachabilityMetric1: 'some value1',
|
|
3096
3230
|
someReachabilityMetric2: 'some value2',
|
|
3097
3231
|
}),
|
|
3232
|
+
stopReachability: sinon.stub(),
|
|
3098
3233
|
};
|
|
3099
3234
|
meeting.iceCandidatesCount = 3;
|
|
3100
3235
|
meeting.iceCandidateErrors.set('701_error', 3);
|
|
@@ -3424,6 +3559,55 @@ describe('plugin-meetings', () => {
|
|
|
3424
3559
|
});
|
|
3425
3560
|
});
|
|
3426
3561
|
|
|
3562
|
+
it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
|
|
3563
|
+
let fakeMembersCollection = {
|
|
3564
|
+
members: {
|
|
3565
|
+
member1: {isInMeeting: true},
|
|
3566
|
+
member2: {isInMeeting: true},
|
|
3567
|
+
member3: {isInMeeting: false},
|
|
3568
|
+
},
|
|
3569
|
+
};
|
|
3570
|
+
sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
|
|
3571
|
+
const fakeData = {intervalMetadata: {}, networkType: 'wifi'};
|
|
3572
|
+
|
|
3573
|
+
statsAnalyzerStub.emit(
|
|
3574
|
+
{file: 'test', function: 'test'},
|
|
3575
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3576
|
+
{data: fakeData}
|
|
3577
|
+
);
|
|
3578
|
+
|
|
3579
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
3580
|
+
name: 'client.mediaquality.event',
|
|
3581
|
+
options: {
|
|
3582
|
+
meetingId: meeting.id,
|
|
3583
|
+
},
|
|
3584
|
+
payload: {
|
|
3585
|
+
intervals: [
|
|
3586
|
+
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
|
|
3587
|
+
],
|
|
3588
|
+
},
|
|
3589
|
+
});
|
|
3590
|
+
fakeMembersCollection.members.member2.isInMeeting = false;
|
|
3591
|
+
|
|
3592
|
+
statsAnalyzerStub.emit(
|
|
3593
|
+
{file: 'test', function: 'test'},
|
|
3594
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3595
|
+
{data: fakeData}
|
|
3596
|
+
);
|
|
3597
|
+
|
|
3598
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
3599
|
+
name: 'client.mediaquality.event',
|
|
3600
|
+
options: {
|
|
3601
|
+
meetingId: meeting.id,
|
|
3602
|
+
},
|
|
3603
|
+
payload: {
|
|
3604
|
+
intervals: [
|
|
3605
|
+
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1)),
|
|
3606
|
+
],
|
|
3607
|
+
},
|
|
3608
|
+
});
|
|
3609
|
+
});
|
|
3610
|
+
|
|
3427
3611
|
it('calls submitMQE correctly', async () => {
|
|
3428
3612
|
const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
|
|
3429
3613
|
|
|
@@ -3501,14 +3685,6 @@ describe('plugin-meetings', () => {
|
|
|
3501
3685
|
});
|
|
3502
3686
|
});
|
|
3503
3687
|
|
|
3504
|
-
it('succeeds even if getDevices() throws', async () => {
|
|
3505
|
-
meeting.meetingState = 'ACTIVE';
|
|
3506
|
-
|
|
3507
|
-
sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
|
|
3508
|
-
|
|
3509
|
-
await meeting.addMedia();
|
|
3510
|
-
});
|
|
3511
|
-
|
|
3512
3688
|
describe('CA ice failures checks', () => {
|
|
3513
3689
|
[
|
|
3514
3690
|
{
|
|
@@ -3562,6 +3738,7 @@ describe('plugin-meetings', () => {
|
|
|
3562
3738
|
|
|
3563
3739
|
webex.meetings.reachability = {
|
|
3564
3740
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(unreachable || false),
|
|
3741
|
+
stopReachability: sinon.stub(),
|
|
3565
3742
|
};
|
|
3566
3743
|
|
|
3567
3744
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
@@ -3650,6 +3827,72 @@ describe('plugin-meetings', () => {
|
|
|
3650
3827
|
});
|
|
3651
3828
|
});
|
|
3652
3829
|
|
|
3830
|
+
describe(`#beRightBack`, () => {
|
|
3831
|
+
const fakeMultistreamRoapMediaConnection = {
|
|
3832
|
+
createSendSlot: sinon.stub().returns({
|
|
3833
|
+
setSourceStateOverride: sinon.stub().resolves(),
|
|
3834
|
+
clearSourceStateOverride: sinon.stub().resolves(),
|
|
3835
|
+
}),
|
|
3836
|
+
};
|
|
3837
|
+
|
|
3838
|
+
beforeEach(() => {
|
|
3839
|
+
meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
|
|
3840
|
+
meeting.sendSlotManager.createSlot(
|
|
3841
|
+
fakeMultistreamRoapMediaConnection,
|
|
3842
|
+
MediaType.VideoMain
|
|
3843
|
+
);
|
|
3844
|
+
|
|
3845
|
+
meeting.locusUrl = 'locus url';
|
|
3846
|
+
meeting.deviceUrl = 'device url';
|
|
3847
|
+
meeting.selfId = 'self id';
|
|
3848
|
+
meeting.brbState = createBrbState(meeting, false);
|
|
3849
|
+
meeting.brbState.enable = sinon.stub().resolves();
|
|
3850
|
+
});
|
|
3851
|
+
|
|
3852
|
+
afterEach(() => {
|
|
3853
|
+
sinon.restore();
|
|
3854
|
+
});
|
|
3855
|
+
|
|
3856
|
+
it('should have #beRightBack', () => {
|
|
3857
|
+
assert.exists(meeting.beRightBack);
|
|
3858
|
+
});
|
|
3859
|
+
|
|
3860
|
+
describe('when in a multistream meeting', () => {
|
|
3861
|
+
beforeEach(() => {
|
|
3862
|
+
meeting.isMultistream = true;
|
|
3863
|
+
});
|
|
3864
|
+
|
|
3865
|
+
it('should enable #beRightBack and return a promise', async () => {
|
|
3866
|
+
const brbResult = meeting.beRightBack(true);
|
|
3867
|
+
|
|
3868
|
+
await brbResult;
|
|
3869
|
+
assert.exists(brbResult.then);
|
|
3870
|
+
assert.calledOnce(meeting.brbState.enable);
|
|
3871
|
+
});
|
|
3872
|
+
|
|
3873
|
+
it('should disable #beRightBack and return a promise', async () => {
|
|
3874
|
+
const brbResult = meeting.beRightBack(false);
|
|
3875
|
+
|
|
3876
|
+
await brbResult;
|
|
3877
|
+
assert.exists(brbResult.then);
|
|
3878
|
+
assert.calledOnce(meeting.brbState.enable);
|
|
3879
|
+
});
|
|
3880
|
+
|
|
3881
|
+
it('should throw an error and reject the promise if setBrb fails', async () => {
|
|
3882
|
+
const error = new Error('setBrb failed');
|
|
3883
|
+
meeting.brbState.enable.rejects(error);
|
|
3884
|
+
|
|
3885
|
+
try {
|
|
3886
|
+
await meeting.beRightBack(true);
|
|
3887
|
+
} catch (err) {
|
|
3888
|
+
assert.instanceOf(err, Error);
|
|
3889
|
+
assert.equal(err.message, 'setBrb failed');
|
|
3890
|
+
assert.isRejected((Promise.reject()));
|
|
3891
|
+
}
|
|
3892
|
+
});
|
|
3893
|
+
});
|
|
3894
|
+
});
|
|
3895
|
+
|
|
3653
3896
|
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
|
3654
3897
|
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
|
3655
3898
|
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
|
@@ -3692,6 +3935,12 @@ describe('plugin-meetings', () => {
|
|
|
3692
3935
|
meeting.setMercuryListener = sinon.stub();
|
|
3693
3936
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
3694
3937
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
3938
|
+
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
|
|
3939
|
+
.stub()
|
|
3940
|
+
.resolves({id: 'fake reachability'});
|
|
3941
|
+
meeting.webex.meetings.reachability.getClientMediaPreferences = sinon
|
|
3942
|
+
.stub()
|
|
3943
|
+
.resolves({id: 'fake clientMediaPreferences'});
|
|
3695
3944
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
3696
3945
|
turnServerInfo: {
|
|
3697
3946
|
url: 'turns:turn-server-url:443?transport=tcp',
|
|
@@ -3772,6 +4021,7 @@ describe('plugin-meetings', () => {
|
|
|
3772
4021
|
initiateOffer: sinon.stub().resolves({}),
|
|
3773
4022
|
update: sinon.stub().resolves({}),
|
|
3774
4023
|
on: sinon.stub(),
|
|
4024
|
+
roapMessageReceived: sinon.stub(),
|
|
3775
4025
|
};
|
|
3776
4026
|
|
|
3777
4027
|
fakeMultistreamRoapMediaConnection = {
|
|
@@ -3800,7 +4050,7 @@ describe('plugin-meetings', () => {
|
|
|
3800
4050
|
|
|
3801
4051
|
locusMediaRequestStub = sinon
|
|
3802
4052
|
.stub(WebexPlugin.prototype, 'request')
|
|
3803
|
-
.resolves({body: {locus: {fullState: {}}}});
|
|
4053
|
+
.resolves({body: {locus: {fullState: {}}}, upload: sinon.match.instanceOf(EventEmitter), download: sinon.match.instanceOf(EventEmitter)});
|
|
3804
4054
|
|
|
3805
4055
|
// setup some things and mocks so that the call to join() works
|
|
3806
4056
|
// (we need to call join() because it creates the LocusMediaRequest instance
|
|
@@ -3858,8 +4108,10 @@ describe('plugin-meetings', () => {
|
|
|
3858
4108
|
};
|
|
3859
4109
|
|
|
3860
4110
|
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
3861
|
-
const simulateRoapOffer = async () => {
|
|
3862
|
-
|
|
4111
|
+
const simulateRoapOffer = async (stubWaitingForAnswer = true) => {
|
|
4112
|
+
if (stubWaitingForAnswer) {
|
|
4113
|
+
meeting.deferSDPAnswer = {resolve: sinon.stub()};
|
|
4114
|
+
}
|
|
3863
4115
|
const roapListener = getRoapListener();
|
|
3864
4116
|
|
|
3865
4117
|
await roapListener({roapMessage: roapOfferMessage});
|
|
@@ -3877,6 +4129,15 @@ describe('plugin-meetings', () => {
|
|
|
3877
4129
|
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
|
3878
4130
|
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
|
3879
4131
|
|
|
4132
|
+
assert.calledWith(
|
|
4133
|
+
meeting.webex.meetings.reachability.getClientMediaPreferences,
|
|
4134
|
+
meeting.isMultistream,
|
|
4135
|
+
0
|
|
4136
|
+
);
|
|
4137
|
+
assert.calledWith(
|
|
4138
|
+
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap
|
|
4139
|
+
);
|
|
4140
|
+
|
|
3880
4141
|
assert.calledWith(locusMediaRequestStub, {
|
|
3881
4142
|
method: 'PUT',
|
|
3882
4143
|
uri: `${meeting.selfUrl}/media`,
|
|
@@ -3890,16 +4151,16 @@ describe('plugin-meetings', () => {
|
|
|
3890
4151
|
correlationId: meeting.correlationId,
|
|
3891
4152
|
localMedias: [
|
|
3892
4153
|
{
|
|
3893
|
-
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}","headers":["includeAnswerInHttpResponse","noOkInTransaction"]}}`,
|
|
4154
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}","headers":["includeAnswerInHttpResponse","noOkInTransaction"]},"reachability":{"id":"fake reachability"}}`,
|
|
3894
4155
|
mediaId: 'fake media id',
|
|
3895
4156
|
},
|
|
3896
4157
|
],
|
|
3897
4158
|
clientMediaPreferences: {
|
|
3898
|
-
|
|
3899
|
-
joinCookie: undefined,
|
|
3900
|
-
ipver: 0,
|
|
4159
|
+
id: 'fake clientMediaPreferences',
|
|
3901
4160
|
},
|
|
3902
4161
|
},
|
|
4162
|
+
upload: sinon.match.instanceOf(EventEmitter),
|
|
4163
|
+
download: sinon.match.instanceOf(EventEmitter),
|
|
3903
4164
|
});
|
|
3904
4165
|
};
|
|
3905
4166
|
|
|
@@ -3918,17 +4179,17 @@ describe('plugin-meetings', () => {
|
|
|
3918
4179
|
},
|
|
3919
4180
|
correlationId: meeting.correlationId,
|
|
3920
4181
|
clientMediaPreferences: {
|
|
3921
|
-
|
|
3922
|
-
ipver: undefined,
|
|
3923
|
-
joinCookie: undefined,
|
|
4182
|
+
id: 'fake clientMediaPreferences',
|
|
3924
4183
|
},
|
|
3925
4184
|
localMedias: [
|
|
3926
4185
|
{
|
|
3927
|
-
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OK","version":"2","seq":"${seq}"}}`,
|
|
4186
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OK","version":"2","seq":"${seq}"},"reachability":{"id":"fake reachability"}}`,
|
|
3928
4187
|
mediaId: 'fake media id',
|
|
3929
4188
|
},
|
|
3930
4189
|
],
|
|
3931
4190
|
},
|
|
4191
|
+
upload: sinon.match.instanceOf(EventEmitter),
|
|
4192
|
+
download: sinon.match.instanceOf(EventEmitter),
|
|
3932
4193
|
});
|
|
3933
4194
|
};
|
|
3934
4195
|
|
|
@@ -3950,13 +4211,11 @@ describe('plugin-meetings', () => {
|
|
|
3950
4211
|
mediaId: 'fake media id',
|
|
3951
4212
|
},
|
|
3952
4213
|
],
|
|
3953
|
-
clientMediaPreferences: {
|
|
3954
|
-
preferTranscoding: !meeting.isMultistream,
|
|
3955
|
-
ipver: undefined,
|
|
3956
|
-
},
|
|
3957
4214
|
respOnlySdp: true,
|
|
3958
4215
|
usingResource: null,
|
|
3959
4216
|
},
|
|
4217
|
+
upload: sinon.match.instanceOf(EventEmitter),
|
|
4218
|
+
download: sinon.match.instanceOf(EventEmitter),
|
|
3960
4219
|
});
|
|
3961
4220
|
};
|
|
3962
4221
|
|
|
@@ -3967,8 +4226,9 @@ describe('plugin-meetings', () => {
|
|
|
3967
4226
|
remoteQualityLevel,
|
|
3968
4227
|
expectedDebugId,
|
|
3969
4228
|
meetingId,
|
|
4229
|
+
expectMultistream = isMultistream,
|
|
3970
4230
|
}) => {
|
|
3971
|
-
if (
|
|
4231
|
+
if (expectMultistream) {
|
|
3972
4232
|
const {iceServers} = mediaConnectionConfig;
|
|
3973
4233
|
|
|
3974
4234
|
assert.calledOnceWithMatch(
|
|
@@ -4128,7 +4388,6 @@ describe('plugin-meetings', () => {
|
|
|
4128
4388
|
});
|
|
4129
4389
|
|
|
4130
4390
|
it('addMedia() works correctly when media is enabled with streams to publish', async () => {
|
|
4131
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4132
4391
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
4133
4392
|
await simulateRoapOffer();
|
|
4134
4393
|
await simulateRoapOk();
|
|
@@ -4159,12 +4418,9 @@ describe('plugin-meetings', () => {
|
|
|
4159
4418
|
|
|
4160
4419
|
// and that these were the only /media requests that were sent
|
|
4161
4420
|
assert.calledTwice(locusMediaRequestStub);
|
|
4162
|
-
|
|
4163
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4164
4421
|
});
|
|
4165
4422
|
|
|
4166
4423
|
it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
|
|
4167
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4168
4424
|
fakeMicrophoneStream.userMuted = true;
|
|
4169
4425
|
|
|
4170
4426
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
@@ -4196,7 +4452,6 @@ describe('plugin-meetings', () => {
|
|
|
4196
4452
|
|
|
4197
4453
|
// and that these were the only /media requests that were sent
|
|
4198
4454
|
assert.calledTwice(locusMediaRequestStub);
|
|
4199
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4200
4455
|
});
|
|
4201
4456
|
|
|
4202
4457
|
it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
|
|
@@ -4268,7 +4523,6 @@ describe('plugin-meetings', () => {
|
|
|
4268
4523
|
});
|
|
4269
4524
|
|
|
4270
4525
|
it('addMedia() works correctly when media is disabled with streams to publish', async () => {
|
|
4271
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4272
4526
|
await meeting.addMedia({
|
|
4273
4527
|
localStreams: {microphone: fakeMicrophoneStream},
|
|
4274
4528
|
audioEnabled: false,
|
|
@@ -4302,20 +4556,6 @@ describe('plugin-meetings', () => {
|
|
|
4302
4556
|
|
|
4303
4557
|
// and that these were the only /media requests that were sent
|
|
4304
4558
|
assert.calledTwice(locusMediaRequestStub);
|
|
4305
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4306
|
-
});
|
|
4307
|
-
|
|
4308
|
-
it('handleDeviceLogging not called when media is disabled', async () => {
|
|
4309
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4310
|
-
await meeting.addMedia({
|
|
4311
|
-
localStreams: {microphone: fakeMicrophoneStream},
|
|
4312
|
-
audioEnabled: false,
|
|
4313
|
-
videoEnabled: false,
|
|
4314
|
-
});
|
|
4315
|
-
await simulateRoapOffer();
|
|
4316
|
-
await simulateRoapOk();
|
|
4317
|
-
|
|
4318
|
-
assert.notCalled(handleDeviceLoggingSpy);
|
|
4319
4559
|
});
|
|
4320
4560
|
|
|
4321
4561
|
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
@@ -4351,20 +4591,6 @@ describe('plugin-meetings', () => {
|
|
|
4351
4591
|
assert.calledTwice(locusMediaRequestStub);
|
|
4352
4592
|
});
|
|
4353
4593
|
|
|
4354
|
-
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
4355
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4356
|
-
await meeting.addMedia({audioEnabled: false});
|
|
4357
|
-
//calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
|
|
4358
|
-
assert.calledWith(handleDeviceLoggingSpy, false, true);
|
|
4359
|
-
});
|
|
4360
|
-
|
|
4361
|
-
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
4362
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4363
|
-
await meeting.addMedia({videoEnabled: false});
|
|
4364
|
-
//calling handleDeviceLogging audioEnabled as true videoEnabled as false
|
|
4365
|
-
assert.calledWith(handleDeviceLoggingSpy, true, false);
|
|
4366
|
-
});
|
|
4367
|
-
|
|
4368
4594
|
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
4369
4595
|
await meeting.addMedia({videoEnabled: false});
|
|
4370
4596
|
await simulateRoapOffer();
|
|
@@ -4431,13 +4657,6 @@ describe('plugin-meetings', () => {
|
|
|
4431
4657
|
assert.calledTwice(locusMediaRequestStub);
|
|
4432
4658
|
});
|
|
4433
4659
|
|
|
4434
|
-
it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
|
|
4435
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4436
|
-
await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
|
|
4437
|
-
//calling handleDeviceLogging with audioEnabled true and videoEnabled as true
|
|
4438
|
-
assert.calledWith(handleDeviceLoggingSpy, true, true);
|
|
4439
|
-
});
|
|
4440
|
-
|
|
4441
4660
|
describe('publishStreams()/unpublishStreams() calls', () => {
|
|
4442
4661
|
[
|
|
4443
4662
|
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
|
@@ -4833,6 +5052,211 @@ describe('plugin-meetings', () => {
|
|
|
4833
5052
|
assert.notCalled(fakeRoapMediaConnection.update);
|
|
4834
5053
|
})
|
|
4835
5054
|
);
|
|
5055
|
+
|
|
5056
|
+
if (isMultistream) {
|
|
5057
|
+
describe('fallback from multistream to transcoded', () => {
|
|
5058
|
+
let multistreamEventListeners;
|
|
5059
|
+
let transcodedEventListeners;
|
|
5060
|
+
let mockStatsAnalyzerCtor;
|
|
5061
|
+
|
|
5062
|
+
const setupFakeRoapMediaConnection = (fakeRoapMediaConnection, eventListeners) => {
|
|
5063
|
+
fakeRoapMediaConnection.on.callsFake((eventName, cb) => {
|
|
5064
|
+
eventListeners[eventName] = cb;
|
|
5065
|
+
});
|
|
5066
|
+
fakeRoapMediaConnection.initiateOffer.callsFake(() => {
|
|
5067
|
+
// simulate offer being generated
|
|
5068
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
5069
|
+
|
|
5070
|
+
return Promise.resolve();
|
|
5071
|
+
});
|
|
5072
|
+
};
|
|
5073
|
+
|
|
5074
|
+
beforeEach(() => {
|
|
5075
|
+
multistreamEventListeners = {};
|
|
5076
|
+
transcodedEventListeners = {};
|
|
5077
|
+
|
|
5078
|
+
meeting.config.stats.enableStatsAnalyzer = true;
|
|
5079
|
+
|
|
5080
|
+
setupFakeRoapMediaConnection(fakeRoapMediaConnection, transcodedEventListeners);
|
|
5081
|
+
setupFakeRoapMediaConnection(
|
|
5082
|
+
fakeMultistreamRoapMediaConnection,
|
|
5083
|
+
multistreamEventListeners
|
|
5084
|
+
);
|
|
5085
|
+
|
|
5086
|
+
mockStatsAnalyzerCtor = sinon
|
|
5087
|
+
.stub(InternalMediaCoreModule, 'StatsAnalyzer')
|
|
5088
|
+
.callsFake(() => {
|
|
5089
|
+
return {on: sinon.stub(), stopAnalyzer: sinon.stub()};
|
|
5090
|
+
});
|
|
5091
|
+
|
|
5092
|
+
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
5093
|
+
sinon.stub();
|
|
5094
|
+
|
|
5095
|
+
// setup the mock so that we get an SDP answer not from Homer
|
|
5096
|
+
locusMediaRequestStub.callsFake(() => {
|
|
5097
|
+
return Promise.resolve({
|
|
5098
|
+
body: {
|
|
5099
|
+
locus: {},
|
|
5100
|
+
mediaConnections: [
|
|
5101
|
+
{
|
|
5102
|
+
remoteSdp:
|
|
5103
|
+
'{"audioMuted":false,"videoMuted":true,"roapMessage":{"messageType":"ANSWER","version":"2","seq":1,"sdps":["v=0\\r\\no=linus 0 1 IN IP4 23.89.67.4\\r\\ns=-\\r\\nc=IN IP4 23.89.67.4\\r\\n"],"headers":["noOkInTransaction"]},"type":"SDP"}',
|
|
5104
|
+
},
|
|
5105
|
+
],
|
|
5106
|
+
},
|
|
5107
|
+
});
|
|
5108
|
+
});
|
|
5109
|
+
|
|
5110
|
+
sinon.stub(meeting, 'closePeerConnections');
|
|
5111
|
+
sinon.stub(meeting.mediaProperties, 'unsetPeerConnection');
|
|
5112
|
+
sinon.stub(meeting.locusMediaRequest, 'downgradeFromMultistreamToTranscoded');
|
|
5113
|
+
});
|
|
5114
|
+
|
|
5115
|
+
const runCheck = async (turnServerInfo, forceTurnDiscovery) => {
|
|
5116
|
+
// we're calling addMediaInternal() with mic stream,
|
|
5117
|
+
// so that we also verify that audioMute, videoMute info is correctly sent to backend
|
|
5118
|
+
const addMediaPromise = meeting.addMediaInternal(
|
|
5119
|
+
() => '',
|
|
5120
|
+
turnServerInfo,
|
|
5121
|
+
forceTurnDiscovery,
|
|
5122
|
+
{
|
|
5123
|
+
localStreams: {microphone: fakeMicrophoneStream},
|
|
5124
|
+
}
|
|
5125
|
+
);
|
|
5126
|
+
await testUtils.flushPromises();
|
|
5127
|
+
await simulateRoapOffer(false);
|
|
5128
|
+
|
|
5129
|
+
// check MultistreamRoapMediaConnection was created correctly
|
|
5130
|
+
checkMediaConnectionCreated({
|
|
5131
|
+
expectMultistream: true,
|
|
5132
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
5133
|
+
localStreams: {
|
|
5134
|
+
audio: fakeMicrophoneStream,
|
|
5135
|
+
video: undefined,
|
|
5136
|
+
screenShareVideo: undefined,
|
|
5137
|
+
screenShareAudio: undefined,
|
|
5138
|
+
},
|
|
5139
|
+
direction: {
|
|
5140
|
+
audio: 'sendrecv',
|
|
5141
|
+
video: 'sendrecv',
|
|
5142
|
+
screenShare: 'recvonly',
|
|
5143
|
+
},
|
|
5144
|
+
remoteQualityLevel: 'HIGH',
|
|
5145
|
+
expectedDebugId,
|
|
5146
|
+
meetingId: meeting.id,
|
|
5147
|
+
});
|
|
5148
|
+
|
|
5149
|
+
// check that stats analyzer was created with the right config and store the reference to it so that we can later check that it was stopped
|
|
5150
|
+
assert.calledOnceWithExactly(
|
|
5151
|
+
mockStatsAnalyzerCtor,
|
|
5152
|
+
sinon.match({
|
|
5153
|
+
isMultistream: true,
|
|
5154
|
+
})
|
|
5155
|
+
);
|
|
5156
|
+
const initialStatsAnalyzer = mockStatsAnalyzerCtor.returnValues[0];
|
|
5157
|
+
mockStatsAnalyzerCtor.resetHistory();
|
|
5158
|
+
|
|
5159
|
+
// TURN discovery was done (if needed)
|
|
5160
|
+
if (turnServerInfo) {
|
|
5161
|
+
assert.notCalled(meeting.roap.doTurnDiscovery);
|
|
5162
|
+
} else {
|
|
5163
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false, false);
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
5167
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
5168
|
+
|
|
5169
|
+
await testUtils.flushPromises();
|
|
5170
|
+
|
|
5171
|
+
// at this point the meeting should have been downgraded to transcoded
|
|
5172
|
+
assert.equal(meeting.isMultistream, false);
|
|
5173
|
+
|
|
5174
|
+
// old stats analyzer stopped and new one created
|
|
5175
|
+
assert.calledOnce(initialStatsAnalyzer.stopAnalyzer);
|
|
5176
|
+
assert.calledOnceWithExactly(
|
|
5177
|
+
mockStatsAnalyzerCtor,
|
|
5178
|
+
sinon.match({
|
|
5179
|
+
isMultistream: false,
|
|
5180
|
+
})
|
|
5181
|
+
);
|
|
5182
|
+
|
|
5183
|
+
// and correct cleanup of other things should have been done
|
|
5184
|
+
assert.calledOnceWithExactly(meeting.closePeerConnections, false);
|
|
5185
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.unsetPeerConnection);
|
|
5186
|
+
assert.calledOnceWithExactly(
|
|
5187
|
+
meeting.locusMediaRequest.downgradeFromMultistreamToTranscoded
|
|
5188
|
+
);
|
|
5189
|
+
|
|
5190
|
+
// new connection should have been created
|
|
5191
|
+
checkMediaConnectionCreated({
|
|
5192
|
+
expectMultistream: false,
|
|
5193
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
5194
|
+
localStreams: {
|
|
5195
|
+
audio: fakeMicrophoneStream,
|
|
5196
|
+
video: undefined,
|
|
5197
|
+
screenShareVideo: undefined,
|
|
5198
|
+
screenShareAudio: undefined,
|
|
5199
|
+
},
|
|
5200
|
+
direction: {
|
|
5201
|
+
audio: 'sendrecv',
|
|
5202
|
+
video: 'sendrecv',
|
|
5203
|
+
screenShare: 'recvonly',
|
|
5204
|
+
},
|
|
5205
|
+
remoteQualityLevel: 'HIGH',
|
|
5206
|
+
expectedDebugId,
|
|
5207
|
+
meetingId: meeting.id,
|
|
5208
|
+
});
|
|
5209
|
+
|
|
5210
|
+
// and new TURN discovery done (no matter if it was being done before or not)
|
|
5211
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, true, true);
|
|
5212
|
+
|
|
5213
|
+
// simulate new offer
|
|
5214
|
+
await simulateRoapOffer(false);
|
|
5215
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
5216
|
+
|
|
5217
|
+
// overall there should have been 2 calls to locusMediaRequestStub, because 2 offers were sent
|
|
5218
|
+
assert.calledTwice(locusMediaRequestStub);
|
|
5219
|
+
|
|
5220
|
+
// simulate answer being processed correctly
|
|
5221
|
+
transcodedEventListeners[MediaConnectionEventNames.REMOTE_SDP_ANSWER_PROCESSED]();
|
|
5222
|
+
|
|
5223
|
+
// check that addMedia finally resolved
|
|
5224
|
+
await addMediaPromise;
|
|
5225
|
+
};
|
|
5226
|
+
|
|
5227
|
+
it('addMedia() falls back to transcoded if SDP answer is not from Homer', async () => {
|
|
5228
|
+
// call addMediaInternal like addMedia() does it
|
|
5229
|
+
await runCheck(undefined, false);
|
|
5230
|
+
});
|
|
5231
|
+
|
|
5232
|
+
it('addMediaInternal() correctly falls back to transcoded if SDP answer is not from Homer (joinWithMedia() case)', async () => {
|
|
5233
|
+
// call addMediaInternal the way joinWithMedia() does it - with TURN info already provided
|
|
5234
|
+
// and check that when we fallback to transcoded we still do another TURN discovery
|
|
5235
|
+
await runCheck(
|
|
5236
|
+
{
|
|
5237
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
5238
|
+
username: 'turn user',
|
|
5239
|
+
password: 'turn password',
|
|
5240
|
+
},
|
|
5241
|
+
false
|
|
5242
|
+
);
|
|
5243
|
+
});
|
|
5244
|
+
|
|
5245
|
+
it('addMediaInternal() correctly falls back to transcoded if SDP answer is not from Homer (joinWithMedia() retry case)', async () => {
|
|
5246
|
+
// call addMediaInternal the way joinWithMedia() does it when it does a retry - with TURN info already provided
|
|
5247
|
+
// but also with forceTurnDiscovery=true - this shouldn't affect the flow for fallback to transcoded in any way
|
|
5248
|
+
// but doing it just for completeness
|
|
5249
|
+
await runCheck(
|
|
5250
|
+
{
|
|
5251
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
5252
|
+
username: 'turn user',
|
|
5253
|
+
password: 'turn password',
|
|
5254
|
+
},
|
|
5255
|
+
true
|
|
5256
|
+
);
|
|
5257
|
+
});
|
|
5258
|
+
});
|
|
5259
|
+
}
|
|
4836
5260
|
})
|
|
4837
5261
|
);
|
|
4838
5262
|
|
|
@@ -4910,6 +5334,11 @@ describe('plugin-meetings', () => {
|
|
|
4910
5334
|
meeting.logger.error = sinon.stub().returns(true);
|
|
4911
5335
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
4912
5336
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
5337
|
+
meeting.stopTranscription = sinon.stub();
|
|
5338
|
+
meeting.transcription = {};
|
|
5339
|
+
|
|
5340
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
5341
|
+
webex.internal.llm.off = sinon.stub();
|
|
4913
5342
|
|
|
4914
5343
|
// A meeting needs to be joined to leave
|
|
4915
5344
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -4930,6 +5359,9 @@ describe('plugin-meetings', () => {
|
|
|
4930
5359
|
assert.calledOnce(meeting.closePeerConnections);
|
|
4931
5360
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
4932
5361
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
5362
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
5363
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
5364
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
4933
5365
|
});
|
|
4934
5366
|
|
|
4935
5367
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -5917,6 +6349,38 @@ describe('plugin-meetings', () => {
|
|
|
5917
6349
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
5918
6350
|
});
|
|
5919
6351
|
|
|
6352
|
+
it('handles meetingInfoProvider not reach JBH', async () => {
|
|
6353
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6354
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6355
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6356
|
+
fetchMeetingInfo: sinon
|
|
6357
|
+
.stub()
|
|
6358
|
+
.throws(new MeetingInfoV2JoinForbiddenError(403003, FAKE_MEETING_INFO)),
|
|
6359
|
+
};
|
|
6360
|
+
|
|
6361
|
+
await assert.isRejected(
|
|
6362
|
+
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6363
|
+
JoinForbiddenError
|
|
6364
|
+
);
|
|
6365
|
+
|
|
6366
|
+
assert.calledWith(
|
|
6367
|
+
meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
|
|
6368
|
+
FAKE_DESTINATION,
|
|
6369
|
+
FAKE_TYPE,
|
|
6370
|
+
null,
|
|
6371
|
+
null,
|
|
6372
|
+
undefined,
|
|
6373
|
+
'locus-id',
|
|
6374
|
+
{},
|
|
6375
|
+
{meetingId: meeting.id, sendCAevents: true}
|
|
6376
|
+
);
|
|
6377
|
+
|
|
6378
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6379
|
+
assert.equal(meeting.meetingInfoFailureCode, 403003);
|
|
6380
|
+
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH);
|
|
6381
|
+
assert.equal(meeting.requiredCaptcha, null);
|
|
6382
|
+
});
|
|
6383
|
+
|
|
5920
6384
|
it('handles meetingInfoProvider policy error', async () => {
|
|
5921
6385
|
meeting.destination = FAKE_DESTINATION;
|
|
5922
6386
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -6284,29 +6748,59 @@ describe('plugin-meetings', () => {
|
|
|
6284
6748
|
assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
|
|
6285
6749
|
});
|
|
6286
6750
|
|
|
6287
|
-
it('handles
|
|
6751
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
|
|
6288
6752
|
meeting.destination = FAKE_DESTINATION;
|
|
6289
6753
|
meeting.destinationType = FAKE_TYPE;
|
|
6290
6754
|
meeting.attrs.meetingInfoProvider = {
|
|
6291
6755
|
fetchMeetingInfo: sinon
|
|
6292
6756
|
.stub()
|
|
6293
|
-
.throws(
|
|
6294
|
-
new MeetingInfoV2WebinarRegistrationError(403021, FAKE_MEETING_INFO, 'a message')
|
|
6295
|
-
),
|
|
6757
|
+
.throws(new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')),
|
|
6296
6758
|
};
|
|
6297
6759
|
|
|
6298
|
-
await assert.isRejected(
|
|
6299
|
-
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6300
|
-
WebinarRegistrationError
|
|
6301
|
-
);
|
|
6760
|
+
await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
|
|
6302
6761
|
|
|
6303
6762
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6304
|
-
assert.equal(meeting.meetingInfoFailureCode, 403021);
|
|
6305
6763
|
assert.equal(
|
|
6306
6764
|
meeting.meetingInfoFailureReason,
|
|
6307
6765
|
MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
|
|
6308
6766
|
);
|
|
6309
6767
|
});
|
|
6768
|
+
|
|
6769
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need join with webcast', async () => {
|
|
6770
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6771
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6772
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6773
|
+
fetchMeetingInfo: sinon
|
|
6774
|
+
.stub()
|
|
6775
|
+
.throws(new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')),
|
|
6776
|
+
};
|
|
6777
|
+
|
|
6778
|
+
await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
|
|
6779
|
+
|
|
6780
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6781
|
+
assert.equal(
|
|
6782
|
+
meeting.meetingInfoFailureReason,
|
|
6783
|
+
MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST
|
|
6784
|
+
);
|
|
6785
|
+
});
|
|
6786
|
+
|
|
6787
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registrationId', async () => {
|
|
6788
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6789
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6790
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6791
|
+
fetchMeetingInfo: sinon
|
|
6792
|
+
.stub()
|
|
6793
|
+
.throws(new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')),
|
|
6794
|
+
};
|
|
6795
|
+
|
|
6796
|
+
await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
|
|
6797
|
+
|
|
6798
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6799
|
+
assert.equal(
|
|
6800
|
+
meeting.meetingInfoFailureReason,
|
|
6801
|
+
MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATION_ID
|
|
6802
|
+
);
|
|
6803
|
+
});
|
|
6310
6804
|
});
|
|
6311
6805
|
|
|
6312
6806
|
describe('#refreshPermissionToken', () => {
|
|
@@ -6364,7 +6858,8 @@ describe('plugin-meetings', () => {
|
|
|
6364
6858
|
'fake-installed-org-id',
|
|
6365
6859
|
'locus-id',
|
|
6366
6860
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
|
6367
|
-
{meetingId: meeting.id, sendCAevents: true}
|
|
6861
|
+
{meetingId: meeting.id, sendCAevents: true},
|
|
6862
|
+
null
|
|
6368
6863
|
);
|
|
6369
6864
|
assert.deepEqual(meeting.meetingInfo, {
|
|
6370
6865
|
...FAKE_MEETING_INFO,
|
|
@@ -6409,7 +6904,8 @@ describe('plugin-meetings', () => {
|
|
|
6409
6904
|
'fake-installed-org-id',
|
|
6410
6905
|
'locus-id',
|
|
6411
6906
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
|
6412
|
-
{meetingId: meeting.id, sendCAevents: true}
|
|
6907
|
+
{meetingId: meeting.id, sendCAevents: true},
|
|
6908
|
+
null
|
|
6413
6909
|
);
|
|
6414
6910
|
assert.deepEqual(meeting.meetingInfo, {
|
|
6415
6911
|
...FAKE_MEETING_INFO,
|
|
@@ -6463,7 +6959,8 @@ describe('plugin-meetings', () => {
|
|
|
6463
6959
|
extraParam1: 'value1',
|
|
6464
6960
|
permissionToken: FAKE_PERMISSION_TOKEN,
|
|
6465
6961
|
},
|
|
6466
|
-
{meetingId: meeting.id, sendCAevents: true}
|
|
6962
|
+
{meetingId: meeting.id, sendCAevents: true},
|
|
6963
|
+
null
|
|
6467
6964
|
);
|
|
6468
6965
|
assert.deepEqual(meeting.meetingInfo, {
|
|
6469
6966
|
...FAKE_MEETING_INFO,
|
|
@@ -6767,6 +7264,9 @@ describe('plugin-meetings', () => {
|
|
|
6767
7264
|
meeting.transcription = {};
|
|
6768
7265
|
meeting.stopTranscription = sinon.stub();
|
|
6769
7266
|
|
|
7267
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
7268
|
+
webex.internal.llm.off = sinon.stub();
|
|
7269
|
+
|
|
6770
7270
|
// A meeting needs to be joined to end
|
|
6771
7271
|
meeting.meetingState = 'ACTIVE';
|
|
6772
7272
|
meeting.state = 'JOINED';
|
|
@@ -6787,6 +7287,9 @@ describe('plugin-meetings', () => {
|
|
|
6787
7287
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
6788
7288
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
6789
7289
|
assert.calledOnce(meeting?.stopTranscription);
|
|
7290
|
+
|
|
7291
|
+
assert.called(meeting.annotation.deregisterEvents);
|
|
7292
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
6790
7293
|
});
|
|
6791
7294
|
});
|
|
6792
7295
|
|
|
@@ -7769,7 +8272,9 @@ describe('plugin-meetings', () => {
|
|
|
7769
8272
|
});
|
|
7770
8273
|
|
|
7771
8274
|
it('should collect ice candidates', () => {
|
|
7772
|
-
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
|
8275
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
|
8276
|
+
candidate: {candidate: 'candidate'},
|
|
8277
|
+
});
|
|
7773
8278
|
|
|
7774
8279
|
assert.equal(meeting.iceCandidatesCount, 1);
|
|
7775
8280
|
});
|
|
@@ -8075,10 +8580,10 @@ describe('plugin-meetings', () => {
|
|
|
8075
8580
|
meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
|
|
8076
8581
|
meeting.reconnectionManager = {
|
|
8077
8582
|
reconnect: sinon.stub().resolves(),
|
|
8078
|
-
resetReconnectionTimer: () => {}
|
|
8583
|
+
resetReconnectionTimer: () => {},
|
|
8079
8584
|
};
|
|
8080
8585
|
meeting.currentMediaStatus = {
|
|
8081
|
-
video: true
|
|
8586
|
+
video: true,
|
|
8082
8587
|
};
|
|
8083
8588
|
|
|
8084
8589
|
await mockFailedEvent();
|
|
@@ -8360,8 +8865,7 @@ describe('plugin-meetings', () => {
|
|
|
8360
8865
|
assert.calledWith(meeting.roapMessageReceived, fakeAnswer);
|
|
8361
8866
|
});
|
|
8362
8867
|
|
|
8363
|
-
|
|
8364
|
-
const fakeError = new Error('fake error');
|
|
8868
|
+
const runOfferSendingFailureTest = async (fakeError, canProceed, expectedErrorCode) => {
|
|
8365
8869
|
const clock = sinon.useFakeTimers();
|
|
8366
8870
|
sinon.spy(clock, 'clearTimeout');
|
|
8367
8871
|
meeting.deferSDPAnswer = {reject: sinon.stub()};
|
|
@@ -8399,19 +8903,31 @@ describe('plugin-meetings', () => {
|
|
|
8399
8903
|
assert.equal(meeting.sdpResponseTimer, undefined);
|
|
8400
8904
|
|
|
8401
8905
|
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
8402
|
-
clientErrorCode:
|
|
8906
|
+
clientErrorCode: expectedErrorCode,
|
|
8403
8907
|
});
|
|
8404
8908
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8405
8909
|
name: 'client.media-engine.remote-sdp-received',
|
|
8406
8910
|
payload: {
|
|
8407
|
-
canProceed
|
|
8408
|
-
errors: [{errorCode:
|
|
8911
|
+
canProceed,
|
|
8912
|
+
errors: [{errorCode: expectedErrorCode, fatal: true}],
|
|
8409
8913
|
},
|
|
8410
8914
|
options: {
|
|
8411
8915
|
meetingId: meeting.id,
|
|
8412
8916
|
rawError: fakeError,
|
|
8413
8917
|
},
|
|
8414
8918
|
});
|
|
8919
|
+
};
|
|
8920
|
+
|
|
8921
|
+
it('handles OFFER message correctly when request fails', async () => {
|
|
8922
|
+
const fakeError = new Error('fake error');
|
|
8923
|
+
|
|
8924
|
+
await runOfferSendingFailureTest(fakeError, false, 2007);
|
|
8925
|
+
});
|
|
8926
|
+
|
|
8927
|
+
it('handles OFFER message correctly when we get a non-homer answer', async () => {
|
|
8928
|
+
const fakeError = new MultistreamNotSupportedError();
|
|
8929
|
+
|
|
8930
|
+
await runOfferSendingFailureTest(fakeError, true, 2012);
|
|
8415
8931
|
});
|
|
8416
8932
|
|
|
8417
8933
|
it('handles ANSWER message correctly', () => {
|
|
@@ -8614,6 +9130,7 @@ describe('plugin-meetings', () => {
|
|
|
8614
9130
|
});
|
|
8615
9131
|
});
|
|
8616
9132
|
});
|
|
9133
|
+
|
|
8617
9134
|
describe('#setUpLocusInfoSelfListener', () => {
|
|
8618
9135
|
it('listens to the self unadmitted guest event', (done) => {
|
|
8619
9136
|
meeting.startKeepAlive = sinon.stub();
|
|
@@ -8629,6 +9146,13 @@ describe('plugin-meetings', () => {
|
|
|
8629
9146
|
{payload: test1}
|
|
8630
9147
|
);
|
|
8631
9148
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
9149
|
+
assert.calledOnceWithExactly(
|
|
9150
|
+
Metrics.sendBehavioralMetric,
|
|
9151
|
+
BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY,
|
|
9152
|
+
{
|
|
9153
|
+
correlation_id: meeting.correlationId,
|
|
9154
|
+
}
|
|
9155
|
+
);
|
|
8632
9156
|
done();
|
|
8633
9157
|
});
|
|
8634
9158
|
it('listens to the self admitted guest event', (done) => {
|
|
@@ -8650,6 +9174,13 @@ describe('plugin-meetings', () => {
|
|
|
8650
9174
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
8651
9175
|
assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
|
|
8652
9176
|
|
|
9177
|
+
assert.calledOnceWithExactly(
|
|
9178
|
+
Metrics.sendBehavioralMetric,
|
|
9179
|
+
BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY,
|
|
9180
|
+
{
|
|
9181
|
+
correlation_id: meeting.correlationId,
|
|
9182
|
+
}
|
|
9183
|
+
);
|
|
8653
9184
|
done();
|
|
8654
9185
|
});
|
|
8655
9186
|
|
|
@@ -8694,6 +9225,27 @@ describe('plugin-meetings', () => {
|
|
|
8694
9225
|
);
|
|
8695
9226
|
});
|
|
8696
9227
|
|
|
9228
|
+
it('listens to the brb state changed event', () => {
|
|
9229
|
+
const assertBrb = (enabled) => {
|
|
9230
|
+
meeting.brbState = createBrbState(meeting, false);
|
|
9231
|
+
meeting.locusInfo.emit(
|
|
9232
|
+
{function: 'test', file: 'test'},
|
|
9233
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
9234
|
+
{brb: {enabled}}
|
|
9235
|
+
);
|
|
9236
|
+
assert.calledWithExactly(
|
|
9237
|
+
TriggerProxy.trigger,
|
|
9238
|
+
meeting,
|
|
9239
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
9240
|
+
EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
|
|
9241
|
+
{payload: {brb: {enabled}}}
|
|
9242
|
+
);
|
|
9243
|
+
};
|
|
9244
|
+
|
|
9245
|
+
assertBrb(true);
|
|
9246
|
+
assertBrb(false);
|
|
9247
|
+
});
|
|
9248
|
+
|
|
8697
9249
|
it('listens to the interpretation changed event', () => {
|
|
8698
9250
|
meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
|
|
8699
9251
|
|
|
@@ -8982,6 +9534,8 @@ describe('plugin-meetings', () => {
|
|
|
8982
9534
|
});
|
|
8983
9535
|
|
|
8984
9536
|
it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
|
|
9537
|
+
meeting.webinar.updatePracticeSessionStatus = sinon.stub();
|
|
9538
|
+
|
|
8985
9539
|
const state = {example: 'value'};
|
|
8986
9540
|
|
|
8987
9541
|
await meeting.locusInfo.emitScoped(
|
|
@@ -8990,6 +9544,7 @@ describe('plugin-meetings', () => {
|
|
|
8990
9544
|
{state}
|
|
8991
9545
|
);
|
|
8992
9546
|
|
|
9547
|
+
assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
|
|
8993
9548
|
assert.calledWith(
|
|
8994
9549
|
TriggerProxy.trigger,
|
|
8995
9550
|
meeting,
|
|
@@ -9373,6 +9928,22 @@ describe('plugin-meetings', () => {
|
|
|
9373
9928
|
});
|
|
9374
9929
|
});
|
|
9375
9930
|
|
|
9931
|
+
describe('#emailInput', () => {
|
|
9932
|
+
it('should set the email input', () => {
|
|
9933
|
+
assert.notOk(meeting.emailInput);
|
|
9934
|
+
meeting.emailInput = 'current';
|
|
9935
|
+
assert.equal(meeting.emailInput, 'current');
|
|
9936
|
+
});
|
|
9937
|
+
});
|
|
9938
|
+
|
|
9939
|
+
describe('#userNameInput', () => {
|
|
9940
|
+
it('should set the user name input', () => {
|
|
9941
|
+
assert.notOk(meeting.userNameInput);
|
|
9942
|
+
meeting.userNameInput = 'current';
|
|
9943
|
+
assert.equal(meeting.userNameInput, 'current');
|
|
9944
|
+
});
|
|
9945
|
+
});
|
|
9946
|
+
|
|
9376
9947
|
describe('#setPermissionTokenPayload', () => {
|
|
9377
9948
|
let now;
|
|
9378
9949
|
let clock;
|
|
@@ -9472,15 +10043,44 @@ describe('plugin-meetings', () => {
|
|
|
9472
10043
|
describe('#closePeerConnections', () => {
|
|
9473
10044
|
it('should close the webrtc media connection, and return a promise', async () => {
|
|
9474
10045
|
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
|
9475
|
-
|
|
10046
|
+
const fakeWebrtcMediaConnection = {close: sinon.stub()};
|
|
10047
|
+
meeting.mediaProperties.webrtcMediaConnection = fakeWebrtcMediaConnection;
|
|
10048
|
+
|
|
10049
|
+
meeting.audio = {id: 'fakeAudioMuteState'};
|
|
10050
|
+
meeting.video = {id: 'fakeVideoMuteState'};
|
|
10051
|
+
|
|
9476
10052
|
const pcs = meeting.closePeerConnections();
|
|
9477
10053
|
|
|
9478
10054
|
assert.exists(pcs.then);
|
|
9479
10055
|
await pcs;
|
|
9480
|
-
assert.calledOnce(
|
|
10056
|
+
assert.calledOnce(fakeWebrtcMediaConnection.close);
|
|
9481
10057
|
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
|
10058
|
+
assert.equal(meeting.audio, null);
|
|
10059
|
+
assert.equal(meeting.video, null);
|
|
10060
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection, null);
|
|
10061
|
+
});
|
|
10062
|
+
|
|
10063
|
+
it('should close the webrtc media connection, but keep audio and video props unchanged if called with resetMuteStates=false', async () => {
|
|
10064
|
+
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
|
10065
|
+
const fakeWebrtcMediaConnection = {close: sinon.stub()};
|
|
10066
|
+
meeting.mediaProperties.webrtcMediaConnection = fakeWebrtcMediaConnection;
|
|
10067
|
+
|
|
10068
|
+
const fakeAudio = {id: 'fakeAudioMuteState'};
|
|
10069
|
+
const fakeVideo = {id: 'fakeVideoMuteState'};
|
|
10070
|
+
|
|
10071
|
+
meeting.audio = fakeAudio;
|
|
10072
|
+
meeting.video = fakeVideo;
|
|
10073
|
+
|
|
10074
|
+
await meeting.closePeerConnections(false);
|
|
10075
|
+
|
|
10076
|
+
assert.calledOnce(fakeWebrtcMediaConnection.close);
|
|
10077
|
+
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
|
10078
|
+
assert.equal(meeting.audio, fakeAudio);
|
|
10079
|
+
assert.equal(meeting.video, fakeVideo);
|
|
10080
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection, null);
|
|
9482
10081
|
});
|
|
9483
10082
|
});
|
|
10083
|
+
|
|
9484
10084
|
describe('#unsetPeerConnections', () => {
|
|
9485
10085
|
it('should unset the peer connections', () => {
|
|
9486
10086
|
meeting.mediaProperties.unsetPeerConnection = sinon.stub().returns(true);
|
|
@@ -10609,6 +11209,7 @@ describe('plugin-meetings', () => {
|
|
|
10609
11209
|
meeting.webex.internal.llm.on = sinon.stub();
|
|
10610
11210
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
10611
11211
|
meeting.processRelayEvent = sinon.stub();
|
|
11212
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
10612
11213
|
});
|
|
10613
11214
|
|
|
10614
11215
|
it('does not connect if the call is not joined yet', async () => {
|
|
@@ -10672,7 +11273,10 @@ describe('plugin-meetings', () => {
|
|
|
10672
11273
|
|
|
10673
11274
|
const result = await meeting.updateLLMConnection();
|
|
10674
11275
|
|
|
10675
|
-
assert.calledWith(webex.internal.llm.disconnectLLM
|
|
11276
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
11277
|
+
code: 3050,
|
|
11278
|
+
reason: 'done (permanent)',
|
|
11279
|
+
});
|
|
10676
11280
|
assert.calledWith(
|
|
10677
11281
|
webex.internal.llm.registerAndConnect,
|
|
10678
11282
|
'a different url',
|
|
@@ -10702,7 +11306,10 @@ describe('plugin-meetings', () => {
|
|
|
10702
11306
|
|
|
10703
11307
|
const result = await meeting.updateLLMConnection();
|
|
10704
11308
|
|
|
10705
|
-
assert.calledWith(webex.internal.llm.disconnectLLM
|
|
11309
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
11310
|
+
code: 3050,
|
|
11311
|
+
reason: 'done (permanent)',
|
|
11312
|
+
});
|
|
10706
11313
|
assert.calledWith(
|
|
10707
11314
|
webex.internal.llm.registerAndConnect,
|
|
10708
11315
|
'a url',
|
|
@@ -10731,7 +11338,7 @@ describe('plugin-meetings', () => {
|
|
|
10731
11338
|
|
|
10732
11339
|
const result = await meeting.updateLLMConnection();
|
|
10733
11340
|
|
|
10734
|
-
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
11341
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, undefined);
|
|
10735
11342
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
10736
11343
|
assert.equal(result, undefined);
|
|
10737
11344
|
assert.calledOnceWithExactly(
|
|
@@ -10740,6 +11347,22 @@ describe('plugin-meetings', () => {
|
|
|
10740
11347
|
meeting.processRelayEvent
|
|
10741
11348
|
);
|
|
10742
11349
|
});
|
|
11350
|
+
|
|
11351
|
+
it('connect ps data channel if ps started in webinar', async () => {
|
|
11352
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
11353
|
+
meeting.locusInfo = {
|
|
11354
|
+
url: 'a url',
|
|
11355
|
+
info: {
|
|
11356
|
+
datachannelUrl: 'a datachannel url',
|
|
11357
|
+
practiceSessionDatachannelUrl: 'a ps datachannel url',
|
|
11358
|
+
},
|
|
11359
|
+
};
|
|
11360
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
|
|
11361
|
+
await meeting.updateLLMConnection();
|
|
11362
|
+
|
|
11363
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
11364
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
|
|
11365
|
+
});
|
|
10743
11366
|
});
|
|
10744
11367
|
|
|
10745
11368
|
describe('#setLocus', () => {
|
|
@@ -10931,6 +11554,7 @@ describe('plugin-meetings', () => {
|
|
|
10931
11554
|
beforeEach(() => {
|
|
10932
11555
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
10933
11556
|
meeting.deviceUrl = 'my-web-url';
|
|
11557
|
+
meeting.locusInfo.info = {isWebinar: false};
|
|
10934
11558
|
});
|
|
10935
11559
|
|
|
10936
11560
|
const USER_IDS = {
|
|
@@ -11156,13 +11780,29 @@ describe('plugin-meetings', () => {
|
|
|
11156
11780
|
|
|
11157
11781
|
activeSharingId.whiteboard = beneficiaryId;
|
|
11158
11782
|
|
|
11159
|
-
eventTrigger.share.push(
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11783
|
+
eventTrigger.share.push(
|
|
11784
|
+
meeting.webinar.selfIsAttendee
|
|
11785
|
+
? {
|
|
11786
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11787
|
+
functionName: 'remoteShare',
|
|
11788
|
+
eventPayload: {
|
|
11789
|
+
memberId: null,
|
|
11790
|
+
url,
|
|
11791
|
+
shareInstanceId,
|
|
11792
|
+
annotationInfo: undefined,
|
|
11793
|
+
resourceType: undefined,
|
|
11794
|
+
},
|
|
11795
|
+
}
|
|
11796
|
+
: {
|
|
11797
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11798
|
+
functionName: 'startWhiteboardShare',
|
|
11799
|
+
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11800
|
+
}
|
|
11801
|
+
);
|
|
11164
11802
|
|
|
11165
|
-
shareStatus =
|
|
11803
|
+
shareStatus = meeting.webinar.selfIsAttendee
|
|
11804
|
+
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
|
11805
|
+
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11166
11806
|
}
|
|
11167
11807
|
|
|
11168
11808
|
if (eventTrigger.member) {
|
|
@@ -11194,13 +11834,29 @@ describe('plugin-meetings', () => {
|
|
|
11194
11834
|
newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
|
|
11195
11835
|
newPayload.current.content.beneficiaryId = otherBeneficiaryId;
|
|
11196
11836
|
|
|
11197
|
-
eventTrigger.share.push(
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11837
|
+
eventTrigger.share.push(
|
|
11838
|
+
meeting.webinar.selfIsAttendee
|
|
11839
|
+
? {
|
|
11840
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11841
|
+
functionName: 'remoteShare',
|
|
11842
|
+
eventPayload: {
|
|
11843
|
+
memberId: null,
|
|
11844
|
+
url,
|
|
11845
|
+
shareInstanceId,
|
|
11846
|
+
annotationInfo: undefined,
|
|
11847
|
+
resourceType: undefined,
|
|
11848
|
+
},
|
|
11849
|
+
}
|
|
11850
|
+
: {
|
|
11851
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11852
|
+
functionName: 'startWhiteboardShare',
|
|
11853
|
+
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11854
|
+
}
|
|
11855
|
+
);
|
|
11202
11856
|
|
|
11203
|
-
shareStatus =
|
|
11857
|
+
shareStatus = meeting.webinar.selfIsAttendee
|
|
11858
|
+
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
|
11859
|
+
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11204
11860
|
} else {
|
|
11205
11861
|
eventTrigger.share.push({
|
|
11206
11862
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
|
|
@@ -11327,6 +11983,37 @@ describe('plugin-meetings', () => {
|
|
|
11327
11983
|
assert.exists(meeting.setUpLocusMediaSharesListener);
|
|
11328
11984
|
});
|
|
11329
11985
|
|
|
11986
|
+
describe('Whiteboard Share - Webinar Attendee', () => {
|
|
11987
|
+
it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
|
|
11988
|
+
// Set the webinar attendee flag
|
|
11989
|
+
meeting.webinar = {selfIsAttendee: true};
|
|
11990
|
+
meeting.locusInfo.info.isWebinar = true;
|
|
11991
|
+
|
|
11992
|
+
// Step 1: Start sharing whiteboard A
|
|
11993
|
+
const data1 = generateData(
|
|
11994
|
+
blankPayload, // Initial payload
|
|
11995
|
+
true, // isGranting: Granting share
|
|
11996
|
+
false, // isContent: Whiteboard (not content)
|
|
11997
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
|
11998
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
|
11999
|
+
);
|
|
12000
|
+
|
|
12001
|
+
// Step 2: Stop sharing whiteboard A
|
|
12002
|
+
const data2 = generateData(
|
|
12003
|
+
data1.payload, // Updated payload from Step 1
|
|
12004
|
+
false, // isGranting: Stopping share
|
|
12005
|
+
false, // isContent: Whiteboard
|
|
12006
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
|
12007
|
+
);
|
|
12008
|
+
|
|
12009
|
+
// Validate the payload changes and status updates
|
|
12010
|
+
payloadTestHelper([data1]);
|
|
12011
|
+
|
|
12012
|
+
// Specific assertions for webinar attendee status
|
|
12013
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
|
12014
|
+
});
|
|
12015
|
+
});
|
|
12016
|
+
|
|
11330
12017
|
describe('Whiteboard A --> Whiteboard B', () => {
|
|
11331
12018
|
it('Scenario #1: you share both whiteboards', () => {
|
|
11332
12019
|
const data1 = generateData(
|
|
@@ -12002,9 +12689,12 @@ describe('plugin-meetings', () => {
|
|
|
12002
12689
|
it('startKeepAlive starts the keep alive', async () => {
|
|
12003
12690
|
meeting.meetingRequest.keepAlive = sinon.stub().returns(Promise.resolve());
|
|
12004
12691
|
|
|
12692
|
+
const keepAliveUrl1 = 'keep.alive.url1';
|
|
12693
|
+
const keepAliveUrl2 = 'keep.alive.url2';
|
|
12694
|
+
|
|
12005
12695
|
assert.isNull(meeting.keepAliveTimerId);
|
|
12006
12696
|
meeting.joinedWith = {
|
|
12007
|
-
keepAliveUrl:
|
|
12697
|
+
keepAliveUrl: keepAliveUrl1,
|
|
12008
12698
|
keepAliveSecs: defaultKeepAliveSecs,
|
|
12009
12699
|
};
|
|
12010
12700
|
meeting.startKeepAlive();
|
|
@@ -12013,12 +12703,15 @@ describe('plugin-meetings', () => {
|
|
|
12013
12703
|
assert.notCalled(meeting.meetingRequest.keepAlive);
|
|
12014
12704
|
await progressTime(defaultExpectedInterval);
|
|
12015
12705
|
assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
|
|
12016
|
-
keepAliveUrl:
|
|
12706
|
+
keepAliveUrl: keepAliveUrl1,
|
|
12017
12707
|
});
|
|
12708
|
+
// joinedWith keep alive url might change (when we fallback from multistream to transcoded)
|
|
12709
|
+
meeting.joinedWith.keepAliveUrl = keepAliveUrl2;
|
|
12710
|
+
|
|
12018
12711
|
await progressTime(defaultExpectedInterval);
|
|
12019
12712
|
assert.calledTwice(meeting.meetingRequest.keepAlive);
|
|
12020
|
-
assert.
|
|
12021
|
-
keepAliveUrl:
|
|
12713
|
+
assert.calledWith(meeting.meetingRequest.keepAlive, {
|
|
12714
|
+
keepAliveUrl: keepAliveUrl2,
|
|
12022
12715
|
});
|
|
12023
12716
|
});
|
|
12024
12717
|
it('startKeepAlive handles existing keepAliveTimerId', async () => {
|
|
@@ -12599,7 +13292,7 @@ describe('plugin-meetings', () => {
|
|
|
12599
13292
|
|
|
12600
13293
|
describe('#roapMessageReceived', () => {
|
|
12601
13294
|
it('calls roapMessageReceived on the webrtc media connection', () => {
|
|
12602
|
-
const fakeMessage = {messageType: '
|
|
13295
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
|
12603
13296
|
|
|
12604
13297
|
const getMediaServer = sinon.stub(MeetingsUtil, 'getMediaServer').returns('homer');
|
|
12605
13298
|
|
|
@@ -12616,5 +13309,96 @@ describe('plugin-meetings', () => {
|
|
|
12616
13309
|
assert.calledOnceWithExactly(getMediaServer, 'fake sdp');
|
|
12617
13310
|
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'homer');
|
|
12618
13311
|
});
|
|
13312
|
+
|
|
13313
|
+
it('throws MultistreamNotSupportedError if we get a non-homer SDP answer', async () => {
|
|
13314
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
|
13315
|
+
|
|
13316
|
+
meeting.isMultistream = true;
|
|
13317
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
13318
|
+
roapMessageReceived: sinon.stub(),
|
|
13319
|
+
};
|
|
13320
|
+
|
|
13321
|
+
sinon.stub(MeetingsUtil, 'getMediaServer').returns('linus');
|
|
13322
|
+
|
|
13323
|
+
try {
|
|
13324
|
+
await meeting.roapMessageReceived(fakeMessage);
|
|
13325
|
+
|
|
13326
|
+
assert.fail('Expected MultistreamNotSupportedError to be thrown');
|
|
13327
|
+
} catch (e) {
|
|
13328
|
+
assert.isTrue(e instanceof MultistreamNotSupportedError);
|
|
13329
|
+
}
|
|
13330
|
+
|
|
13331
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived);
|
|
13332
|
+
});
|
|
13333
|
+
|
|
13334
|
+
it('does not call getMediaServer for a roap message other than ANSWER', async () => {
|
|
13335
|
+
const fakeMessage = {messageType: 'ERROR', sdp: 'fake sdp'};
|
|
13336
|
+
|
|
13337
|
+
meeting.isMultistream = true;
|
|
13338
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
13339
|
+
roapMessageReceived: sinon.stub(),
|
|
13340
|
+
};
|
|
13341
|
+
meeting.mediaProperties.webrtcMediaConnection.mediaServer = 'linus';
|
|
13342
|
+
|
|
13343
|
+
const getMediaServerStub = sinon.stub(MeetingsUtil, 'getMediaServer').returns('something');
|
|
13344
|
+
|
|
13345
|
+
meeting.roapMessageReceived(fakeMessage);
|
|
13346
|
+
|
|
13347
|
+
assert.calledOnceWithExactly(
|
|
13348
|
+
meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived,
|
|
13349
|
+
fakeMessage
|
|
13350
|
+
);
|
|
13351
|
+
assert.notCalled(getMediaServerStub);
|
|
13352
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'linus'); // check that it hasn't been overwritten
|
|
13353
|
+
});
|
|
13354
|
+
});
|
|
13355
|
+
|
|
13356
|
+
describe('#verifyRegistrationId', () => {
|
|
13357
|
+
it('calls fetchMeetingInfo() with the passed registrationId and captcha code', async () => {
|
|
13358
|
+
// simulate successful case
|
|
13359
|
+
meeting.fetchMeetingInfo = sinon.stub().resolves();
|
|
13360
|
+
const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
|
|
13361
|
+
|
|
13362
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
13363
|
+
assert.calledWith(
|
|
13364
|
+
Metrics.sendBehavioralMetric,
|
|
13365
|
+
BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_SUCCESS
|
|
13366
|
+
);
|
|
13367
|
+
assert.equal(result.isRegistrationIdValid, true);
|
|
13368
|
+
assert.equal(result.requiredCaptcha, null);
|
|
13369
|
+
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
13370
|
+
assert.calledWith(meeting.fetchMeetingInfo, {
|
|
13371
|
+
registrationId: 'registrationId',
|
|
13372
|
+
captchaCode: 'captcha id',
|
|
13373
|
+
sendCAevents: false,
|
|
13374
|
+
});
|
|
13375
|
+
});
|
|
13376
|
+
it('handles registrationIdError returned by fetchMeetingInfo', async () => {
|
|
13377
|
+
meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
|
|
13378
|
+
meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATIONID;
|
|
13379
|
+
|
|
13380
|
+
return Promise.reject(new JoinWebinarError());
|
|
13381
|
+
});
|
|
13382
|
+
const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
|
|
13383
|
+
|
|
13384
|
+
assert.equal(result.isRegistrationIdValid, false);
|
|
13385
|
+
assert.equal(result.requiredCaptcha, null);
|
|
13386
|
+
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID);
|
|
13387
|
+
});
|
|
13388
|
+
it('handles CaptchaError returned by fetchMeetingInfo', async () => {
|
|
13389
|
+
const FAKE_CAPTCHA = {captchaId: 'some catcha id...'};
|
|
13390
|
+
|
|
13391
|
+
meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
|
|
13392
|
+
meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA;
|
|
13393
|
+
meeting.requiredCaptcha = FAKE_CAPTCHA;
|
|
13394
|
+
|
|
13395
|
+
return Promise.reject(new CaptchaError());
|
|
13396
|
+
});
|
|
13397
|
+
const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
|
|
13398
|
+
|
|
13399
|
+
assert.equal(result.isRegistrationIdValid, false);
|
|
13400
|
+
assert.deepEqual(result.requiredCaptcha, FAKE_CAPTCHA);
|
|
13401
|
+
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
|
|
13402
|
+
});
|
|
12619
13403
|
});
|
|
12620
13404
|
});
|