@webex/plugin-meetings 3.3.1 → 3.4.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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +7 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/constants.js +11 -4
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/selfUtils.js +0 -5
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +70 -15
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/index.js +18 -9
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +67 -0
- package/dist/meeting/connectionStateHandler.js.map +1 -0
- package/dist/meeting/index.js +576 -374
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +7 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +6 -1
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/index.js +4 -4
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +2 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/util.js +17 -17
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +16 -16
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +1 -1
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +41 -35
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +8 -0
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/util.js +3 -2
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +57 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +1 -1
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/reachability/clusterReachability.js +108 -53
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +546 -115
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +1 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/rtcMetrics/index.js +26 -6
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/types/constants.d.ts +11 -3
- package/dist/types/media/MediaConnectionAwaiter.d.ts +24 -4
- package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
- package/dist/types/meeting/index.d.ts +28 -8
- package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
- package/dist/types/meeting-info/index.d.ts +3 -2
- package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -2
- package/dist/types/meeting-info/util.d.ts +5 -4
- package/dist/types/meeting-info/utilv2.d.ts +3 -2
- package/dist/types/meetings/collection.d.ts +3 -2
- package/dist/types/meetings/index.d.ts +6 -4
- package/dist/types/meetings/meetings.types.d.ts +9 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/metrics/index.d.ts +15 -0
- package/dist/types/reachability/clusterReachability.d.ts +31 -3
- package/dist/types/reachability/index.d.ts +107 -4
- package/dist/types/rtcMetrics/index.d.ts +11 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -23
- package/src/breakouts/index.ts +7 -1
- package/src/constants.ts +13 -17
- package/src/locus-info/selfUtils.ts +0 -5
- package/src/media/MediaConnectionAwaiter.ts +89 -14
- package/src/media/index.ts +18 -9
- package/src/meeting/connectionStateHandler.ts +65 -0
- package/src/meeting/index.ts +541 -298
- package/src/meeting/locusMediaRequest.ts +5 -0
- package/src/meeting/muteState.ts +6 -1
- package/src/meeting/util.ts +1 -0
- package/src/meeting-info/index.ts +9 -6
- package/src/meeting-info/meeting-info-v2.ts +4 -4
- package/src/meeting-info/util.ts +23 -28
- package/src/meeting-info/utilv2.ts +18 -24
- package/src/meetings/collection.ts +3 -3
- package/src/meetings/index.ts +43 -43
- package/src/meetings/meetings.types.ts +11 -0
- package/src/meetings/util.ts +5 -4
- package/src/metrics/constants.ts +1 -0
- package/src/metrics/index.ts +44 -0
- package/src/personal-meeting-room/index.ts +2 -2
- package/src/reachability/clusterReachability.ts +86 -25
- package/src/reachability/index.ts +364 -30
- package/src/reconnection-manager/index.ts +1 -1
- package/src/rtcMetrics/index.ts +25 -5
- package/test/unit/spec/breakouts/index.ts +51 -32
- package/test/unit/spec/locus-info/selfUtils.js +25 -23
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
- package/test/unit/spec/media/index.ts +75 -34
- package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
- package/test/unit/spec/meeting/index.js +807 -185
- package/test/unit/spec/meeting/locusMediaRequest.ts +7 -0
- package/test/unit/spec/meeting/muteState.js +24 -0
- package/test/unit/spec/meeting-info/index.js +4 -4
- package/test/unit/spec/meeting-info/meetinginfov2.js +24 -28
- package/test/unit/spec/meeting-info/request.js +2 -2
- package/test/unit/spec/meeting-info/utilv2.js +41 -49
- package/test/unit/spec/meetings/index.js +44 -3
- package/test/unit/spec/metrics/index.js +126 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -2
- package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -2
- package/test/unit/spec/reachability/clusterReachability.ts +116 -22
- package/test/unit/spec/reachability/index.ts +1398 -131
- package/test/unit/spec/rtcMetrics/index.ts +32 -0
- package/dist/mediaQualityMetrics/config.js +0 -321
- package/dist/mediaQualityMetrics/config.js.map +0 -1
- package/dist/networkQualityMonitor/index.js +0 -227
- package/dist/networkQualityMonitor/index.js.map +0 -1
- package/dist/statsAnalyzer/global.js +0 -44
- package/dist/statsAnalyzer/global.js.map +0 -1
- package/dist/statsAnalyzer/index.js +0 -1072
- package/dist/statsAnalyzer/index.js.map +0 -1
- package/dist/statsAnalyzer/mqaUtil.js +0 -368
- package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
- package/dist/types/mediaQualityMetrics/config.d.ts +0 -247
- package/dist/types/networkQualityMonitor/index.d.ts +0 -70
- package/dist/types/statsAnalyzer/global.d.ts +0 -36
- package/dist/types/statsAnalyzer/index.d.ts +0 -217
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
- package/src/mediaQualityMetrics/config.ts +0 -255
- package/src/networkQualityMonitor/index.ts +0 -211
- package/src/statsAnalyzer/global.ts +0 -37
- package/src/statsAnalyzer/index.ts +0 -1318
- package/src/statsAnalyzer/mqaUtil.ts +0 -463
- package/test/unit/spec/networkQualityMonitor/index.js +0 -99
- package/test/unit/spec/stats-analyzer/index.js +0 -1819
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
import 'jsdom-global/register';
|
|
5
5
|
import {cloneDeep, forEach, isEqual, isUndefined} from 'lodash';
|
|
6
6
|
import sinon from 'sinon';
|
|
7
|
-
import * as
|
|
7
|
+
import * as InternalMediaCoreModule from '@webex/internal-media-core';
|
|
8
|
+
import * as RtcMetricsModule from '@webex/plugin-meetings/src/rtcMetrics';
|
|
9
|
+
import * as RemoteMediaManagerModule from '@webex/plugin-meetings/src/multistream/remoteMediaManager';
|
|
8
10
|
import StateMachine from 'javascript-state-machine';
|
|
9
11
|
import uuid from 'uuid';
|
|
10
12
|
import {assert, expect} from '@webex/test-helper-chai';
|
|
11
|
-
import {Credentials,
|
|
13
|
+
import {Credentials, WebexPlugin} from '@webex/webex-core';
|
|
12
14
|
import Support from '@webex/internal-plugin-support';
|
|
13
15
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
14
16
|
import StaticConfig from '@webex/plugin-meetings/src/common/config';
|
|
@@ -21,31 +23,28 @@ import {
|
|
|
21
23
|
PASSWORD_STATUS,
|
|
22
24
|
EVENTS,
|
|
23
25
|
EVENT_TRIGGERS,
|
|
24
|
-
|
|
25
|
-
_MEETING_ID_,
|
|
26
|
+
DESTINATION_TYPE,
|
|
26
27
|
MEETING_REMOVED_REASON,
|
|
27
28
|
LOCUSINFO,
|
|
28
29
|
ICE_AND_DTLS_CONNECTION_TIMEOUT,
|
|
29
30
|
DISPLAY_HINTS,
|
|
30
31
|
SELF_POLICY,
|
|
31
32
|
IP_VERSION,
|
|
32
|
-
ERROR_DICTIONARY,
|
|
33
33
|
NETWORK_STATUS,
|
|
34
34
|
ONLINE,
|
|
35
35
|
OFFLINE,
|
|
36
|
-
|
|
36
|
+
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
37
37
|
} from '@webex/plugin-meetings/src/constants';
|
|
38
|
-
import * as InternalMediaCoreModule from '@webex/internal-media-core';
|
|
39
38
|
import {
|
|
40
39
|
ConnectionState,
|
|
41
|
-
|
|
40
|
+
MediaConnectionEventNames,
|
|
41
|
+
StatsAnalyzerEventNames,
|
|
42
42
|
Errors,
|
|
43
43
|
ErrorType,
|
|
44
44
|
RemoteTrackType,
|
|
45
45
|
MediaType,
|
|
46
46
|
} from '@webex/internal-media-core';
|
|
47
47
|
import {LocalStreamEventNames} from '@webex/media-helpers';
|
|
48
|
-
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
49
48
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
50
49
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
51
50
|
import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
@@ -72,6 +71,7 @@ import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaR
|
|
|
72
71
|
import * as ReceiveSlotManagerModule from '@webex/plugin-meetings/src/multistream/receiveSlotManager';
|
|
73
72
|
import * as SendSlotManagerModule from '@webex/plugin-meetings/src/multistream/sendSlotManager';
|
|
74
73
|
import {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';
|
|
74
|
+
import * as LocusMediaRequestModule from '@webex/plugin-meetings/src/meeting/locusMediaRequest';
|
|
75
75
|
|
|
76
76
|
import CallDiagnosticLatencies from '@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics-latencies';
|
|
77
77
|
import LLM from '@webex/internal-plugin-llm';
|
|
@@ -102,6 +102,7 @@ import {
|
|
|
102
102
|
import {
|
|
103
103
|
DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
|
|
104
104
|
ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE,
|
|
105
|
+
ICE_AND_REACHABILITY_FAILED_CLIENT_CODE,
|
|
105
106
|
ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
|
|
106
107
|
ICE_FAILURE_CLIENT_CODE,
|
|
107
108
|
MISSING_ROAP_ANSWER_CLIENT_CODE,
|
|
@@ -279,7 +280,7 @@ describe('plugin-meetings', () => {
|
|
|
279
280
|
deviceUrl: uuid3,
|
|
280
281
|
locus: {url: url1},
|
|
281
282
|
destination: testDestination,
|
|
282
|
-
destinationType:
|
|
283
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
283
284
|
correlationId,
|
|
284
285
|
selfId: uuid1,
|
|
285
286
|
},
|
|
@@ -347,7 +348,7 @@ describe('plugin-meetings', () => {
|
|
|
347
348
|
assert.equal(meeting.requiredCaptcha, null);
|
|
348
349
|
assert.equal(meeting.meetingInfoFailureReason, undefined);
|
|
349
350
|
assert.equal(meeting.destination, testDestination);
|
|
350
|
-
assert.equal(meeting.destinationType,
|
|
351
|
+
assert.equal(meeting.destinationType, DESTINATION_TYPE.MEETING_ID);
|
|
351
352
|
assert.instanceOf(meeting.breakouts, Breakouts);
|
|
352
353
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
353
354
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
@@ -367,7 +368,7 @@ describe('plugin-meetings', () => {
|
|
|
367
368
|
deviceUrl: uuid3,
|
|
368
369
|
locus: {url: url1},
|
|
369
370
|
destination: testDestination,
|
|
370
|
-
destinationType:
|
|
371
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
371
372
|
},
|
|
372
373
|
{
|
|
373
374
|
parent: webex,
|
|
@@ -385,7 +386,7 @@ describe('plugin-meetings', () => {
|
|
|
385
386
|
deviceUrl: uuid3,
|
|
386
387
|
locus: {url: url1},
|
|
387
388
|
destination: testDestination,
|
|
388
|
-
destinationType:
|
|
389
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
389
390
|
callStateForMetrics: {
|
|
390
391
|
correlationId: uuid4,
|
|
391
392
|
joinTrigger: 'fake-join-trigger',
|
|
@@ -426,7 +427,7 @@ describe('plugin-meetings', () => {
|
|
|
426
427
|
deviceUrl: uuid3,
|
|
427
428
|
locus: {url: url1},
|
|
428
429
|
destination: testDestination,
|
|
429
|
-
destinationType:
|
|
430
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
430
431
|
},
|
|
431
432
|
{
|
|
432
433
|
parent: webex,
|
|
@@ -502,7 +503,7 @@ describe('plugin-meetings', () => {
|
|
|
502
503
|
deviceUrl: uuid3,
|
|
503
504
|
locus: {url: url1},
|
|
504
505
|
destination: testDestination,
|
|
505
|
-
destinationType:
|
|
506
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
506
507
|
},
|
|
507
508
|
{
|
|
508
509
|
parent: webex,
|
|
@@ -622,10 +623,13 @@ describe('plugin-meetings', () => {
|
|
|
622
623
|
let generateTurnDiscoveryRequestMessageStub;
|
|
623
624
|
let handleTurnDiscoveryHttpResponseStub;
|
|
624
625
|
let abortTurnDiscoveryStub;
|
|
626
|
+
let addMediaInternalStub;
|
|
625
627
|
|
|
626
628
|
beforeEach(() => {
|
|
627
629
|
meeting.join = sinon.stub().returns(Promise.resolve(fakeJoinResult));
|
|
628
|
-
|
|
630
|
+
addMediaInternalStub = sinon
|
|
631
|
+
.stub(meeting, 'addMediaInternal')
|
|
632
|
+
.returns(Promise.resolve(test4));
|
|
629
633
|
|
|
630
634
|
webex.meetings.reachability.getReachabilityResults.resolves(fakeReachabilityResults);
|
|
631
635
|
|
|
@@ -644,7 +648,7 @@ describe('plugin-meetings', () => {
|
|
|
644
648
|
mediaOptions,
|
|
645
649
|
});
|
|
646
650
|
|
|
647
|
-
// check that TURN discovery is done with join and
|
|
651
|
+
// check that TURN discovery is done with join and addMediaInternal() called
|
|
648
652
|
assert.calledOnceWithExactly(meeting.join, {
|
|
649
653
|
...joinOptions,
|
|
650
654
|
roapMessage: fakeRoapMessage,
|
|
@@ -656,12 +660,21 @@ describe('plugin-meetings', () => {
|
|
|
656
660
|
meeting,
|
|
657
661
|
fakeJoinResult
|
|
658
662
|
);
|
|
659
|
-
assert.calledOnceWithExactly(
|
|
663
|
+
assert.calledOnceWithExactly(
|
|
664
|
+
meeting.addMediaInternal,
|
|
665
|
+
sinon.match.any,
|
|
666
|
+
fakeTurnServerInfo,
|
|
667
|
+
false,
|
|
668
|
+
mediaOptions
|
|
669
|
+
);
|
|
660
670
|
|
|
661
671
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
662
672
|
|
|
663
673
|
// resets joinWithMediaRetryInfo
|
|
664
|
-
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
674
|
+
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
675
|
+
isRetry: false,
|
|
676
|
+
prevJoinResponse: undefined,
|
|
677
|
+
});
|
|
665
678
|
});
|
|
666
679
|
|
|
667
680
|
it("should not call handleTurnDiscoveryHttpResponse if we don't send a TURN discovery request with join", async () => {
|
|
@@ -672,7 +685,7 @@ describe('plugin-meetings', () => {
|
|
|
672
685
|
mediaOptions,
|
|
673
686
|
});
|
|
674
687
|
|
|
675
|
-
// check that TURN discovery is done with join and
|
|
688
|
+
// check that TURN discovery is done with join and addMediaInternal() called
|
|
676
689
|
assert.calledOnceWithExactly(meeting.join, {
|
|
677
690
|
...joinOptions,
|
|
678
691
|
roapMessage: undefined,
|
|
@@ -681,7 +694,13 @@ describe('plugin-meetings', () => {
|
|
|
681
694
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
682
695
|
assert.notCalled(handleTurnDiscoveryHttpResponseStub);
|
|
683
696
|
assert.notCalled(abortTurnDiscoveryStub);
|
|
684
|
-
assert.calledOnceWithExactly(
|
|
697
|
+
assert.calledOnceWithExactly(
|
|
698
|
+
meeting.addMediaInternal,
|
|
699
|
+
sinon.match.any,
|
|
700
|
+
undefined,
|
|
701
|
+
false,
|
|
702
|
+
mediaOptions
|
|
703
|
+
);
|
|
685
704
|
|
|
686
705
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
687
706
|
assert.equal(meeting.turnServerUsed, false);
|
|
@@ -698,7 +717,7 @@ describe('plugin-meetings', () => {
|
|
|
698
717
|
mediaOptions,
|
|
699
718
|
});
|
|
700
719
|
|
|
701
|
-
// check that TURN discovery is done with join and
|
|
720
|
+
// check that TURN discovery is done with join and addMediaInternal() called
|
|
702
721
|
assert.calledOnceWithExactly(meeting.join, {
|
|
703
722
|
...joinOptions,
|
|
704
723
|
roapMessage: fakeRoapMessage,
|
|
@@ -711,7 +730,13 @@ describe('plugin-meetings', () => {
|
|
|
711
730
|
fakeJoinResult
|
|
712
731
|
);
|
|
713
732
|
assert.calledOnceWithExactly(abortTurnDiscoveryStub);
|
|
714
|
-
assert.calledOnceWithExactly(
|
|
733
|
+
assert.calledOnceWithExactly(
|
|
734
|
+
meeting.addMediaInternal,
|
|
735
|
+
sinon.match.any,
|
|
736
|
+
undefined,
|
|
737
|
+
false,
|
|
738
|
+
mediaOptions
|
|
739
|
+
);
|
|
715
740
|
|
|
716
741
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
717
742
|
});
|
|
@@ -758,12 +783,20 @@ describe('plugin-meetings', () => {
|
|
|
758
783
|
);
|
|
759
784
|
|
|
760
785
|
// resets joinWithMediaRetryInfo
|
|
761
|
-
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
786
|
+
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
787
|
+
isRetry: false,
|
|
788
|
+
prevJoinResponse: undefined,
|
|
789
|
+
});
|
|
762
790
|
});
|
|
763
791
|
|
|
764
792
|
it('should resolve if join() fails the first time but succeeds the second time', async () => {
|
|
765
793
|
const error = new Error('fake');
|
|
766
|
-
meeting.join = sinon
|
|
794
|
+
meeting.join = sinon
|
|
795
|
+
.stub()
|
|
796
|
+
.onFirstCall()
|
|
797
|
+
.returns(Promise.reject(error))
|
|
798
|
+
.onSecondCall()
|
|
799
|
+
.returns(Promise.resolve(fakeJoinResult));
|
|
767
800
|
const leaveStub = sinon.stub(meeting, 'leave').resolves();
|
|
768
801
|
|
|
769
802
|
const result = await meeting.joinWithMedia({
|
|
@@ -795,24 +828,27 @@ describe('plugin-meetings', () => {
|
|
|
795
828
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
796
829
|
|
|
797
830
|
// resets joinWithMediaRetryInfo
|
|
798
|
-
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
831
|
+
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
832
|
+
isRetry: false,
|
|
833
|
+
prevJoinResponse: undefined,
|
|
834
|
+
});
|
|
799
835
|
});
|
|
800
836
|
|
|
801
837
|
it('should fail if called with allowMediaInLobby:false', async () => {
|
|
802
838
|
meeting.join = sinon.stub().returns(Promise.resolve(test1));
|
|
803
|
-
meeting.
|
|
839
|
+
meeting.addMediaInternal = sinon.stub().returns(Promise.resolve(test4));
|
|
804
840
|
|
|
805
841
|
await assert.isRejected(
|
|
806
842
|
meeting.joinWithMedia({mediaOptions: {allowMediaInLobby: false}})
|
|
807
843
|
);
|
|
808
844
|
});
|
|
809
845
|
|
|
810
|
-
it('should call leave() if
|
|
846
|
+
it('should call leave() if addMediaInternal() fails and ignore leave() failure', async () => {
|
|
811
847
|
const leaveError = new Error('leave error');
|
|
812
848
|
const addMediaError = new Error('fake addMedia error');
|
|
813
849
|
|
|
814
850
|
const leaveStub = sinon.stub(meeting, 'leave').rejects(leaveError);
|
|
815
|
-
meeting.
|
|
851
|
+
meeting.addMediaInternal = sinon.stub().rejects(addMediaError);
|
|
816
852
|
|
|
817
853
|
await assert.isRejected(
|
|
818
854
|
meeting.joinWithMedia({
|
|
@@ -828,7 +864,6 @@ describe('plugin-meetings', () => {
|
|
|
828
864
|
reason: 'joinWithMedia failure',
|
|
829
865
|
});
|
|
830
866
|
|
|
831
|
-
|
|
832
867
|
// Behavioral metric is sent on both calls of joinWithMedia
|
|
833
868
|
assert.calledTwice(Metrics.sendBehavioralMetric);
|
|
834
869
|
assert.calledWith(
|
|
@@ -863,12 +898,11 @@ describe('plugin-meetings', () => {
|
|
|
863
898
|
);
|
|
864
899
|
});
|
|
865
900
|
|
|
866
|
-
it('should not call leave() if
|
|
901
|
+
it('should not call leave() if addMediaInternal() fails the first time and succeeds the second time and should only call join() once', async () => {
|
|
867
902
|
const addMediaError = new Error('fake addMedia error');
|
|
868
|
-
const
|
|
869
|
-
const leaveStub = sinon.stub(meeting, 'leave').rejects(leaveError);
|
|
903
|
+
const leaveStub = sinon.stub(meeting, 'leave');
|
|
870
904
|
|
|
871
|
-
meeting.
|
|
905
|
+
meeting.addMediaInternal = sinon
|
|
872
906
|
.stub()
|
|
873
907
|
.onFirstCall()
|
|
874
908
|
.rejects(addMediaError)
|
|
@@ -902,6 +936,203 @@ describe('plugin-meetings', () => {
|
|
|
902
936
|
}
|
|
903
937
|
);
|
|
904
938
|
});
|
|
939
|
+
|
|
940
|
+
it('should send the right CA events when media connection fails', async () => {
|
|
941
|
+
const fakeClientError = {id: 'error'};
|
|
942
|
+
|
|
943
|
+
const fakeMediaConnection = {
|
|
944
|
+
close: sinon.stub(),
|
|
945
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
946
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
947
|
+
on: sinon.stub(),
|
|
948
|
+
forceRtcMetricsSend: sinon.stub().resolves(),
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
// setup the stubs so that media connection always fails on waitForMediaConnectionConnected()
|
|
952
|
+
addMediaInternalStub.restore();
|
|
953
|
+
meeting.join.returns(
|
|
954
|
+
Promise.resolve({id: 'join result', roapMessage: 'fake TURN discovery response'})
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
958
|
+
sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
|
|
959
|
+
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
960
|
+
sinon
|
|
961
|
+
.stub(meeting.mediaProperties, 'waitForMediaConnectionConnected')
|
|
962
|
+
.rejects(new Error('fake error'));
|
|
963
|
+
|
|
964
|
+
webex.meetings.reachability.isWebexMediaBackendUnreachable = sinon.stub().resolves(false);
|
|
965
|
+
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
|
|
966
|
+
.stub()
|
|
967
|
+
.returns(fakeClientError);
|
|
968
|
+
|
|
969
|
+
// call joinWithMedia() - it should fail
|
|
970
|
+
await assert.isRejected(
|
|
971
|
+
meeting.joinWithMedia({
|
|
972
|
+
joinOptions,
|
|
973
|
+
mediaOptions,
|
|
974
|
+
})
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
// check the right CA events have been sent:
|
|
978
|
+
// calls at index 0 and 2 to submitClientEvent are for "client.media.capabilities" which we don't care about in this test
|
|
979
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent.getCall(1), {
|
|
980
|
+
name: 'client.ice.end',
|
|
981
|
+
payload: {
|
|
982
|
+
canProceed: false,
|
|
983
|
+
icePhase: 'JOIN_MEETING_RETRY',
|
|
984
|
+
errors: [fakeClientError],
|
|
985
|
+
},
|
|
986
|
+
options: {
|
|
987
|
+
meetingId: meeting.id,
|
|
988
|
+
},
|
|
989
|
+
});
|
|
990
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent.getCall(3), {
|
|
991
|
+
name: 'client.ice.end',
|
|
992
|
+
payload: {
|
|
993
|
+
canProceed: false,
|
|
994
|
+
icePhase: 'JOIN_MEETING_FINAL',
|
|
995
|
+
errors: [fakeClientError],
|
|
996
|
+
},
|
|
997
|
+
options: {
|
|
998
|
+
meetingId: meeting.id,
|
|
999
|
+
},
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
it('should force TURN discovery on the 2nd attempt, if addMediaInternal() fails the first time', async () => {
|
|
1004
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1005
|
+
|
|
1006
|
+
const fakeMediaConnection = {
|
|
1007
|
+
close: sinon.stub(),
|
|
1008
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1009
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1010
|
+
on: sinon.stub(),
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
/* Setup the stubs so that the first call to addMediaInternal() fails
|
|
1014
|
+
and the 2nd call calls the real implementation - so that we can check that
|
|
1015
|
+
addMediaInternal() eventually calls meeting.roap.doTurnDiscovery() with isForced=true.
|
|
1016
|
+
As a result we need to also stub a few other methods like createMediaConnection() and waitForRemoteSDPAnswer() */
|
|
1017
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
1018
|
+
sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
|
|
1019
|
+
|
|
1020
|
+
addMediaInternalStub.onFirstCall().rejects(addMediaError);
|
|
1021
|
+
addMediaInternalStub.onSecondCall().callsFake((...args) => {
|
|
1022
|
+
return addMediaInternalStub.wrappedMethod.bind(meeting)(...args);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
1026
|
+
|
|
1027
|
+
const result = await meeting.joinWithMedia({
|
|
1028
|
+
joinOptions,
|
|
1029
|
+
mediaOptions,
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: undefined});
|
|
1033
|
+
|
|
1034
|
+
assert.calledOnce(meeting.join);
|
|
1035
|
+
|
|
1036
|
+
// first addMediaInternal() call without forcing TURN
|
|
1037
|
+
assert.calledWith(
|
|
1038
|
+
meeting.addMediaInternal.firstCall,
|
|
1039
|
+
sinon.match.any,
|
|
1040
|
+
fakeTurnServerInfo,
|
|
1041
|
+
false,
|
|
1042
|
+
mediaOptions
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
// second addMediaInternal() call with forcing TURN
|
|
1046
|
+
assert.calledWith(
|
|
1047
|
+
meeting.addMediaInternal.secondCall,
|
|
1048
|
+
sinon.match.any,
|
|
1049
|
+
undefined,
|
|
1050
|
+
true,
|
|
1051
|
+
mediaOptions
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
// now check that TURN is actually forced by addMediaInternal(),
|
|
1055
|
+
// we're not checking the isReconnecting param value, because it depends on the full sequence of things
|
|
1056
|
+
// being done correctly (like SDP offer creation) and some of these are stubbed in this test
|
|
1057
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, sinon.match.any, true);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
it('should return the right icePhase in icePhaseCallback on 1st attempt and retry', async () => {
|
|
1061
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1062
|
+
|
|
1063
|
+
const icePhaseCallbacks = [];
|
|
1064
|
+
const addMediaInternalResults = [];
|
|
1065
|
+
|
|
1066
|
+
meeting.addMediaInternal = sinon
|
|
1067
|
+
.stub()
|
|
1068
|
+
.callsFake((icePhaseCallback, _turnServerInfo, _forceTurnDiscovery) => {
|
|
1069
|
+
const defer = new Defer();
|
|
1070
|
+
|
|
1071
|
+
icePhaseCallbacks.push(icePhaseCallback);
|
|
1072
|
+
addMediaInternalResults.push(defer);
|
|
1073
|
+
return defer.promise;
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const result = meeting.joinWithMedia({
|
|
1077
|
+
joinOptions,
|
|
1078
|
+
mediaOptions,
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
await testUtils.flushPromises();
|
|
1082
|
+
|
|
1083
|
+
// check the callback works correctly on the 1st attempt
|
|
1084
|
+
assert.equal(icePhaseCallbacks.length, 1);
|
|
1085
|
+
assert.equal(icePhaseCallbacks[0](), 'JOIN_MEETING_RETRY');
|
|
1086
|
+
|
|
1087
|
+
// now trigger the failure, so that joinWithMedia() does a retry
|
|
1088
|
+
addMediaInternalResults[0].reject(addMediaError);
|
|
1089
|
+
|
|
1090
|
+
await testUtils.flushPromises();
|
|
1091
|
+
|
|
1092
|
+
// check the callback works correctly on the 2nd attempt
|
|
1093
|
+
assert.equal(icePhaseCallbacks.length, 2);
|
|
1094
|
+
assert.equal(icePhaseCallbacks[1](), 'JOIN_MEETING_FINAL');
|
|
1095
|
+
|
|
1096
|
+
// trigger 2nd failure
|
|
1097
|
+
addMediaInternalResults[1].reject(addMediaError);
|
|
1098
|
+
|
|
1099
|
+
await assert.isRejected(result);
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
it('should not attempt a retry if we fail to create the offer on first atttempt', async () => {
|
|
1103
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1104
|
+
addMediaError.name = 'SdpOfferCreationError';
|
|
1105
|
+
|
|
1106
|
+
meeting.addMediaInternal.rejects(addMediaError);
|
|
1107
|
+
|
|
1108
|
+
await assert.isRejected(
|
|
1109
|
+
meeting.joinWithMedia({
|
|
1110
|
+
joinOptions,
|
|
1111
|
+
mediaOptions,
|
|
1112
|
+
}),
|
|
1113
|
+
addMediaError
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
// check that only 1 attempt was done
|
|
1117
|
+
assert.calledOnce(meeting.join);
|
|
1118
|
+
assert.calledOnce(meeting.addMediaInternal);
|
|
1119
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1120
|
+
assert.calledWith(
|
|
1121
|
+
Metrics.sendBehavioralMetric.firstCall,
|
|
1122
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1123
|
+
{
|
|
1124
|
+
correlation_id: meeting.correlationId,
|
|
1125
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1126
|
+
reason: addMediaError.message,
|
|
1127
|
+
stack: addMediaError.stack,
|
|
1128
|
+
leaveErrorReason: undefined,
|
|
1129
|
+
isRetry: false,
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
type: addMediaError.name,
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
1135
|
+
});
|
|
905
1136
|
});
|
|
906
1137
|
|
|
907
1138
|
describe('#isTranscriptionSupported', () => {
|
|
@@ -946,19 +1177,18 @@ describe('plugin-meetings', () => {
|
|
|
946
1177
|
assert.calledTwice(webex.internal.voicea.turnOnCaptions);
|
|
947
1178
|
});
|
|
948
1179
|
|
|
949
|
-
it('should listen to events and
|
|
1180
|
+
it('should listen to events and turnOnCaptions for all users', async () => {
|
|
950
1181
|
meeting.joinedWith = {
|
|
951
1182
|
state: 'JOINED',
|
|
952
1183
|
};
|
|
953
1184
|
meeting.areVoiceaEventsSetup = false;
|
|
954
|
-
meeting.roles = ['COHOST'];
|
|
955
1185
|
|
|
956
1186
|
await meeting.startTranscription();
|
|
957
1187
|
|
|
958
1188
|
assert.equal(webex.internal.voicea.on.callCount, 4);
|
|
959
1189
|
assert.equal(meeting.areVoiceaEventsSetup, true);
|
|
960
1190
|
assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
|
|
961
|
-
assert.
|
|
1191
|
+
assert.calledOnce(webex.internal.voicea.turnOnCaptions);
|
|
962
1192
|
});
|
|
963
1193
|
|
|
964
1194
|
it("should throw error if request doesn't work", async () => {
|
|
@@ -1075,6 +1305,7 @@ describe('plugin-meetings', () => {
|
|
|
1075
1305
|
webex.internal.voicea.on = sinon.stub();
|
|
1076
1306
|
webex.internal.voicea.off = sinon.stub();
|
|
1077
1307
|
webex.internal.voicea.setSpokenLanguage = sinon.stub();
|
|
1308
|
+
meeting.roles = ['MODERATOR'];
|
|
1078
1309
|
});
|
|
1079
1310
|
|
|
1080
1311
|
afterEach(() => {
|
|
@@ -1091,6 +1322,16 @@ describe('plugin-meetings', () => {
|
|
|
1091
1322
|
});
|
|
1092
1323
|
});
|
|
1093
1324
|
|
|
1325
|
+
it('should reject if current user is not a host', (done) => {
|
|
1326
|
+
meeting.isTranscriptionSupported.returns(true);
|
|
1327
|
+
meeting.roles = ['COHOST'];
|
|
1328
|
+
|
|
1329
|
+
meeting.setSpokenLanguage('fr').catch((error) => {
|
|
1330
|
+
assert.equal(error.message, 'Only host can set spoken language');
|
|
1331
|
+
done();
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1094
1335
|
it('should resolve with the language code on successful language update', (done) => {
|
|
1095
1336
|
meeting.isTranscriptionSupported.returns(true);
|
|
1096
1337
|
const languageCode = 'fr';
|
|
@@ -1136,10 +1377,7 @@ describe('plugin-meetings', () => {
|
|
|
1136
1377
|
|
|
1137
1378
|
it('should trigger meeting:caption-received event', () => {
|
|
1138
1379
|
meeting.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]({});
|
|
1139
|
-
assert.calledWith(
|
|
1140
|
-
meeting.trigger,
|
|
1141
|
-
EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED
|
|
1142
|
-
);
|
|
1380
|
+
assert.calledWith(meeting.trigger, EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED);
|
|
1143
1381
|
});
|
|
1144
1382
|
|
|
1145
1383
|
it('should trigger meeting:receiveTranscription:started event', () => {
|
|
@@ -1152,10 +1390,7 @@ describe('plugin-meetings', () => {
|
|
|
1152
1390
|
|
|
1153
1391
|
it('should trigger meeting:caption-received event', () => {
|
|
1154
1392
|
meeting.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]({});
|
|
1155
|
-
assert.calledWith(
|
|
1156
|
-
meeting.trigger,
|
|
1157
|
-
EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED
|
|
1158
|
-
);
|
|
1393
|
+
assert.calledWith(meeting.trigger, EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED);
|
|
1159
1394
|
});
|
|
1160
1395
|
});
|
|
1161
1396
|
|
|
@@ -1310,11 +1545,7 @@ describe('plugin-meetings', () => {
|
|
|
1310
1545
|
|
|
1311
1546
|
it('turns off llm online, emits transcription connected events', () => {
|
|
1312
1547
|
meeting.handleLLMOnline();
|
|
1313
|
-
assert.calledOnceWithExactly(
|
|
1314
|
-
webex.internal.llm.off,
|
|
1315
|
-
'online',
|
|
1316
|
-
meeting.handleLLMOnline
|
|
1317
|
-
);
|
|
1548
|
+
assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
1318
1549
|
assert.calledWith(
|
|
1319
1550
|
TriggerProxy.trigger,
|
|
1320
1551
|
sinon.match.instanceOf(Meeting),
|
|
@@ -1376,11 +1607,40 @@ describe('plugin-meetings', () => {
|
|
|
1376
1607
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
1377
1608
|
assert.calledOnce(meeting.setLocus);
|
|
1378
1609
|
assert.equal(result, joinMeetingResult);
|
|
1379
|
-
assert.calledWith(
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
)
|
|
1610
|
+
assert.calledWith(webex.internal.llm.on, 'online', meeting.handleLLMOnline);
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
[true, false].forEach((enableMultistream) => {
|
|
1614
|
+
it(`should instantiate LocusMediaRequest with correct parameters (enableMultistream=${enableMultistream})`, async () => {
|
|
1615
|
+
meeting.config.deviceType = 'web';
|
|
1616
|
+
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
1617
|
+
|
|
1618
|
+
const mockLocusMediaRequestCtor = sinon
|
|
1619
|
+
.stub(LocusMediaRequestModule, 'LocusMediaRequest')
|
|
1620
|
+
.returns({
|
|
1621
|
+
id: 'fake LocusMediaRequest instance',
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
await meeting.join({enableMultistream});
|
|
1625
|
+
|
|
1626
|
+
assert.calledOnceWithExactly(
|
|
1627
|
+
mockLocusMediaRequestCtor,
|
|
1628
|
+
{
|
|
1629
|
+
correlationId: meeting.correlationId,
|
|
1630
|
+
meetingId: meeting.id,
|
|
1631
|
+
device: {
|
|
1632
|
+
url: meeting.deviceUrl,
|
|
1633
|
+
deviceType: meeting.config.deviceType,
|
|
1634
|
+
countryCode: 'UK',
|
|
1635
|
+
regionCode: 'EU',
|
|
1636
|
+
},
|
|
1637
|
+
preferTranscoding: !enableMultistream,
|
|
1638
|
+
},
|
|
1639
|
+
{
|
|
1640
|
+
parent: meeting.webex,
|
|
1641
|
+
}
|
|
1642
|
+
);
|
|
1643
|
+
});
|
|
1384
1644
|
});
|
|
1385
1645
|
|
|
1386
1646
|
it('should take trigger from meeting joinTrigger if available', () => {
|
|
@@ -1661,7 +1921,7 @@ describe('plugin-meetings', () => {
|
|
|
1661
1921
|
|
|
1662
1922
|
let fakeMediaConnection;
|
|
1663
1923
|
|
|
1664
|
-
beforeEach(() => {
|
|
1924
|
+
beforeEach(async () => {
|
|
1665
1925
|
fakeMediaConnection = {
|
|
1666
1926
|
close: sinon.stub(),
|
|
1667
1927
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
@@ -1670,17 +1930,29 @@ describe('plugin-meetings', () => {
|
|
|
1670
1930
|
};
|
|
1671
1931
|
meeting.mediaProperties.setMediaDirection = sinon.stub().returns(true);
|
|
1672
1932
|
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
1673
|
-
meeting.mediaProperties.getCurrentConnectionInfo = sinon
|
|
1933
|
+
meeting.mediaProperties.getCurrentConnectionInfo = sinon
|
|
1934
|
+
.stub()
|
|
1935
|
+
.resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
|
|
1674
1936
|
meeting.audio = muteStateStub;
|
|
1675
1937
|
meeting.video = muteStateStub;
|
|
1676
1938
|
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
.stub()
|
|
1939
|
+
sinon.stub(meeting, 'setupMediaConnectionListeners');
|
|
1940
|
+
sinon.stub(meeting, 'setMercuryListener');
|
|
1941
|
+
sinon
|
|
1942
|
+
.stub(meeting.roap, 'doTurnDiscovery')
|
|
1682
1943
|
.resolves({turnServerInfo: {}, turnDiscoverySkippedReason: undefined});
|
|
1683
|
-
|
|
1944
|
+
sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
|
|
1945
|
+
|
|
1946
|
+
// normally the first Roap message we send is creating confluence, so mock LocusMediaRequest.isConfluenceCreated()
|
|
1947
|
+
// to return false the first time it's called and true the 2nd time, to simulate how it would happen for real
|
|
1948
|
+
meeting.locusMediaRequest = {
|
|
1949
|
+
isConfluenceCreated: sinon
|
|
1950
|
+
.stub()
|
|
1951
|
+
.onFirstCall()
|
|
1952
|
+
.returns(false)
|
|
1953
|
+
.onSecondCall()
|
|
1954
|
+
.returns(true),
|
|
1955
|
+
};
|
|
1684
1956
|
});
|
|
1685
1957
|
|
|
1686
1958
|
it('should have #addMedia', () => {
|
|
@@ -1778,6 +2050,7 @@ describe('plugin-meetings', () => {
|
|
|
1778
2050
|
someReachabilityMetric2: 'some value2',
|
|
1779
2051
|
selectedCandidatePairChanges: 2,
|
|
1780
2052
|
numTransports: 1,
|
|
2053
|
+
iceCandidatesCount: 0,
|
|
1781
2054
|
}
|
|
1782
2055
|
);
|
|
1783
2056
|
});
|
|
@@ -1885,6 +2158,7 @@ describe('plugin-meetings', () => {
|
|
|
1885
2158
|
someReachabilityMetric2: 'some value2',
|
|
1886
2159
|
selectedCandidatePairChanges: 2,
|
|
1887
2160
|
numTransports: 1,
|
|
2161
|
+
iceCandidatesCount: 0,
|
|
1888
2162
|
}
|
|
1889
2163
|
);
|
|
1890
2164
|
});
|
|
@@ -2028,6 +2302,61 @@ describe('plugin-meetings', () => {
|
|
|
2028
2302
|
}
|
|
2029
2303
|
});
|
|
2030
2304
|
|
|
2305
|
+
it('sends correct CA event when times out waiting for SDP answer', async () => {
|
|
2306
|
+
const eventListeners = {};
|
|
2307
|
+
const clock = sinon.useFakeTimers();
|
|
2308
|
+
|
|
2309
|
+
// these 2 are stubbed, we need the real versions:
|
|
2310
|
+
meeting.waitForRemoteSDPAnswer.restore();
|
|
2311
|
+
meeting.setupMediaConnectionListeners.restore();
|
|
2312
|
+
|
|
2313
|
+
meeting.meetingState = 'ACTIVE';
|
|
2314
|
+
|
|
2315
|
+
// setup a mock media connection that will trigger an offer when initiateOffer() is called
|
|
2316
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
2317
|
+
initiateOffer: sinon.stub().callsFake(() => {
|
|
2318
|
+
// simulate offer being generated
|
|
2319
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
2320
|
+
|
|
2321
|
+
return Promise.resolve();
|
|
2322
|
+
}),
|
|
2323
|
+
close: sinon.stub(),
|
|
2324
|
+
on: (event, listener) => {
|
|
2325
|
+
eventListeners[event] = listener;
|
|
2326
|
+
},
|
|
2327
|
+
forceRtcMetricsSend: sinon.stub().resolves(),
|
|
2328
|
+
});
|
|
2329
|
+
|
|
2330
|
+
const getErrorPayloadForClientErrorCodeStub =
|
|
2331
|
+
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
2332
|
+
sinon
|
|
2333
|
+
.stub()
|
|
2334
|
+
.callsFake(({clientErrorCode}) => ({errorCode: clientErrorCode, fatal: true})));
|
|
2335
|
+
|
|
2336
|
+
const result = meeting.addMedia();
|
|
2337
|
+
await testUtils.flushPromises();
|
|
2338
|
+
|
|
2339
|
+
// simulate timeout waiting for the SDP answer that never comes
|
|
2340
|
+
await clock.tickAsync(ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
|
|
2341
|
+
|
|
2342
|
+
await assert.isRejected(result);
|
|
2343
|
+
|
|
2344
|
+
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
2345
|
+
clientErrorCode: 2007,
|
|
2346
|
+
});
|
|
2347
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2348
|
+
name: 'client.media-engine.remote-sdp-received',
|
|
2349
|
+
payload: {
|
|
2350
|
+
canProceed: false,
|
|
2351
|
+
errors: [{errorCode: 2007, fatal: true}],
|
|
2352
|
+
},
|
|
2353
|
+
options: {
|
|
2354
|
+
meetingId: meeting.id,
|
|
2355
|
+
rawError: sinon.match.instanceOf(Error),
|
|
2356
|
+
},
|
|
2357
|
+
});
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2031
2360
|
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 () => {
|
|
2032
2361
|
meeting.meetingState = 'ACTIVE';
|
|
2033
2362
|
// setup the mock to cause addMedia() to fail
|
|
@@ -2078,9 +2407,7 @@ describe('plugin-meetings', () => {
|
|
|
2078
2407
|
Media.createMediaConnection,
|
|
2079
2408
|
false,
|
|
2080
2409
|
meeting.getMediaConnectionDebugId(),
|
|
2081
|
-
webex,
|
|
2082
2410
|
meeting.id,
|
|
2083
|
-
meeting.correlationId,
|
|
2084
2411
|
sinon.match({turnServerInfo: undefined})
|
|
2085
2412
|
);
|
|
2086
2413
|
assert.calledOnce(meeting.setMercuryListener);
|
|
@@ -2122,6 +2449,44 @@ describe('plugin-meetings', () => {
|
|
|
2122
2449
|
checkWorking({allowMediaInLobby: true});
|
|
2123
2450
|
});
|
|
2124
2451
|
|
|
2452
|
+
it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
|
|
2453
|
+
const fakeRtcMetrics = {id: 'fake rtc metrics object'};
|
|
2454
|
+
const rtcMetricsCtor = sinon.stub(RtcMetricsModule, 'default').returns(fakeRtcMetrics);
|
|
2455
|
+
|
|
2456
|
+
// setup the minimum mocks required for multistream connection
|
|
2457
|
+
fakeMediaConnection.createSendSlot = sinon.stub().returns({
|
|
2458
|
+
publishStream: sinon.stub(),
|
|
2459
|
+
unpublishStream: sinon.stub(),
|
|
2460
|
+
setNamedMediaGroups: sinon.stub(),
|
|
2461
|
+
});
|
|
2462
|
+
sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
|
|
2463
|
+
start: sinon.stub().resolves(),
|
|
2464
|
+
on: sinon.stub(),
|
|
2465
|
+
logAllReceiveSlots: sinon.stub(),
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2468
|
+
meeting.meetingState = 'ACTIVE';
|
|
2469
|
+
meeting.isMultistream = true;
|
|
2470
|
+
|
|
2471
|
+
await meeting.addMedia({
|
|
2472
|
+
mediaSettings: {},
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
assert.calledOnceWithExactly(rtcMetricsCtor, webex, meeting.id, meeting.correlationId);
|
|
2476
|
+
|
|
2477
|
+
// check that rtcMetrics was passed to Media.createMediaConnection
|
|
2478
|
+
assert.calledOnce(Media.createMediaConnection);
|
|
2479
|
+
assert.calledWith(
|
|
2480
|
+
Media.createMediaConnection,
|
|
2481
|
+
true,
|
|
2482
|
+
meeting.getMediaConnectionDebugId(),
|
|
2483
|
+
meeting.id,
|
|
2484
|
+
sinon.match({
|
|
2485
|
+
rtcMetrics: fakeRtcMetrics,
|
|
2486
|
+
})
|
|
2487
|
+
);
|
|
2488
|
+
});
|
|
2489
|
+
|
|
2125
2490
|
it('should pass the turn server info to the peer connection', async () => {
|
|
2126
2491
|
const FAKE_TURN_URL = 'turns:webex.com:3478';
|
|
2127
2492
|
const FAKE_TURN_USER = 'some-turn-username';
|
|
@@ -2151,9 +2516,7 @@ describe('plugin-meetings', () => {
|
|
|
2151
2516
|
Media.createMediaConnection,
|
|
2152
2517
|
false,
|
|
2153
2518
|
meeting.getMediaConnectionDebugId(),
|
|
2154
|
-
webex,
|
|
2155
2519
|
meeting.id,
|
|
2156
|
-
meeting.correlationId,
|
|
2157
2520
|
sinon.match({
|
|
2158
2521
|
turnServerInfo: {
|
|
2159
2522
|
url: FAKE_TURN_URL,
|
|
@@ -2189,6 +2552,10 @@ describe('plugin-meetings', () => {
|
|
|
2189
2552
|
const getErrorPayloadForClientErrorCodeStub =
|
|
2190
2553
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
2191
2554
|
sinon.stub().returns(FAKE_ERROR));
|
|
2555
|
+
webex.meetings.reachability = {
|
|
2556
|
+
isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
|
|
2557
|
+
getReachabilityMetrics: sinon.stub().resolves(),
|
|
2558
|
+
};
|
|
2192
2559
|
const MOCK_CLIENT_ERROR_CODE = 2004;
|
|
2193
2560
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
2194
2561
|
.stub(CallDiagnosticUtils, 'generateClientErrorCodeForIceFailure')
|
|
@@ -2216,7 +2583,7 @@ describe('plugin-meetings', () => {
|
|
|
2216
2583
|
turnDiscoverySkippedReason: undefined,
|
|
2217
2584
|
});
|
|
2218
2585
|
meeting.meetingState = 'ACTIVE';
|
|
2219
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
2586
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2220
2587
|
|
|
2221
2588
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2222
2589
|
const closeMediaConnectionStub = sinon.stub();
|
|
@@ -2240,13 +2607,15 @@ describe('plugin-meetings', () => {
|
|
|
2240
2607
|
assert.calledTwice(generateClientErrorCodeForIceFailureStub);
|
|
2241
2608
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2242
2609
|
signalingState: 'unknown',
|
|
2243
|
-
|
|
2610
|
+
iceConnected: false,
|
|
2244
2611
|
turnServerUsed: false,
|
|
2612
|
+
unreachable: false,
|
|
2245
2613
|
});
|
|
2246
2614
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2247
2615
|
signalingState: 'unknown',
|
|
2248
|
-
|
|
2616
|
+
iceConnected: false,
|
|
2249
2617
|
turnServerUsed: true,
|
|
2618
|
+
unreachable: false,
|
|
2250
2619
|
});
|
|
2251
2620
|
|
|
2252
2621
|
assert.calledTwice(getErrorPayloadForClientErrorCodeStub);
|
|
@@ -2364,6 +2733,7 @@ describe('plugin-meetings', () => {
|
|
|
2364
2733
|
iceConnectionState: 'unknown',
|
|
2365
2734
|
selectedCandidatePairChanges: 2,
|
|
2366
2735
|
numTransports: 1,
|
|
2736
|
+
iceCandidatesCount: 0,
|
|
2367
2737
|
},
|
|
2368
2738
|
]);
|
|
2369
2739
|
|
|
@@ -2371,7 +2741,7 @@ describe('plugin-meetings', () => {
|
|
|
2371
2741
|
const doTurnDiscoveryCalls = meeting.roap.doTurnDiscovery.getCalls();
|
|
2372
2742
|
assert.equal(doTurnDiscoveryCalls.length, 2);
|
|
2373
2743
|
assert.deepEqual(doTurnDiscoveryCalls[0].args, [meeting, false, false]);
|
|
2374
|
-
assert.deepEqual(doTurnDiscoveryCalls[1].args, [
|
|
2744
|
+
assert.deepEqual(doTurnDiscoveryCalls[1].args.slice(1), [true, true]);
|
|
2375
2745
|
|
|
2376
2746
|
// Some clean up steps happens twice
|
|
2377
2747
|
assert.calledTwice(forceRtcMetricsSend);
|
|
@@ -2383,6 +2753,17 @@ describe('plugin-meetings', () => {
|
|
|
2383
2753
|
|
|
2384
2754
|
it('should resolve if waitForMediaConnectionConnected() rejects the first time but resolves the second time', async () => {
|
|
2385
2755
|
const FAKE_ERROR = {fatal: true};
|
|
2756
|
+
webex.meetings.reachability = {
|
|
2757
|
+
isWebexMediaBackendUnreachable: sinon
|
|
2758
|
+
.stub()
|
|
2759
|
+
.onCall(0)
|
|
2760
|
+
.rejects()
|
|
2761
|
+
.onCall(1)
|
|
2762
|
+
.resolves(true)
|
|
2763
|
+
.onCall(2)
|
|
2764
|
+
.resolves(false),
|
|
2765
|
+
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
2766
|
+
};
|
|
2386
2767
|
const getErrorPayloadForClientErrorCodeStub =
|
|
2387
2768
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
2388
2769
|
sinon.stub().returns(FAKE_ERROR));
|
|
@@ -2440,8 +2821,9 @@ describe('plugin-meetings', () => {
|
|
|
2440
2821
|
assert.calledOnce(generateClientErrorCodeForIceFailureStub);
|
|
2441
2822
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2442
2823
|
signalingState: 'unknown',
|
|
2443
|
-
|
|
2824
|
+
iceConnected: undefined,
|
|
2444
2825
|
turnServerUsed: false,
|
|
2826
|
+
unreachable: false,
|
|
2445
2827
|
});
|
|
2446
2828
|
|
|
2447
2829
|
assert.calledOnce(getErrorPayloadForClientErrorCodeStub);
|
|
@@ -2547,6 +2929,7 @@ describe('plugin-meetings', () => {
|
|
|
2547
2929
|
isMultistream: false,
|
|
2548
2930
|
retriedWithTurnServer: true,
|
|
2549
2931
|
isJoinWithMediaRetry: false,
|
|
2932
|
+
iceCandidatesCount: 0,
|
|
2550
2933
|
},
|
|
2551
2934
|
]);
|
|
2552
2935
|
meeting.roap.doTurnDiscovery;
|
|
@@ -2675,6 +3058,10 @@ describe('plugin-meetings', () => {
|
|
|
2675
3058
|
someReachabilityMetric2: 'some value2',
|
|
2676
3059
|
}),
|
|
2677
3060
|
};
|
|
3061
|
+
meeting.iceCandidatesCount = 3;
|
|
3062
|
+
meeting.iceCandidateErrors.set('701_error', 3);
|
|
3063
|
+
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
3064
|
+
|
|
2678
3065
|
await meeting.addMedia({
|
|
2679
3066
|
mediaSettings: {},
|
|
2680
3067
|
});
|
|
@@ -2694,6 +3081,9 @@ describe('plugin-meetings', () => {
|
|
|
2694
3081
|
isJoinWithMediaRetry: false,
|
|
2695
3082
|
someReachabilityMetric1: 'some value1',
|
|
2696
3083
|
someReachabilityMetric2: 'some value2',
|
|
3084
|
+
iceCandidatesCount: 3,
|
|
3085
|
+
'701_error': 3,
|
|
3086
|
+
'701_turn_host_lookup_received_error': 1,
|
|
2697
3087
|
}
|
|
2698
3088
|
);
|
|
2699
3089
|
|
|
@@ -2715,7 +3105,63 @@ describe('plugin-meetings', () => {
|
|
|
2715
3105
|
turnDiscoverySkippedReason: undefined,
|
|
2716
3106
|
});
|
|
2717
3107
|
meeting.meetingState = 'ACTIVE';
|
|
2718
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
3108
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
3109
|
+
|
|
3110
|
+
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
3111
|
+
const closeMediaConnectionStub = sinon.stub();
|
|
3112
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
3113
|
+
close: closeMediaConnectionStub,
|
|
3114
|
+
forceRtcMetricsSend,
|
|
3115
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
3116
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
3117
|
+
on: sinon.stub(),
|
|
3118
|
+
});
|
|
3119
|
+
|
|
3120
|
+
await meeting
|
|
3121
|
+
.addMedia({
|
|
3122
|
+
mediaSettings: {},
|
|
3123
|
+
})
|
|
3124
|
+
.catch((err) => {
|
|
3125
|
+
errorThrown = err;
|
|
3126
|
+
assert.instanceOf(err, AddMediaFailed);
|
|
3127
|
+
});
|
|
3128
|
+
|
|
3129
|
+
// Check that the only metric sent is ADD_MEDIA_FAILURE
|
|
3130
|
+
assert.calledOnceWithExactly(
|
|
3131
|
+
Metrics.sendBehavioralMetric,
|
|
3132
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
3133
|
+
{
|
|
3134
|
+
correlation_id: meeting.correlationId,
|
|
3135
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3136
|
+
reason: errorThrown.message,
|
|
3137
|
+
stack: errorThrown.stack,
|
|
3138
|
+
code: errorThrown.code,
|
|
3139
|
+
turnDiscoverySkippedReason: undefined,
|
|
3140
|
+
turnServerUsed: true,
|
|
3141
|
+
retriedWithTurnServer: false,
|
|
3142
|
+
isMultistream: false,
|
|
3143
|
+
isJoinWithMediaRetry: false,
|
|
3144
|
+
signalingState: 'unknown',
|
|
3145
|
+
connectionState: 'unknown',
|
|
3146
|
+
iceConnectionState: 'unknown',
|
|
3147
|
+
selectedCandidatePairChanges: 2,
|
|
3148
|
+
numTransports: 1,
|
|
3149
|
+
iceCandidatesCount: 0,
|
|
3150
|
+
}
|
|
3151
|
+
);
|
|
3152
|
+
|
|
3153
|
+
assert.isOk(errorThrown);
|
|
3154
|
+
});
|
|
3155
|
+
|
|
3156
|
+
it('should send ICE_CANDIDATE_ERROR metric if media connection fails and ice candidate errors have been gathered', async () => {
|
|
3157
|
+
let errorThrown = undefined;
|
|
3158
|
+
|
|
3159
|
+
meeting.roap.doTurnDiscovery = sinon.stub().returns({
|
|
3160
|
+
turnServerInfo: undefined,
|
|
3161
|
+
turnDiscoverySkippedReason: undefined,
|
|
3162
|
+
});
|
|
3163
|
+
meeting.meetingState = 'ACTIVE';
|
|
3164
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2719
3165
|
|
|
2720
3166
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2721
3167
|
const closeMediaConnectionStub = sinon.stub();
|
|
@@ -2727,6 +3173,9 @@ describe('plugin-meetings', () => {
|
|
|
2727
3173
|
on: sinon.stub(),
|
|
2728
3174
|
});
|
|
2729
3175
|
|
|
3176
|
+
meeting.iceCandidateErrors.set('701_error', 2);
|
|
3177
|
+
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
3178
|
+
|
|
2730
3179
|
await meeting
|
|
2731
3180
|
.addMedia({
|
|
2732
3181
|
mediaSettings: {},
|
|
@@ -2756,6 +3205,9 @@ describe('plugin-meetings', () => {
|
|
|
2756
3205
|
iceConnectionState: 'unknown',
|
|
2757
3206
|
selectedCandidatePairChanges: 2,
|
|
2758
3207
|
numTransports: 1,
|
|
3208
|
+
'701_error': 2,
|
|
3209
|
+
'701_turn_host_lookup_received_error': 1,
|
|
3210
|
+
iceCandidatesCount: 0,
|
|
2759
3211
|
}
|
|
2760
3212
|
);
|
|
2761
3213
|
|
|
@@ -2775,7 +3227,7 @@ describe('plugin-meetings', () => {
|
|
|
2775
3227
|
|
|
2776
3228
|
statsAnalyzerStub = new EventsScope();
|
|
2777
3229
|
// mock the StatsAnalyzer constructor
|
|
2778
|
-
sinon.stub(
|
|
3230
|
+
sinon.stub(InternalMediaCoreModule, 'StatsAnalyzer').returns(statsAnalyzerStub);
|
|
2779
3231
|
|
|
2780
3232
|
await meeting.addMedia({
|
|
2781
3233
|
mediaSettings: {},
|
|
@@ -2789,8 +3241,8 @@ describe('plugin-meetings', () => {
|
|
|
2789
3241
|
it('LOCAL_MEDIA_STARTED triggers "meeting:media:local:start" event and sends metrics', async () => {
|
|
2790
3242
|
statsAnalyzerStub.emit(
|
|
2791
3243
|
{file: 'test', function: 'test'},
|
|
2792
|
-
|
|
2793
|
-
{
|
|
3244
|
+
StatsAnalyzerEventNames.LOCAL_MEDIA_STARTED,
|
|
3245
|
+
{mediaType: 'audio'}
|
|
2794
3246
|
);
|
|
2795
3247
|
|
|
2796
3248
|
assert.calledWith(
|
|
@@ -2802,7 +3254,7 @@ describe('plugin-meetings', () => {
|
|
|
2802
3254
|
},
|
|
2803
3255
|
EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
|
|
2804
3256
|
{
|
|
2805
|
-
|
|
3257
|
+
mediaType: 'audio',
|
|
2806
3258
|
}
|
|
2807
3259
|
);
|
|
2808
3260
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2817,8 +3269,8 @@ describe('plugin-meetings', () => {
|
|
|
2817
3269
|
it('LOCAL_MEDIA_STOPPED triggers the right metrics', async () => {
|
|
2818
3270
|
statsAnalyzerStub.emit(
|
|
2819
3271
|
{file: 'test', function: 'test'},
|
|
2820
|
-
|
|
2821
|
-
{
|
|
3272
|
+
StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED,
|
|
3273
|
+
{mediaType: 'video'}
|
|
2822
3274
|
);
|
|
2823
3275
|
|
|
2824
3276
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2833,8 +3285,8 @@ describe('plugin-meetings', () => {
|
|
|
2833
3285
|
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics', async () => {
|
|
2834
3286
|
statsAnalyzerStub.emit(
|
|
2835
3287
|
{file: 'test', function: 'test'},
|
|
2836
|
-
|
|
2837
|
-
{
|
|
3288
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
|
|
3289
|
+
{mediaType: 'video'}
|
|
2838
3290
|
);
|
|
2839
3291
|
|
|
2840
3292
|
assert.calledWith(
|
|
@@ -2846,7 +3298,7 @@ describe('plugin-meetings', () => {
|
|
|
2846
3298
|
},
|
|
2847
3299
|
EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
|
|
2848
3300
|
{
|
|
2849
|
-
|
|
3301
|
+
mediaType: 'video',
|
|
2850
3302
|
}
|
|
2851
3303
|
);
|
|
2852
3304
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2861,8 +3313,8 @@ describe('plugin-meetings', () => {
|
|
|
2861
3313
|
it('REMOTE_MEDIA_STOPPED triggers the right metrics', async () => {
|
|
2862
3314
|
statsAnalyzerStub.emit(
|
|
2863
3315
|
{file: 'test', function: 'test'},
|
|
2864
|
-
|
|
2865
|
-
{
|
|
3316
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
|
|
3317
|
+
{mediaType: 'audio'}
|
|
2866
3318
|
);
|
|
2867
3319
|
|
|
2868
3320
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2877,8 +3329,8 @@ describe('plugin-meetings', () => {
|
|
|
2877
3329
|
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics for share', async () => {
|
|
2878
3330
|
statsAnalyzerStub.emit(
|
|
2879
3331
|
{file: 'test', function: 'test'},
|
|
2880
|
-
|
|
2881
|
-
{
|
|
3332
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
|
|
3333
|
+
{mediaType: 'share'}
|
|
2882
3334
|
);
|
|
2883
3335
|
|
|
2884
3336
|
assert.calledWith(
|
|
@@ -2890,7 +3342,7 @@ describe('plugin-meetings', () => {
|
|
|
2890
3342
|
},
|
|
2891
3343
|
EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
|
|
2892
3344
|
{
|
|
2893
|
-
|
|
3345
|
+
mediaType: 'share',
|
|
2894
3346
|
}
|
|
2895
3347
|
);
|
|
2896
3348
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2913,8 +3365,8 @@ describe('plugin-meetings', () => {
|
|
|
2913
3365
|
it('REMOTE_MEDIA_STOPPED triggers the right metrics for share', async () => {
|
|
2914
3366
|
statsAnalyzerStub.emit(
|
|
2915
3367
|
{file: 'test', function: 'test'},
|
|
2916
|
-
|
|
2917
|
-
{
|
|
3368
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
|
|
3369
|
+
{mediaType: 'share'}
|
|
2918
3370
|
);
|
|
2919
3371
|
|
|
2920
3372
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2935,19 +3387,18 @@ describe('plugin-meetings', () => {
|
|
|
2935
3387
|
});
|
|
2936
3388
|
|
|
2937
3389
|
it('calls submitMQE correctly', async () => {
|
|
2938
|
-
const fakeData = {intervalMetadata: {bla: 'bla'}};
|
|
3390
|
+
const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
|
|
2939
3391
|
|
|
2940
3392
|
statsAnalyzerStub.emit(
|
|
2941
3393
|
{file: 'test', function: 'test'},
|
|
2942
|
-
|
|
2943
|
-
{data: fakeData
|
|
3394
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3395
|
+
{data: fakeData}
|
|
2944
3396
|
);
|
|
2945
3397
|
|
|
2946
3398
|
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
2947
3399
|
name: 'client.mediaquality.event',
|
|
2948
3400
|
options: {
|
|
2949
3401
|
meetingId: meeting.id,
|
|
2950
|
-
networkType: 'wifi',
|
|
2951
3402
|
},
|
|
2952
3403
|
payload: {
|
|
2953
3404
|
intervals: [fakeData],
|
|
@@ -2986,9 +3437,7 @@ describe('plugin-meetings', () => {
|
|
|
2986
3437
|
Media.createMediaConnection,
|
|
2987
3438
|
false,
|
|
2988
3439
|
meeting.getMediaConnectionDebugId(),
|
|
2989
|
-
webex,
|
|
2990
3440
|
meeting.id,
|
|
2991
|
-
meeting.correlationId,
|
|
2992
3441
|
sinon.match({
|
|
2993
3442
|
turnServerInfo: {
|
|
2994
3443
|
url: FAKE_TURN_URL,
|
|
@@ -3004,7 +3453,7 @@ describe('plugin-meetings', () => {
|
|
|
3004
3453
|
it('succeeds even if getDevices() throws', async () => {
|
|
3005
3454
|
meeting.meetingState = 'ACTIVE';
|
|
3006
3455
|
|
|
3007
|
-
sinon.stub(
|
|
3456
|
+
sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
|
|
3008
3457
|
|
|
3009
3458
|
await meeting.addMedia();
|
|
3010
3459
|
});
|
|
@@ -3021,7 +3470,7 @@ describe('plugin-meetings', () => {
|
|
|
3021
3470
|
clientErrorCode: MISSING_ROAP_ANSWER_CLIENT_CODE,
|
|
3022
3471
|
expectedErrorPayload: {
|
|
3023
3472
|
errorDescription: ERROR_DESCRIPTIONS.MISSING_ROAP_ANSWER,
|
|
3024
|
-
category: '
|
|
3473
|
+
category: 'media',
|
|
3025
3474
|
},
|
|
3026
3475
|
},
|
|
3027
3476
|
{
|
|
@@ -3040,10 +3489,18 @@ describe('plugin-meetings', () => {
|
|
|
3040
3489
|
clientErrorCode: ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
|
|
3041
3490
|
expectedErrorPayload: {
|
|
3042
3491
|
errorDescription: ERROR_DESCRIPTIONS.ICE_FAILED_WITH_TURN_TLS,
|
|
3043
|
-
category: '
|
|
3492
|
+
category: 'media',
|
|
3493
|
+
},
|
|
3494
|
+
},
|
|
3495
|
+
{
|
|
3496
|
+
clientErrorCode: ICE_AND_REACHABILITY_FAILED_CLIENT_CODE,
|
|
3497
|
+
unreachable: true,
|
|
3498
|
+
expectedErrorPayload: {
|
|
3499
|
+
errorDescription: ERROR_DESCRIPTIONS.ICE_AND_REACHABILITY_FAILED,
|
|
3500
|
+
category: 'expected',
|
|
3044
3501
|
},
|
|
3045
3502
|
},
|
|
3046
|
-
].forEach(({clientErrorCode, expectedErrorPayload}) => {
|
|
3503
|
+
].forEach(({clientErrorCode, expectedErrorPayload, unreachable}) => {
|
|
3047
3504
|
it(`should handle all ice failures correctly for ${clientErrorCode}`, async () => {
|
|
3048
3505
|
// setting the method to the real implementation
|
|
3049
3506
|
// because newMetrics is mocked completely in the webex-mock
|
|
@@ -3052,14 +3509,18 @@ describe('plugin-meetings', () => {
|
|
|
3052
3509
|
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
3053
3510
|
CD.getErrorPayloadForClientErrorCode;
|
|
3054
3511
|
|
|
3512
|
+
webex.meetings.reachability = {
|
|
3513
|
+
isWebexMediaBackendUnreachable: sinon.stub().resolves(unreachable || false),
|
|
3514
|
+
};
|
|
3515
|
+
|
|
3055
3516
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
3056
3517
|
.stub(CallDiagnosticUtils, 'generateClientErrorCodeForIceFailure')
|
|
3057
3518
|
.returns(clientErrorCode);
|
|
3058
3519
|
|
|
3059
3520
|
meeting.meetingState = 'ACTIVE';
|
|
3060
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
3061
|
-
|
|
3062
|
-
);
|
|
3521
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({
|
|
3522
|
+
iceConnected: false,
|
|
3523
|
+
});
|
|
3063
3524
|
|
|
3064
3525
|
let errorThrown = false;
|
|
3065
3526
|
|
|
@@ -3073,8 +3534,9 @@ describe('plugin-meetings', () => {
|
|
|
3073
3534
|
|
|
3074
3535
|
assert.calledOnceWithExactly(generateClientErrorCodeForIceFailureStub, {
|
|
3075
3536
|
signalingState: 'unknown',
|
|
3076
|
-
|
|
3537
|
+
iceConnected: false,
|
|
3077
3538
|
turnServerUsed: true,
|
|
3539
|
+
unreachable: unreachable || false,
|
|
3078
3540
|
});
|
|
3079
3541
|
|
|
3080
3542
|
const submitClientEventCalls = webex.internal.newMetrics.submitClientEvent.getCalls();
|
|
@@ -3162,7 +3624,7 @@ describe('plugin-meetings', () => {
|
|
|
3162
3624
|
|
|
3163
3625
|
let clock;
|
|
3164
3626
|
|
|
3165
|
-
beforeEach(() => {
|
|
3627
|
+
beforeEach(async () => {
|
|
3166
3628
|
clock = sinon.useFakeTimers();
|
|
3167
3629
|
|
|
3168
3630
|
sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
|
|
@@ -3171,15 +3633,20 @@ describe('plugin-meetings', () => {
|
|
|
3171
3633
|
meeting.config.deviceType = 'web';
|
|
3172
3634
|
meeting.isMultistream = isMultistream;
|
|
3173
3635
|
meeting.meetingState = 'ACTIVE';
|
|
3174
|
-
meeting.mediaId = 'fake media id';
|
|
3175
3636
|
meeting.selfUrl = 'selfUrl';
|
|
3176
3637
|
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
3177
|
-
meeting.mediaProperties.getCurrentConnectionInfo = sinon
|
|
3638
|
+
meeting.mediaProperties.getCurrentConnectionInfo = sinon
|
|
3639
|
+
.stub()
|
|
3640
|
+
.resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
|
|
3178
3641
|
meeting.setMercuryListener = sinon.stub();
|
|
3179
3642
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
3180
3643
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
3181
3644
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
3182
|
-
turnServerInfo: {
|
|
3645
|
+
turnServerInfo: {
|
|
3646
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
3647
|
+
username: 'turn user',
|
|
3648
|
+
password: 'turn password',
|
|
3649
|
+
},
|
|
3183
3650
|
turnDiscoverySkippedReason: 'reachability',
|
|
3184
3651
|
});
|
|
3185
3652
|
meeting.deferSDPAnswer = new Defer();
|
|
@@ -3192,7 +3659,18 @@ describe('plugin-meetings', () => {
|
|
|
3192
3659
|
// setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
|
|
3193
3660
|
expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
|
|
3194
3661
|
expectedMediaConnectionConfig = {
|
|
3195
|
-
iceServers: [
|
|
3662
|
+
iceServers: [
|
|
3663
|
+
{
|
|
3664
|
+
urls: 'turn:turn-server-url:5004?transport=tcp',
|
|
3665
|
+
username: 'turn user',
|
|
3666
|
+
credential: 'turn password',
|
|
3667
|
+
},
|
|
3668
|
+
{
|
|
3669
|
+
urls: 'turns:turn-server-url:443?transport=tcp',
|
|
3670
|
+
username: 'turn user',
|
|
3671
|
+
credential: 'turn password',
|
|
3672
|
+
},
|
|
3673
|
+
],
|
|
3196
3674
|
skipInactiveTransceivers: false,
|
|
3197
3675
|
requireH264: true,
|
|
3198
3676
|
sdpMunging: {
|
|
@@ -3261,16 +3739,28 @@ describe('plugin-meetings', () => {
|
|
|
3261
3739
|
};
|
|
3262
3740
|
|
|
3263
3741
|
roapMediaConnectionConstructorStub = sinon
|
|
3264
|
-
.stub(
|
|
3742
|
+
.stub(InternalMediaCoreModule, 'RoapMediaConnection')
|
|
3265
3743
|
.returns(fakeRoapMediaConnection);
|
|
3266
3744
|
|
|
3267
3745
|
multistreamRoapMediaConnectionConstructorStub = sinon
|
|
3268
|
-
.stub(
|
|
3746
|
+
.stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
|
|
3269
3747
|
.returns(fakeMultistreamRoapMediaConnection);
|
|
3270
3748
|
|
|
3271
3749
|
locusMediaRequestStub = sinon
|
|
3272
3750
|
.stub(WebexPlugin.prototype, 'request')
|
|
3273
3751
|
.resolves({body: {locus: {fullState: {}}}});
|
|
3752
|
+
|
|
3753
|
+
// setup some things and mocks so that the call to join() works
|
|
3754
|
+
// (we need to call join() because it creates the LocusMediaRequest instance
|
|
3755
|
+
// that's being tested in these tests)
|
|
3756
|
+
meeting.webex.meetings.registered = true;
|
|
3757
|
+
meeting.webex.internal.device.config = {};
|
|
3758
|
+
sinon.stub(MeetingUtil, 'joinMeeting').resolves({
|
|
3759
|
+
id: 'fake locus from mocked join request',
|
|
3760
|
+
locusUrl: 'fake locus url',
|
|
3761
|
+
mediaId: 'fake media id',
|
|
3762
|
+
});
|
|
3763
|
+
await meeting.join({enableMultistream: isMultistream});
|
|
3274
3764
|
});
|
|
3275
3765
|
|
|
3276
3766
|
afterEach(() => {
|
|
@@ -3299,13 +3789,14 @@ describe('plugin-meetings', () => {
|
|
|
3299
3789
|
|
|
3300
3790
|
for (let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx += 1) {
|
|
3301
3791
|
if (
|
|
3302
|
-
roapMediaConnectionToCheck.on.getCall(idx).args[0] ===
|
|
3792
|
+
roapMediaConnectionToCheck.on.getCall(idx).args[0] ===
|
|
3793
|
+
MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND
|
|
3303
3794
|
) {
|
|
3304
3795
|
return roapMediaConnectionToCheck.on.getCall(idx).args[1];
|
|
3305
3796
|
}
|
|
3306
3797
|
}
|
|
3307
3798
|
assert.fail(
|
|
3308
|
-
'listener for "roap:messageToSend" (
|
|
3799
|
+
'listener for "roap:messageToSend" (MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND) was not registered'
|
|
3309
3800
|
);
|
|
3310
3801
|
};
|
|
3311
3802
|
|
|
@@ -3746,13 +4237,13 @@ describe('plugin-meetings', () => {
|
|
|
3746
4237
|
await meeting.addMedia({
|
|
3747
4238
|
localStreams: {microphone: fakeMicrophoneStream},
|
|
3748
4239
|
audioEnabled: false,
|
|
3749
|
-
videoEnabled: false
|
|
4240
|
+
videoEnabled: false,
|
|
3750
4241
|
});
|
|
3751
4242
|
await simulateRoapOffer();
|
|
3752
4243
|
await simulateRoapOk();
|
|
3753
4244
|
|
|
3754
4245
|
assert.notCalled(handleDeviceLoggingSpy);
|
|
3755
|
-
})
|
|
4246
|
+
});
|
|
3756
4247
|
|
|
3757
4248
|
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
3758
4249
|
await meeting.addMedia({audioEnabled: false});
|
|
@@ -5107,7 +5598,7 @@ describe('plugin-meetings', () => {
|
|
|
5107
5598
|
|
|
5108
5599
|
describe('#fetchMeetingInfo', () => {
|
|
5109
5600
|
const FAKE_DESTINATION = 'something@somecompany.com';
|
|
5110
|
-
const FAKE_TYPE =
|
|
5601
|
+
const FAKE_TYPE = DESTINATION_TYPE.SIP_URI;
|
|
5111
5602
|
const FAKE_TIMEOUT_FETCHMEETINGINFO_ID = '123456';
|
|
5112
5603
|
const FAKE_PASSWORD = '123abc';
|
|
5113
5604
|
const FAKE_CAPTCHA_CODE = 'a1b2c3XYZ';
|
|
@@ -5542,7 +6033,7 @@ describe('plugin-meetings', () => {
|
|
|
5542
6033
|
const FAKE_PASSWORD = '123456';
|
|
5543
6034
|
const FAKE_CAPTCHA_CODE = '654321';
|
|
5544
6035
|
const FAKE_DESTINATION = 'something@somecompany.com';
|
|
5545
|
-
const FAKE_TYPE =
|
|
6036
|
+
const FAKE_TYPE = DESTINATION_TYPE.SIP_URI;
|
|
5546
6037
|
const FAKE_INSTALLED_ORG_ID = '123456';
|
|
5547
6038
|
const FAKE_MEETING_INFO_LOOKUP_URL = 'meetingLookupUrl';
|
|
5548
6039
|
|
|
@@ -6187,14 +6678,14 @@ describe('plugin-meetings', () => {
|
|
|
6187
6678
|
beforeEach(() => {
|
|
6188
6679
|
sandbox = sinon.createSandbox();
|
|
6189
6680
|
meeting.statsAnalyzer = {
|
|
6190
|
-
stopAnalyzer: sinon.stub().returns(Promise.resolve())
|
|
6681
|
+
stopAnalyzer: sinon.stub().returns(Promise.resolve()),
|
|
6191
6682
|
};
|
|
6192
6683
|
|
|
6193
6684
|
meeting.reconnectionManager = {
|
|
6194
|
-
cleanUp: sinon.stub()
|
|
6685
|
+
cleanUp: sinon.stub(),
|
|
6195
6686
|
};
|
|
6196
6687
|
|
|
6197
|
-
meeting.cleanupLocalStreams=sinon.stub();
|
|
6688
|
+
meeting.cleanupLocalStreams = sinon.stub();
|
|
6198
6689
|
meeting.closeRemoteStreams = sinon.stub().returns(Promise.resolve());
|
|
6199
6690
|
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
6200
6691
|
meeting.unsetRemoteStreams = sinon.stub();
|
|
@@ -6275,7 +6766,6 @@ describe('plugin-meetings', () => {
|
|
|
6275
6766
|
},
|
|
6276
6767
|
'SELF_OBSERVING'
|
|
6277
6768
|
);
|
|
6278
|
-
|
|
6279
6769
|
|
|
6280
6770
|
// Verify that the event handler behaves as expected
|
|
6281
6771
|
expect(meeting.statsAnalyzer.stopAnalyzer.calledOnce).to.be.true;
|
|
@@ -6288,11 +6778,13 @@ describe('plugin-meetings', () => {
|
|
|
6288
6778
|
expect(meeting.unsetPeerConnections.calledOnce).to.be.true;
|
|
6289
6779
|
expect(meeting.reconnectionManager.cleanUp.calledOnce).to.be.true;
|
|
6290
6780
|
expect(meeting.mediaProperties.setMediaDirection.calledOnce).to.be.true;
|
|
6291
|
-
expect(
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6781
|
+
expect(
|
|
6782
|
+
meeting.addMedia.calledOnceWithExactly({
|
|
6783
|
+
audioEnabled: false,
|
|
6784
|
+
videoEnabled: false,
|
|
6785
|
+
shareVideoEnabled: true,
|
|
6786
|
+
})
|
|
6787
|
+
).to.be.true;
|
|
6296
6788
|
await testUtils.flushPromises();
|
|
6297
6789
|
assert.equal(meeting.isMoveToInProgress, false);
|
|
6298
6790
|
});
|
|
@@ -7079,6 +7571,12 @@ describe('plugin-meetings', () => {
|
|
|
7079
7571
|
id: 'stream',
|
|
7080
7572
|
getTracks: () => [{id: 'track', addEventListener: sinon.stub()}],
|
|
7081
7573
|
};
|
|
7574
|
+
const simulateConnectionStateChange = (newState) => {
|
|
7575
|
+
meeting.mediaProperties.webrtcMediaConnection.getConnectionState = sinon
|
|
7576
|
+
.stub()
|
|
7577
|
+
.returns(newState);
|
|
7578
|
+
eventListeners[MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED]();
|
|
7579
|
+
};
|
|
7082
7580
|
|
|
7083
7581
|
beforeEach(() => {
|
|
7084
7582
|
eventListeners = {};
|
|
@@ -7088,23 +7586,29 @@ describe('plugin-meetings', () => {
|
|
|
7088
7586
|
on: sinon.stub().callsFake((event, listener) => {
|
|
7089
7587
|
eventListeners[event] = listener;
|
|
7090
7588
|
}),
|
|
7589
|
+
getConnectionState: sinon.stub().returns(ConnectionState.New),
|
|
7091
7590
|
};
|
|
7092
7591
|
MediaUtil.createMediaStream.returns(fakeStream);
|
|
7093
7592
|
});
|
|
7094
7593
|
|
|
7095
7594
|
it('should register for all the correct RoapMediaConnection events', () => {
|
|
7096
7595
|
meeting.setupMediaConnectionListeners();
|
|
7097
|
-
assert.isFunction(eventListeners[
|
|
7098
|
-
assert.isFunction(eventListeners[
|
|
7099
|
-
assert.isFunction(eventListeners[
|
|
7100
|
-
assert.isFunction(eventListeners[
|
|
7101
|
-
assert.isFunction(eventListeners[
|
|
7102
|
-
assert.isFunction(
|
|
7596
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_STARTED]);
|
|
7597
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_DONE]);
|
|
7598
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_FAILURE]);
|
|
7599
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]);
|
|
7600
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]);
|
|
7601
|
+
assert.isFunction(
|
|
7602
|
+
eventListeners[MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED]
|
|
7603
|
+
);
|
|
7604
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ICE_CONNECTION_STATE_CHANGED]);
|
|
7605
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]);
|
|
7606
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]);
|
|
7103
7607
|
});
|
|
7104
7608
|
|
|
7105
7609
|
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
7106
7610
|
meeting.setupMediaConnectionListeners();
|
|
7107
|
-
eventListeners[
|
|
7611
|
+
eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]({
|
|
7108
7612
|
track: 'track',
|
|
7109
7613
|
type: RemoteTrackType.AUDIO,
|
|
7110
7614
|
});
|
|
@@ -7114,7 +7618,7 @@ describe('plugin-meetings', () => {
|
|
|
7114
7618
|
stream: fakeStream,
|
|
7115
7619
|
});
|
|
7116
7620
|
|
|
7117
|
-
eventListeners[
|
|
7621
|
+
eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]({
|
|
7118
7622
|
track: 'track',
|
|
7119
7623
|
type: RemoteTrackType.VIDEO,
|
|
7120
7624
|
});
|
|
@@ -7124,7 +7628,7 @@ describe('plugin-meetings', () => {
|
|
|
7124
7628
|
stream: fakeStream,
|
|
7125
7629
|
});
|
|
7126
7630
|
|
|
7127
|
-
eventListeners[
|
|
7631
|
+
eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]({
|
|
7128
7632
|
track: 'track',
|
|
7129
7633
|
type: RemoteTrackType.SCREENSHARE_VIDEO,
|
|
7130
7634
|
});
|
|
@@ -7135,13 +7639,78 @@ describe('plugin-meetings', () => {
|
|
|
7135
7639
|
});
|
|
7136
7640
|
});
|
|
7137
7641
|
|
|
7642
|
+
describe('should react on a ICE_CANDIDATE event', () => {
|
|
7643
|
+
beforeEach(() => {
|
|
7644
|
+
meeting.setupMediaConnectionListeners();
|
|
7645
|
+
});
|
|
7646
|
+
|
|
7647
|
+
it('should collect ice candidates', () => {
|
|
7648
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: 'candidate'});
|
|
7649
|
+
|
|
7650
|
+
assert.equal(meeting.iceCandidatesCount, 1);
|
|
7651
|
+
});
|
|
7652
|
+
|
|
7653
|
+
it('should not collect null ice candidates', () => {
|
|
7654
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: null});
|
|
7655
|
+
|
|
7656
|
+
assert.equal(meeting.iceCandidatesCount, 0);
|
|
7657
|
+
});
|
|
7658
|
+
});
|
|
7659
|
+
|
|
7660
|
+
describe('should react on a ICE_CANDIDATE_ERROR event', () => {
|
|
7661
|
+
beforeEach(() => {
|
|
7662
|
+
meeting.setupMediaConnectionListeners();
|
|
7663
|
+
});
|
|
7664
|
+
|
|
7665
|
+
it('should not collect skipped ice candidates error', () => {
|
|
7666
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({
|
|
7667
|
+
error: {
|
|
7668
|
+
errorCode: 600,
|
|
7669
|
+
errorText: 'Address not associated with the desired network interface.',
|
|
7670
|
+
},
|
|
7671
|
+
});
|
|
7672
|
+
|
|
7673
|
+
assert.equal(meeting.iceCandidateErrors.size, 0);
|
|
7674
|
+
});
|
|
7675
|
+
|
|
7676
|
+
it('should collect valid ice candidates error', () => {
|
|
7677
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({
|
|
7678
|
+
error: {errorCode: 701, errorText: ''},
|
|
7679
|
+
});
|
|
7680
|
+
|
|
7681
|
+
assert.equal(meeting.iceCandidateErrors.size, 1);
|
|
7682
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7683
|
+
});
|
|
7684
|
+
|
|
7685
|
+
it('should increment counter if same valid ice candidates error collected', () => {
|
|
7686
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({
|
|
7687
|
+
error: {errorCode: 701, errorText: ''},
|
|
7688
|
+
});
|
|
7689
|
+
|
|
7690
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({
|
|
7691
|
+
error: {errorCode: 701, errorText: 'STUN host lookup received error.'},
|
|
7692
|
+
});
|
|
7693
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({
|
|
7694
|
+
error: {errorCode: 701, errorText: 'STUN host lookup received error.'},
|
|
7695
|
+
});
|
|
7696
|
+
|
|
7697
|
+
assert.equal(meeting.iceCandidateErrors.size, 2);
|
|
7698
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7699
|
+
assert.equal(meeting.iceCandidateErrors.get('701_'), 1);
|
|
7700
|
+
assert.equal(
|
|
7701
|
+
meeting.iceCandidateErrors.has('701_stun_host_lookup_received_error'),
|
|
7702
|
+
true
|
|
7703
|
+
);
|
|
7704
|
+
assert.equal(meeting.iceCandidateErrors.get('701_stun_host_lookup_received_error'), 2);
|
|
7705
|
+
});
|
|
7706
|
+
});
|
|
7707
|
+
|
|
7138
7708
|
describe('CONNECTION_STATE_CHANGED event when state = "Connecting"', () => {
|
|
7139
7709
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = true', () => {
|
|
7140
7710
|
meeting.hasMediaConnectionConnectedAtLeastOnce = true;
|
|
7141
7711
|
meeting.setupMediaConnectionListeners();
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
});
|
|
7712
|
+
|
|
7713
|
+
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
7145
7714
|
|
|
7146
7715
|
assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
7147
7716
|
});
|
|
@@ -7149,9 +7718,8 @@ describe('plugin-meetings', () => {
|
|
|
7149
7718
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = false', () => {
|
|
7150
7719
|
meeting.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
7151
7720
|
meeting.setupMediaConnectionListeners();
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
});
|
|
7721
|
+
|
|
7722
|
+
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
7155
7723
|
|
|
7156
7724
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7157
7725
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7177,6 +7745,7 @@ describe('plugin-meetings', () => {
|
|
|
7177
7745
|
on: sinon.stub().callsFake((event, listener) => {
|
|
7178
7746
|
eventListeners[event] = listener;
|
|
7179
7747
|
}),
|
|
7748
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
7180
7749
|
};
|
|
7181
7750
|
};
|
|
7182
7751
|
|
|
@@ -7230,9 +7799,7 @@ describe('plugin-meetings', () => {
|
|
|
7230
7799
|
assert.equal(meeting.hasMediaConnectionConnectedAtLeastOnce, false);
|
|
7231
7800
|
|
|
7232
7801
|
// simulate first connection success
|
|
7233
|
-
|
|
7234
|
-
state: 'Connected',
|
|
7235
|
-
});
|
|
7802
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7236
7803
|
checkExpectedSpies({
|
|
7237
7804
|
icePhase: 'JOIN_MEETING_FINAL',
|
|
7238
7805
|
setNetworkStatusCallParams: [NETWORK_STATUS.CONNECTED],
|
|
@@ -7242,12 +7809,9 @@ describe('plugin-meetings', () => {
|
|
|
7242
7809
|
// now simulate short connection loss, client.ice.end is not sent a second time as hasMediaConnectionConnectedAtLeastOnce = true
|
|
7243
7810
|
resetSpies();
|
|
7244
7811
|
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
eventListeners[Event.CONNECTION_STATE_CHANGED]({
|
|
7249
|
-
state: 'Connected',
|
|
7250
|
-
});
|
|
7812
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7813
|
+
|
|
7814
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7251
7815
|
|
|
7252
7816
|
checkExpectedSpies({
|
|
7253
7817
|
setNetworkStatusCallParams: [NETWORK_STATUS.DISCONNECTED, NETWORK_STATUS.CONNECTED],
|
|
@@ -7255,12 +7819,9 @@ describe('plugin-meetings', () => {
|
|
|
7255
7819
|
|
|
7256
7820
|
resetSpies();
|
|
7257
7821
|
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
eventListeners[Event.CONNECTION_STATE_CHANGED]({
|
|
7262
|
-
state: 'Connected',
|
|
7263
|
-
});
|
|
7822
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7823
|
+
|
|
7824
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7264
7825
|
});
|
|
7265
7826
|
});
|
|
7266
7827
|
|
|
@@ -7282,9 +7843,8 @@ describe('plugin-meetings', () => {
|
|
|
7282
7843
|
|
|
7283
7844
|
const mockDisconnectedEvent = () => {
|
|
7284
7845
|
meeting.setupMediaConnectionListeners();
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
});
|
|
7846
|
+
|
|
7847
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7288
7848
|
};
|
|
7289
7849
|
|
|
7290
7850
|
const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => {
|
|
@@ -7348,9 +7908,8 @@ describe('plugin-meetings', () => {
|
|
|
7348
7908
|
describe('CONNECTION_STATE_CHANGED event when state = "Failed"', () => {
|
|
7349
7909
|
const mockFailedEvent = () => {
|
|
7350
7910
|
meeting.setupMediaConnectionListeners();
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
});
|
|
7911
|
+
|
|
7912
|
+
simulateConnectionStateChange(ConnectionState.Failed);
|
|
7354
7913
|
};
|
|
7355
7914
|
|
|
7356
7915
|
const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => {
|
|
@@ -7432,7 +7991,7 @@ describe('plugin-meetings', () => {
|
|
|
7432
7991
|
cause: {name: fakeRootCauseName},
|
|
7433
7992
|
});
|
|
7434
7993
|
|
|
7435
|
-
eventListeners[
|
|
7994
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7436
7995
|
|
|
7437
7996
|
checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
|
|
7438
7997
|
checkBehavioralMetricSent(
|
|
@@ -7449,7 +8008,7 @@ describe('plugin-meetings', () => {
|
|
|
7449
8008
|
cause: {name: fakeRootCauseName},
|
|
7450
8009
|
});
|
|
7451
8010
|
|
|
7452
|
-
eventListeners[
|
|
8011
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7453
8012
|
|
|
7454
8013
|
checkMetricSent('client.media-engine.remote-sdp-received', fakeError);
|
|
7455
8014
|
checkBehavioralMetricSent(
|
|
@@ -7466,7 +8025,7 @@ describe('plugin-meetings', () => {
|
|
|
7466
8025
|
cause: {name: fakeRootCauseName},
|
|
7467
8026
|
});
|
|
7468
8027
|
|
|
7469
|
-
eventListeners[
|
|
8028
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7470
8029
|
|
|
7471
8030
|
checkMetricSent('client.media-engine.remote-sdp-received', fakeError);
|
|
7472
8031
|
checkBehavioralMetricSent(
|
|
@@ -7481,7 +8040,7 @@ describe('plugin-meetings', () => {
|
|
|
7481
8040
|
// SdpError is usually without a cause
|
|
7482
8041
|
const fakeError = new Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
|
|
7483
8042
|
|
|
7484
|
-
eventListeners[
|
|
8043
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7485
8044
|
|
|
7486
8045
|
checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
|
|
7487
8046
|
// expectedMetadataType is the error name in this case
|
|
@@ -7499,7 +8058,7 @@ describe('plugin-meetings', () => {
|
|
|
7499
8058
|
name: fakeErrorName,
|
|
7500
8059
|
});
|
|
7501
8060
|
|
|
7502
|
-
eventListeners[
|
|
8061
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7503
8062
|
|
|
7504
8063
|
checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
|
|
7505
8064
|
// expectedMetadataType is the error name in this case
|
|
@@ -7525,7 +8084,7 @@ describe('plugin-meetings', () => {
|
|
|
7525
8084
|
};
|
|
7526
8085
|
meeting.sdpResponseTimer = '1234';
|
|
7527
8086
|
|
|
7528
|
-
eventListeners[
|
|
8087
|
+
eventListeners[MediaConnectionEventNames.REMOTE_SDP_ANSWER_PROCESSED]();
|
|
7529
8088
|
|
|
7530
8089
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7531
8090
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7553,7 +8112,7 @@ describe('plugin-meetings', () => {
|
|
|
7553
8112
|
it('handles LOCAL_SDP_OFFER_GENERATED correctly', () => {
|
|
7554
8113
|
assert.equal(meeting.deferSDPAnswer, undefined);
|
|
7555
8114
|
|
|
7556
|
-
eventListeners[
|
|
8115
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
7557
8116
|
|
|
7558
8117
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7559
8118
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7565,7 +8124,7 @@ describe('plugin-meetings', () => {
|
|
|
7565
8124
|
});
|
|
7566
8125
|
|
|
7567
8126
|
it('handles LOCAL_SDP_ANSWER_GENERATED correctly', () => {
|
|
7568
|
-
eventListeners[
|
|
8127
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_ANSWER_GENERATED]();
|
|
7569
8128
|
|
|
7570
8129
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7571
8130
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7575,7 +8134,7 @@ describe('plugin-meetings', () => {
|
|
|
7575
8134
|
});
|
|
7576
8135
|
});
|
|
7577
8136
|
|
|
7578
|
-
describe('handles
|
|
8137
|
+
describe('handles MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND correctly', () => {
|
|
7579
8138
|
let sendRoapOKStub;
|
|
7580
8139
|
let sendRoapMediaRequestStub;
|
|
7581
8140
|
let sendRoapAnswerStub;
|
|
@@ -7593,7 +8152,7 @@ describe('plugin-meetings', () => {
|
|
|
7593
8152
|
});
|
|
7594
8153
|
|
|
7595
8154
|
it('handles OK message correctly', () => {
|
|
7596
|
-
eventListeners[
|
|
8155
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7597
8156
|
roapMessage: {messageType: 'OK', seq: 1},
|
|
7598
8157
|
});
|
|
7599
8158
|
|
|
@@ -7608,7 +8167,7 @@ describe('plugin-meetings', () => {
|
|
|
7608
8167
|
it('handles OFFER message correctly (no answer in the http response)', async () => {
|
|
7609
8168
|
sinon.stub(meeting, 'roapMessageReceived');
|
|
7610
8169
|
|
|
7611
|
-
eventListeners[
|
|
8170
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7612
8171
|
roapMessage: {
|
|
7613
8172
|
messageType: 'OFFER',
|
|
7614
8173
|
seq: 1,
|
|
@@ -7634,7 +8193,7 @@ describe('plugin-meetings', () => {
|
|
|
7634
8193
|
sendRoapMediaRequestStub.resolves({roapAnswer: fakeAnswer});
|
|
7635
8194
|
sinon.stub(meeting, 'roapMessageReceived');
|
|
7636
8195
|
|
|
7637
|
-
eventListeners[
|
|
8196
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7638
8197
|
roapMessage: {
|
|
7639
8198
|
messageType: 'OFFER',
|
|
7640
8199
|
seq: 1,
|
|
@@ -7656,14 +8215,20 @@ describe('plugin-meetings', () => {
|
|
|
7656
8215
|
});
|
|
7657
8216
|
|
|
7658
8217
|
it('handles OFFER message correctly when request fails', async () => {
|
|
8218
|
+
const fakeError = new Error('fake error');
|
|
7659
8219
|
const clock = sinon.useFakeTimers();
|
|
7660
8220
|
sinon.spy(clock, 'clearTimeout');
|
|
7661
8221
|
meeting.deferSDPAnswer = {reject: sinon.stub()};
|
|
7662
8222
|
meeting.sdpResponseTimer = '1234';
|
|
7663
|
-
sendRoapMediaRequestStub.rejects();
|
|
8223
|
+
sendRoapMediaRequestStub.rejects(fakeError);
|
|
7664
8224
|
sinon.stub(meeting, 'roapMessageReceived');
|
|
8225
|
+
const getErrorPayloadForClientErrorCodeStub =
|
|
8226
|
+
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
8227
|
+
sinon
|
|
8228
|
+
.stub()
|
|
8229
|
+
.callsFake(({clientErrorCode}) => ({errorCode: clientErrorCode, fatal: true})));
|
|
7665
8230
|
|
|
7666
|
-
eventListeners[
|
|
8231
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7667
8232
|
roapMessage: {
|
|
7668
8233
|
messageType: 'OFFER',
|
|
7669
8234
|
seq: 1,
|
|
@@ -7686,10 +8251,25 @@ describe('plugin-meetings', () => {
|
|
|
7686
8251
|
assert.calledOnce(clock.clearTimeout);
|
|
7687
8252
|
assert.calledWith(clock.clearTimeout, '1234');
|
|
7688
8253
|
assert.equal(meeting.sdpResponseTimer, undefined);
|
|
8254
|
+
|
|
8255
|
+
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
8256
|
+
clientErrorCode: 2007,
|
|
8257
|
+
});
|
|
8258
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8259
|
+
name: 'client.media-engine.remote-sdp-received',
|
|
8260
|
+
payload: {
|
|
8261
|
+
canProceed: false,
|
|
8262
|
+
errors: [{errorCode: 2007, fatal: true}],
|
|
8263
|
+
},
|
|
8264
|
+
options: {
|
|
8265
|
+
meetingId: meeting.id,
|
|
8266
|
+
rawError: fakeError,
|
|
8267
|
+
},
|
|
8268
|
+
});
|
|
7689
8269
|
});
|
|
7690
8270
|
|
|
7691
8271
|
it('handles ANSWER message correctly', () => {
|
|
7692
|
-
eventListeners[
|
|
8272
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7693
8273
|
roapMessage: {
|
|
7694
8274
|
messageType: 'ANSWER',
|
|
7695
8275
|
seq: 10,
|
|
@@ -7710,7 +8290,7 @@ describe('plugin-meetings', () => {
|
|
|
7710
8290
|
it('sends metrics if fails to send roap ANSWER message', async () => {
|
|
7711
8291
|
sendRoapAnswerStub.rejects(new Error('sending answer failed'));
|
|
7712
8292
|
|
|
7713
|
-
await eventListeners[
|
|
8293
|
+
await eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7714
8294
|
roapMessage: {
|
|
7715
8295
|
messageType: 'ANSWER',
|
|
7716
8296
|
seq: 10,
|
|
@@ -7734,7 +8314,7 @@ describe('plugin-meetings', () => {
|
|
|
7734
8314
|
|
|
7735
8315
|
[ErrorType.CONFLICT, ErrorType.DOUBLECONFLICT].forEach((errorType) =>
|
|
7736
8316
|
it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
|
|
7737
|
-
eventListeners[
|
|
8317
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7738
8318
|
roapMessage: {
|
|
7739
8319
|
messageType: 'ERROR',
|
|
7740
8320
|
seq: 10,
|
|
@@ -7765,7 +8345,7 @@ describe('plugin-meetings', () => {
|
|
|
7765
8345
|
);
|
|
7766
8346
|
|
|
7767
8347
|
it('handles ERROR message indicating other errors correctly', () => {
|
|
7768
|
-
eventListeners[
|
|
8348
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7769
8349
|
roapMessage: {
|
|
7770
8350
|
messageType: 'ERROR',
|
|
7771
8351
|
seq: 10,
|
|
@@ -7793,8 +8373,12 @@ describe('plugin-meetings', () => {
|
|
|
7793
8373
|
});
|
|
7794
8374
|
|
|
7795
8375
|
it('registers for audio and video source count changed', () => {
|
|
7796
|
-
assert.isFunction(
|
|
7797
|
-
|
|
8376
|
+
assert.isFunction(
|
|
8377
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED]
|
|
8378
|
+
);
|
|
8379
|
+
assert.isFunction(
|
|
8380
|
+
eventListeners[MediaConnectionEventNames.AUDIO_SOURCES_COUNT_CHANGED]
|
|
8381
|
+
);
|
|
7798
8382
|
});
|
|
7799
8383
|
|
|
7800
8384
|
it('forwards the VIDEO_SOURCES_COUNT_CHANGED event as "media:remoteVideoSourceCountChanged"', () => {
|
|
@@ -7804,7 +8388,7 @@ describe('plugin-meetings', () => {
|
|
|
7804
8388
|
|
|
7805
8389
|
sinon.stub(meeting.mediaRequestManagers.video, 'setNumCurrentSources');
|
|
7806
8390
|
|
|
7807
|
-
eventListeners[
|
|
8391
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED](
|
|
7808
8392
|
numTotalSources,
|
|
7809
8393
|
numLiveSources,
|
|
7810
8394
|
mediaContent
|
|
@@ -7828,7 +8412,7 @@ describe('plugin-meetings', () => {
|
|
|
7828
8412
|
const numLiveSources = 2;
|
|
7829
8413
|
const mediaContent = 'MAIN';
|
|
7830
8414
|
|
|
7831
|
-
eventListeners[
|
|
8415
|
+
eventListeners[MediaConnectionEventNames.AUDIO_SOURCES_COUNT_CHANGED](
|
|
7832
8416
|
numTotalSources,
|
|
7833
8417
|
numLiveSources,
|
|
7834
8418
|
mediaContent
|
|
@@ -7856,7 +8440,7 @@ describe('plugin-meetings', () => {
|
|
|
7856
8440
|
'setNumCurrentSources'
|
|
7857
8441
|
);
|
|
7858
8442
|
|
|
7859
|
-
eventListeners[
|
|
8443
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED](
|
|
7860
8444
|
numTotalSources,
|
|
7861
8445
|
numLiveSources,
|
|
7862
8446
|
'MAIN'
|
|
@@ -7874,7 +8458,7 @@ describe('plugin-meetings', () => {
|
|
|
7874
8458
|
'setNumCurrentSources'
|
|
7875
8459
|
);
|
|
7876
8460
|
|
|
7877
|
-
eventListeners[
|
|
8461
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED](
|
|
7878
8462
|
numTotalSources,
|
|
7879
8463
|
numLiveSources,
|
|
7880
8464
|
'SLIDES'
|
|
@@ -7904,6 +8488,9 @@ describe('plugin-meetings', () => {
|
|
|
7904
8488
|
it('listens to the self admitted guest event', (done) => {
|
|
7905
8489
|
meeting.stopKeepAlive = sinon.stub();
|
|
7906
8490
|
meeting.updateLLMConnection = sinon.stub();
|
|
8491
|
+
meeting.rtcMetrics = {
|
|
8492
|
+
sendNextMetrics: sinon.stub(),
|
|
8493
|
+
};
|
|
7907
8494
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
7908
8495
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
7909
8496
|
assert.calledThrice(TriggerProxy.trigger);
|
|
@@ -7915,6 +8502,8 @@ describe('plugin-meetings', () => {
|
|
|
7915
8502
|
{payload: test1}
|
|
7916
8503
|
);
|
|
7917
8504
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
8505
|
+
assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
|
|
8506
|
+
|
|
7918
8507
|
done();
|
|
7919
8508
|
});
|
|
7920
8509
|
|
|
@@ -9781,6 +10370,7 @@ describe('plugin-meetings', () => {
|
|
|
9781
10370
|
beforeEach(() => {
|
|
9782
10371
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
9783
10372
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
10373
|
+
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
9784
10374
|
webex.internal.llm.registerAndConnect = sinon
|
|
9785
10375
|
.stub()
|
|
9786
10376
|
.returns(Promise.resolve('something'));
|
|
@@ -9808,6 +10398,7 @@ describe('plugin-meetings', () => {
|
|
|
9808
10398
|
meeting.joinedWith = {state: 'JOINED'};
|
|
9809
10399
|
webex.internal.llm.isConnected.returns(true);
|
|
9810
10400
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
10401
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9811
10402
|
|
|
9812
10403
|
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
9813
10404
|
|
|
@@ -9844,6 +10435,7 @@ describe('plugin-meetings', () => {
|
|
|
9844
10435
|
meeting.joinedWith = {state: 'JOINED'};
|
|
9845
10436
|
webex.internal.llm.isConnected.returns(true);
|
|
9846
10437
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
10438
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9847
10439
|
|
|
9848
10440
|
meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
|
|
9849
10441
|
|
|
@@ -9869,6 +10461,36 @@ describe('plugin-meetings', () => {
|
|
|
9869
10461
|
);
|
|
9870
10462
|
});
|
|
9871
10463
|
|
|
10464
|
+
it('disconnects if first if the data channel url has changed', async () => {
|
|
10465
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
10466
|
+
webex.internal.llm.isConnected.returns(true);
|
|
10467
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
10468
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
10469
|
+
|
|
10470
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a different datachannel url'}};
|
|
10471
|
+
|
|
10472
|
+
const result = await meeting.updateLLMConnection();
|
|
10473
|
+
|
|
10474
|
+
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
10475
|
+
assert.calledWith(
|
|
10476
|
+
webex.internal.llm.registerAndConnect,
|
|
10477
|
+
'a url',
|
|
10478
|
+
'a different datachannel url'
|
|
10479
|
+
);
|
|
10480
|
+
assert.equal(result, 'something');
|
|
10481
|
+
assert.calledWithExactly(
|
|
10482
|
+
meeting.webex.internal.llm.off,
|
|
10483
|
+
'event:relay.event',
|
|
10484
|
+
meeting.processRelayEvent
|
|
10485
|
+
);
|
|
10486
|
+
assert.calledTwice(meeting.webex.internal.llm.off);
|
|
10487
|
+
assert.calledOnceWithExactly(
|
|
10488
|
+
meeting.webex.internal.llm.on,
|
|
10489
|
+
'event:relay.event',
|
|
10490
|
+
meeting.processRelayEvent
|
|
10491
|
+
);
|
|
10492
|
+
});
|
|
10493
|
+
|
|
9872
10494
|
it('disconnects when the state is not JOINED', async () => {
|
|
9873
10495
|
meeting.joinedWith = {state: 'any other state'};
|
|
9874
10496
|
webex.internal.llm.isConnected.returns(true);
|