@webex/plugin-meetings 3.3.1-next.4 → 3.3.1-next.40
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/media/MediaConnectionAwaiter.js +70 -15
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +67 -0
- package/dist/meeting/connectionStateHandler.js.map +1 -0
- package/dist/meeting/index.js +552 -357
- 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/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 +37 -33
- 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 +415 -56
- package/dist/reachability/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 +27 -7
- 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 +4 -3
- 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 +93 -2
- 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/media/MediaConnectionAwaiter.ts +89 -14
- package/src/meeting/connectionStateHandler.ts +65 -0
- package/src/meeting/index.ts +526 -292
- package/src/meeting/locusMediaRequest.ts +5 -0
- 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 +39 -40
- 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 +316 -27
- package/test/unit/spec/breakouts/index.ts +51 -32
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
- package/test/unit/spec/media/index.ts +8 -9
- package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
- package/test/unit/spec/meeting/index.js +643 -140
- package/test/unit/spec/meeting/locusMediaRequest.ts +7 -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 +14 -0
- 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 +1153 -84
- package/test/unit/spec/rtcMetrics/index.ts +1 -0
- package/dist/mediaQualityMetrics/config.js +0 -321
- package/dist/mediaQualityMetrics/config.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/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/statsAnalyzer/global.ts +0 -37
- package/src/statsAnalyzer/index.ts +0 -1318
- package/src/statsAnalyzer/mqaUtil.ts +0 -463
- package/test/unit/spec/stats-analyzer/index.js +0 -1819
|
@@ -4,11 +4,11 @@
|
|
|
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
8
|
import StateMachine from 'javascript-state-machine';
|
|
9
9
|
import uuid from 'uuid';
|
|
10
10
|
import {assert, expect} from '@webex/test-helper-chai';
|
|
11
|
-
import {Credentials,
|
|
11
|
+
import {Credentials, WebexPlugin} from '@webex/webex-core';
|
|
12
12
|
import Support from '@webex/internal-plugin-support';
|
|
13
13
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
14
14
|
import StaticConfig from '@webex/plugin-meetings/src/common/config';
|
|
@@ -21,31 +21,28 @@ import {
|
|
|
21
21
|
PASSWORD_STATUS,
|
|
22
22
|
EVENTS,
|
|
23
23
|
EVENT_TRIGGERS,
|
|
24
|
-
|
|
25
|
-
_MEETING_ID_,
|
|
24
|
+
DESTINATION_TYPE,
|
|
26
25
|
MEETING_REMOVED_REASON,
|
|
27
26
|
LOCUSINFO,
|
|
28
27
|
ICE_AND_DTLS_CONNECTION_TIMEOUT,
|
|
29
28
|
DISPLAY_HINTS,
|
|
30
29
|
SELF_POLICY,
|
|
31
30
|
IP_VERSION,
|
|
32
|
-
ERROR_DICTIONARY,
|
|
33
31
|
NETWORK_STATUS,
|
|
34
32
|
ONLINE,
|
|
35
33
|
OFFLINE,
|
|
36
|
-
|
|
34
|
+
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
37
35
|
} from '@webex/plugin-meetings/src/constants';
|
|
38
|
-
import * as InternalMediaCoreModule from '@webex/internal-media-core';
|
|
39
36
|
import {
|
|
40
37
|
ConnectionState,
|
|
41
|
-
|
|
38
|
+
MediaConnectionEventNames,
|
|
39
|
+
StatsAnalyzerEventNames,
|
|
42
40
|
Errors,
|
|
43
41
|
ErrorType,
|
|
44
42
|
RemoteTrackType,
|
|
45
43
|
MediaType,
|
|
46
44
|
} from '@webex/internal-media-core';
|
|
47
45
|
import {LocalStreamEventNames} from '@webex/media-helpers';
|
|
48
|
-
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
49
46
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
50
47
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
51
48
|
import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
@@ -72,6 +69,7 @@ import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaR
|
|
|
72
69
|
import * as ReceiveSlotManagerModule from '@webex/plugin-meetings/src/multistream/receiveSlotManager';
|
|
73
70
|
import * as SendSlotManagerModule from '@webex/plugin-meetings/src/multistream/sendSlotManager';
|
|
74
71
|
import {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';
|
|
72
|
+
import * as LocusMediaRequestModule from '@webex/plugin-meetings/src/meeting/locusMediaRequest';
|
|
75
73
|
|
|
76
74
|
import CallDiagnosticLatencies from '@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics-latencies';
|
|
77
75
|
import LLM from '@webex/internal-plugin-llm';
|
|
@@ -102,6 +100,7 @@ import {
|
|
|
102
100
|
import {
|
|
103
101
|
DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
|
|
104
102
|
ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE,
|
|
103
|
+
ICE_AND_REACHABILITY_FAILED_CLIENT_CODE,
|
|
105
104
|
ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
|
|
106
105
|
ICE_FAILURE_CLIENT_CODE,
|
|
107
106
|
MISSING_ROAP_ANSWER_CLIENT_CODE,
|
|
@@ -279,7 +278,7 @@ describe('plugin-meetings', () => {
|
|
|
279
278
|
deviceUrl: uuid3,
|
|
280
279
|
locus: {url: url1},
|
|
281
280
|
destination: testDestination,
|
|
282
|
-
destinationType:
|
|
281
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
283
282
|
correlationId,
|
|
284
283
|
selfId: uuid1,
|
|
285
284
|
},
|
|
@@ -347,7 +346,7 @@ describe('plugin-meetings', () => {
|
|
|
347
346
|
assert.equal(meeting.requiredCaptcha, null);
|
|
348
347
|
assert.equal(meeting.meetingInfoFailureReason, undefined);
|
|
349
348
|
assert.equal(meeting.destination, testDestination);
|
|
350
|
-
assert.equal(meeting.destinationType,
|
|
349
|
+
assert.equal(meeting.destinationType, DESTINATION_TYPE.MEETING_ID);
|
|
351
350
|
assert.instanceOf(meeting.breakouts, Breakouts);
|
|
352
351
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
353
352
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
@@ -367,7 +366,7 @@ describe('plugin-meetings', () => {
|
|
|
367
366
|
deviceUrl: uuid3,
|
|
368
367
|
locus: {url: url1},
|
|
369
368
|
destination: testDestination,
|
|
370
|
-
destinationType:
|
|
369
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
371
370
|
},
|
|
372
371
|
{
|
|
373
372
|
parent: webex,
|
|
@@ -385,7 +384,7 @@ describe('plugin-meetings', () => {
|
|
|
385
384
|
deviceUrl: uuid3,
|
|
386
385
|
locus: {url: url1},
|
|
387
386
|
destination: testDestination,
|
|
388
|
-
destinationType:
|
|
387
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
389
388
|
callStateForMetrics: {
|
|
390
389
|
correlationId: uuid4,
|
|
391
390
|
joinTrigger: 'fake-join-trigger',
|
|
@@ -426,7 +425,7 @@ describe('plugin-meetings', () => {
|
|
|
426
425
|
deviceUrl: uuid3,
|
|
427
426
|
locus: {url: url1},
|
|
428
427
|
destination: testDestination,
|
|
429
|
-
destinationType:
|
|
428
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
430
429
|
},
|
|
431
430
|
{
|
|
432
431
|
parent: webex,
|
|
@@ -502,7 +501,7 @@ describe('plugin-meetings', () => {
|
|
|
502
501
|
deviceUrl: uuid3,
|
|
503
502
|
locus: {url: url1},
|
|
504
503
|
destination: testDestination,
|
|
505
|
-
destinationType:
|
|
504
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
506
505
|
},
|
|
507
506
|
{
|
|
508
507
|
parent: webex,
|
|
@@ -622,10 +621,11 @@ describe('plugin-meetings', () => {
|
|
|
622
621
|
let generateTurnDiscoveryRequestMessageStub;
|
|
623
622
|
let handleTurnDiscoveryHttpResponseStub;
|
|
624
623
|
let abortTurnDiscoveryStub;
|
|
624
|
+
let addMediaInternalStub;
|
|
625
625
|
|
|
626
626
|
beforeEach(() => {
|
|
627
627
|
meeting.join = sinon.stub().returns(Promise.resolve(fakeJoinResult));
|
|
628
|
-
|
|
628
|
+
addMediaInternalStub = sinon.stub(meeting, 'addMediaInternal').returns(Promise.resolve(test4));
|
|
629
629
|
|
|
630
630
|
webex.meetings.reachability.getReachabilityResults.resolves(fakeReachabilityResults);
|
|
631
631
|
|
|
@@ -644,7 +644,7 @@ describe('plugin-meetings', () => {
|
|
|
644
644
|
mediaOptions,
|
|
645
645
|
});
|
|
646
646
|
|
|
647
|
-
// check that TURN discovery is done with join and
|
|
647
|
+
// check that TURN discovery is done with join and addMediaInternal() called
|
|
648
648
|
assert.calledOnceWithExactly(meeting.join, {
|
|
649
649
|
...joinOptions,
|
|
650
650
|
roapMessage: fakeRoapMessage,
|
|
@@ -656,7 +656,7 @@ describe('plugin-meetings', () => {
|
|
|
656
656
|
meeting,
|
|
657
657
|
fakeJoinResult
|
|
658
658
|
);
|
|
659
|
-
assert.calledOnceWithExactly(meeting.
|
|
659
|
+
assert.calledOnceWithExactly(meeting.addMediaInternal, sinon.match.any, fakeTurnServerInfo, false, mediaOptions);
|
|
660
660
|
|
|
661
661
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
662
662
|
|
|
@@ -672,7 +672,7 @@ describe('plugin-meetings', () => {
|
|
|
672
672
|
mediaOptions,
|
|
673
673
|
});
|
|
674
674
|
|
|
675
|
-
// check that TURN discovery is done with join and
|
|
675
|
+
// check that TURN discovery is done with join and addMediaInternal() called
|
|
676
676
|
assert.calledOnceWithExactly(meeting.join, {
|
|
677
677
|
...joinOptions,
|
|
678
678
|
roapMessage: undefined,
|
|
@@ -681,7 +681,7 @@ describe('plugin-meetings', () => {
|
|
|
681
681
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
682
682
|
assert.notCalled(handleTurnDiscoveryHttpResponseStub);
|
|
683
683
|
assert.notCalled(abortTurnDiscoveryStub);
|
|
684
|
-
assert.calledOnceWithExactly(meeting.
|
|
684
|
+
assert.calledOnceWithExactly(meeting.addMediaInternal, sinon.match.any, undefined, false, mediaOptions);
|
|
685
685
|
|
|
686
686
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
687
687
|
assert.equal(meeting.turnServerUsed, false);
|
|
@@ -698,7 +698,7 @@ describe('plugin-meetings', () => {
|
|
|
698
698
|
mediaOptions,
|
|
699
699
|
});
|
|
700
700
|
|
|
701
|
-
// check that TURN discovery is done with join and
|
|
701
|
+
// check that TURN discovery is done with join and addMediaInternal() called
|
|
702
702
|
assert.calledOnceWithExactly(meeting.join, {
|
|
703
703
|
...joinOptions,
|
|
704
704
|
roapMessage: fakeRoapMessage,
|
|
@@ -711,7 +711,7 @@ describe('plugin-meetings', () => {
|
|
|
711
711
|
fakeJoinResult
|
|
712
712
|
);
|
|
713
713
|
assert.calledOnceWithExactly(abortTurnDiscoveryStub);
|
|
714
|
-
assert.calledOnceWithExactly(meeting.
|
|
714
|
+
assert.calledOnceWithExactly(meeting.addMediaInternal, sinon.match.any, undefined, false, mediaOptions);
|
|
715
715
|
|
|
716
716
|
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
717
717
|
});
|
|
@@ -800,19 +800,19 @@ describe('plugin-meetings', () => {
|
|
|
800
800
|
|
|
801
801
|
it('should fail if called with allowMediaInLobby:false', async () => {
|
|
802
802
|
meeting.join = sinon.stub().returns(Promise.resolve(test1));
|
|
803
|
-
meeting.
|
|
803
|
+
meeting.addMediaInternal = sinon.stub().returns(Promise.resolve(test4));
|
|
804
804
|
|
|
805
805
|
await assert.isRejected(
|
|
806
806
|
meeting.joinWithMedia({mediaOptions: {allowMediaInLobby: false}})
|
|
807
807
|
);
|
|
808
808
|
});
|
|
809
809
|
|
|
810
|
-
it('should call leave() if
|
|
810
|
+
it('should call leave() if addMediaInternal() fails and ignore leave() failure', async () => {
|
|
811
811
|
const leaveError = new Error('leave error');
|
|
812
812
|
const addMediaError = new Error('fake addMedia error');
|
|
813
813
|
|
|
814
814
|
const leaveStub = sinon.stub(meeting, 'leave').rejects(leaveError);
|
|
815
|
-
meeting.
|
|
815
|
+
meeting.addMediaInternal = sinon.stub().rejects(addMediaError);
|
|
816
816
|
|
|
817
817
|
await assert.isRejected(
|
|
818
818
|
meeting.joinWithMedia({
|
|
@@ -863,12 +863,11 @@ describe('plugin-meetings', () => {
|
|
|
863
863
|
);
|
|
864
864
|
});
|
|
865
865
|
|
|
866
|
-
it('should not call leave() if
|
|
866
|
+
it('should not call leave() if addMediaInternal() fails the first time and succeeds the second time and should only call join() once', async () => {
|
|
867
867
|
const addMediaError = new Error('fake addMedia error');
|
|
868
|
-
const
|
|
869
|
-
const leaveStub = sinon.stub(meeting, 'leave').rejects(leaveError);
|
|
868
|
+
const leaveStub = sinon.stub(meeting, 'leave');
|
|
870
869
|
|
|
871
|
-
meeting.
|
|
870
|
+
meeting.addMediaInternal = sinon
|
|
872
871
|
.stub()
|
|
873
872
|
.onFirstCall()
|
|
874
873
|
.rejects(addMediaError)
|
|
@@ -902,6 +901,200 @@ describe('plugin-meetings', () => {
|
|
|
902
901
|
}
|
|
903
902
|
);
|
|
904
903
|
});
|
|
904
|
+
|
|
905
|
+
it('should send the right CA events when media connection fails', async () => {
|
|
906
|
+
const fakeClientError = {id: 'error'};
|
|
907
|
+
|
|
908
|
+
const fakeMediaConnection = {
|
|
909
|
+
close: sinon.stub(),
|
|
910
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
911
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
912
|
+
on: sinon.stub(),
|
|
913
|
+
forceRtcMetricsSend: sinon.stub().resolves(),
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// setup the stubs so that media connection always fails on waitForMediaConnectionConnected()
|
|
917
|
+
addMediaInternalStub.restore();
|
|
918
|
+
meeting.join.returns(
|
|
919
|
+
Promise.resolve({id: 'join result', roapMessage: 'fake TURN discovery response'})
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
923
|
+
sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
|
|
924
|
+
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
925
|
+
sinon
|
|
926
|
+
.stub(meeting.mediaProperties, 'waitForMediaConnectionConnected')
|
|
927
|
+
.rejects(new Error('fake error'));
|
|
928
|
+
|
|
929
|
+
webex.meetings.reachability.isWebexMediaBackendUnreachable = sinon.stub().resolves(false);
|
|
930
|
+
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
|
|
931
|
+
.stub()
|
|
932
|
+
.returns(fakeClientError);
|
|
933
|
+
|
|
934
|
+
// call joinWithMedia() - it should fail
|
|
935
|
+
await assert.isRejected(
|
|
936
|
+
meeting.joinWithMedia({
|
|
937
|
+
joinOptions,
|
|
938
|
+
mediaOptions,
|
|
939
|
+
})
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
// check the right CA events have been sent:
|
|
943
|
+
// calls at index 0 and 2 to submitClientEvent are for "client.media.capabilities" which we don't care about in this test
|
|
944
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent.getCall(1), {
|
|
945
|
+
name: 'client.ice.end',
|
|
946
|
+
payload: {
|
|
947
|
+
canProceed: false,
|
|
948
|
+
icePhase: 'JOIN_MEETING_RETRY',
|
|
949
|
+
errors: [fakeClientError],
|
|
950
|
+
},
|
|
951
|
+
options: {
|
|
952
|
+
meetingId: meeting.id,
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent.getCall(3), {
|
|
956
|
+
name: 'client.ice.end',
|
|
957
|
+
payload: {
|
|
958
|
+
canProceed: false,
|
|
959
|
+
icePhase: 'JOIN_MEETING_FINAL',
|
|
960
|
+
errors: [fakeClientError],
|
|
961
|
+
},
|
|
962
|
+
options: {
|
|
963
|
+
meetingId: meeting.id,
|
|
964
|
+
},
|
|
965
|
+
});
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
it('should force TURN discovery on the 2nd attempt, if addMediaInternal() fails the first time', async () => {
|
|
969
|
+
const addMediaError = new Error('fake addMedia error');
|
|
970
|
+
|
|
971
|
+
const fakeMediaConnection = {
|
|
972
|
+
close: sinon.stub(),
|
|
973
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
974
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
975
|
+
on: sinon.stub(),
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
/* Setup the stubs so that the first call to addMediaInternal() fails
|
|
979
|
+
and the 2nd call calls the real implementation - so that we can check that
|
|
980
|
+
addMediaInternal() eventually calls meeting.roap.doTurnDiscovery() with isForced=true.
|
|
981
|
+
As a result we need to also stub a few other methods like createMediaConnection() and waitForRemoteSDPAnswer() */
|
|
982
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
983
|
+
sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
|
|
984
|
+
|
|
985
|
+
addMediaInternalStub.onFirstCall().rejects(addMediaError);
|
|
986
|
+
addMediaInternalStub.onSecondCall().callsFake((...args) => {
|
|
987
|
+
return addMediaInternalStub.wrappedMethod.bind(meeting)(...args);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
991
|
+
|
|
992
|
+
const result = await meeting.joinWithMedia({
|
|
993
|
+
joinOptions,
|
|
994
|
+
mediaOptions,
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: undefined});
|
|
998
|
+
|
|
999
|
+
assert.calledOnce(meeting.join);
|
|
1000
|
+
|
|
1001
|
+
// first addMediaInternal() call without forcing TURN
|
|
1002
|
+
assert.calledWith(
|
|
1003
|
+
meeting.addMediaInternal.firstCall,
|
|
1004
|
+
sinon.match.any,
|
|
1005
|
+
fakeTurnServerInfo,
|
|
1006
|
+
false,
|
|
1007
|
+
mediaOptions
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
// second addMediaInternal() call with forcing TURN
|
|
1011
|
+
assert.calledWith(
|
|
1012
|
+
meeting.addMediaInternal.secondCall,
|
|
1013
|
+
sinon.match.any,
|
|
1014
|
+
undefined,
|
|
1015
|
+
true,
|
|
1016
|
+
mediaOptions
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
// now check that TURN is actually forced by addMediaInternal(),
|
|
1020
|
+
// we're not checking the isReconnecting param value, because it depends on the full sequence of things
|
|
1021
|
+
// being done correctly (like SDP offer creation) and some of these are stubbed in this test
|
|
1022
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, sinon.match.any, true);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
it('should return the right icePhase in icePhaseCallback on 1st attempt and retry', async () => {
|
|
1026
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1027
|
+
|
|
1028
|
+
const icePhaseCallbacks = [];
|
|
1029
|
+
const addMediaInternalResults = [];
|
|
1030
|
+
|
|
1031
|
+
meeting.addMediaInternal = sinon
|
|
1032
|
+
.stub()
|
|
1033
|
+
.callsFake((icePhaseCallback, _turnServerInfo, _forceTurnDiscovery) => {
|
|
1034
|
+
const defer = new Defer();
|
|
1035
|
+
|
|
1036
|
+
icePhaseCallbacks.push(icePhaseCallback);
|
|
1037
|
+
addMediaInternalResults.push(defer);
|
|
1038
|
+
return defer.promise;
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
const result = meeting.joinWithMedia({
|
|
1042
|
+
joinOptions,
|
|
1043
|
+
mediaOptions,
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
await testUtils.flushPromises();
|
|
1047
|
+
|
|
1048
|
+
// check the callback works correctly on the 1st attempt
|
|
1049
|
+
assert.equal(icePhaseCallbacks.length, 1);
|
|
1050
|
+
assert.equal(icePhaseCallbacks[0](), 'JOIN_MEETING_RETRY');
|
|
1051
|
+
|
|
1052
|
+
// now trigger the failure, so that joinWithMedia() does a retry
|
|
1053
|
+
addMediaInternalResults[0].reject(addMediaError);
|
|
1054
|
+
|
|
1055
|
+
await testUtils.flushPromises();
|
|
1056
|
+
|
|
1057
|
+
// check the callback works correctly on the 2nd attempt
|
|
1058
|
+
assert.equal(icePhaseCallbacks.length, 2);
|
|
1059
|
+
assert.equal(icePhaseCallbacks[1](), 'JOIN_MEETING_FINAL');
|
|
1060
|
+
|
|
1061
|
+
// trigger 2nd failure
|
|
1062
|
+
addMediaInternalResults[1].reject(addMediaError);
|
|
1063
|
+
|
|
1064
|
+
await assert.isRejected(result);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
it('should not attempt a retry if we fail to create the offer on first atttempt', async () => {
|
|
1068
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1069
|
+
addMediaError.name = 'SdpOfferCreationError';
|
|
1070
|
+
|
|
1071
|
+
meeting.addMediaInternal.rejects(addMediaError)
|
|
1072
|
+
|
|
1073
|
+
await assert.isRejected(meeting.joinWithMedia({
|
|
1074
|
+
joinOptions,
|
|
1075
|
+
mediaOptions,
|
|
1076
|
+
}), addMediaError);
|
|
1077
|
+
|
|
1078
|
+
// check that only 1 attempt was done
|
|
1079
|
+
assert.calledOnce(meeting.join);
|
|
1080
|
+
assert.calledOnce(meeting.addMediaInternal);
|
|
1081
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1082
|
+
assert.calledWith(
|
|
1083
|
+
Metrics.sendBehavioralMetric.firstCall,
|
|
1084
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1085
|
+
{
|
|
1086
|
+
correlation_id: meeting.correlationId,
|
|
1087
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1088
|
+
reason: addMediaError.message,
|
|
1089
|
+
stack: addMediaError.stack,
|
|
1090
|
+
leaveErrorReason: undefined,
|
|
1091
|
+
isRetry: false,
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
type: addMediaError.name,
|
|
1095
|
+
}
|
|
1096
|
+
);
|
|
1097
|
+
});
|
|
905
1098
|
});
|
|
906
1099
|
|
|
907
1100
|
describe('#isTranscriptionSupported', () => {
|
|
@@ -946,19 +1139,18 @@ describe('plugin-meetings', () => {
|
|
|
946
1139
|
assert.calledTwice(webex.internal.voicea.turnOnCaptions);
|
|
947
1140
|
});
|
|
948
1141
|
|
|
949
|
-
it('should listen to events and
|
|
1142
|
+
it('should listen to events and turnOnCaptions for all users', async () => {
|
|
950
1143
|
meeting.joinedWith = {
|
|
951
1144
|
state: 'JOINED',
|
|
952
1145
|
};
|
|
953
1146
|
meeting.areVoiceaEventsSetup = false;
|
|
954
|
-
meeting.roles = ['COHOST'];
|
|
955
1147
|
|
|
956
1148
|
await meeting.startTranscription();
|
|
957
1149
|
|
|
958
1150
|
assert.equal(webex.internal.voicea.on.callCount, 4);
|
|
959
1151
|
assert.equal(meeting.areVoiceaEventsSetup, true);
|
|
960
1152
|
assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
|
|
961
|
-
assert.
|
|
1153
|
+
assert.calledOnce(webex.internal.voicea.turnOnCaptions);
|
|
962
1154
|
});
|
|
963
1155
|
|
|
964
1156
|
it("should throw error if request doesn't work", async () => {
|
|
@@ -1075,6 +1267,7 @@ describe('plugin-meetings', () => {
|
|
|
1075
1267
|
webex.internal.voicea.on = sinon.stub();
|
|
1076
1268
|
webex.internal.voicea.off = sinon.stub();
|
|
1077
1269
|
webex.internal.voicea.setSpokenLanguage = sinon.stub();
|
|
1270
|
+
meeting.roles = ['MODERATOR'];
|
|
1078
1271
|
});
|
|
1079
1272
|
|
|
1080
1273
|
afterEach(() => {
|
|
@@ -1091,6 +1284,16 @@ describe('plugin-meetings', () => {
|
|
|
1091
1284
|
});
|
|
1092
1285
|
});
|
|
1093
1286
|
|
|
1287
|
+
it('should reject if current user is not a host', (done) => {
|
|
1288
|
+
meeting.isTranscriptionSupported.returns(true);
|
|
1289
|
+
meeting.roles = ['COHOST'];
|
|
1290
|
+
|
|
1291
|
+
meeting.setSpokenLanguage('fr').catch((error) => {
|
|
1292
|
+
assert.equal(error.message, 'Only host can set spoken language');
|
|
1293
|
+
done();
|
|
1294
|
+
});
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1094
1297
|
it('should resolve with the language code on successful language update', (done) => {
|
|
1095
1298
|
meeting.isTranscriptionSupported.returns(true);
|
|
1096
1299
|
const languageCode = 'fr';
|
|
@@ -1383,6 +1586,39 @@ describe('plugin-meetings', () => {
|
|
|
1383
1586
|
);
|
|
1384
1587
|
});
|
|
1385
1588
|
|
|
1589
|
+
[true, false].forEach((enableMultistream) => {
|
|
1590
|
+
it(`should instantiate LocusMediaRequest with correct parameters (enableMultistream=${enableMultistream})`, async () => {
|
|
1591
|
+
meeting.config.deviceType = 'web';
|
|
1592
|
+
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
1593
|
+
|
|
1594
|
+
const mockLocusMediaRequestCtor = sinon
|
|
1595
|
+
.stub(LocusMediaRequestModule, 'LocusMediaRequest')
|
|
1596
|
+
.returns({
|
|
1597
|
+
id: 'fake LocusMediaRequest instance',
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
await meeting.join({enableMultistream});
|
|
1601
|
+
|
|
1602
|
+
assert.calledOnceWithExactly(
|
|
1603
|
+
mockLocusMediaRequestCtor,
|
|
1604
|
+
{
|
|
1605
|
+
correlationId: meeting.correlationId,
|
|
1606
|
+
meetingId: meeting.id,
|
|
1607
|
+
device: {
|
|
1608
|
+
url: meeting.deviceUrl,
|
|
1609
|
+
deviceType: meeting.config.deviceType,
|
|
1610
|
+
countryCode: 'UK',
|
|
1611
|
+
regionCode: 'EU',
|
|
1612
|
+
},
|
|
1613
|
+
preferTranscoding: !enableMultistream,
|
|
1614
|
+
},
|
|
1615
|
+
{
|
|
1616
|
+
parent: meeting.webex,
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1386
1622
|
it('should take trigger from meeting joinTrigger if available', () => {
|
|
1387
1623
|
meeting.updateCallStateForMetrics({joinTrigger: 'fake-join-trigger'});
|
|
1388
1624
|
const join = meeting.join();
|
|
@@ -1661,7 +1897,7 @@ describe('plugin-meetings', () => {
|
|
|
1661
1897
|
|
|
1662
1898
|
let fakeMediaConnection;
|
|
1663
1899
|
|
|
1664
|
-
beforeEach(() => {
|
|
1900
|
+
beforeEach(async () => {
|
|
1665
1901
|
fakeMediaConnection = {
|
|
1666
1902
|
close: sinon.stub(),
|
|
1667
1903
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
@@ -1674,13 +1910,18 @@ describe('plugin-meetings', () => {
|
|
|
1674
1910
|
meeting.audio = muteStateStub;
|
|
1675
1911
|
meeting.video = muteStateStub;
|
|
1676
1912
|
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
.stub()
|
|
1913
|
+
sinon.stub(meeting, 'setupMediaConnectionListeners');
|
|
1914
|
+
sinon.stub(meeting, 'setMercuryListener');
|
|
1915
|
+
sinon
|
|
1916
|
+
.stub(meeting.roap, 'doTurnDiscovery')
|
|
1682
1917
|
.resolves({turnServerInfo: {}, turnDiscoverySkippedReason: undefined});
|
|
1683
|
-
|
|
1918
|
+
sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
|
|
1919
|
+
|
|
1920
|
+
// normally the first Roap message we send is creating confluence, so mock LocusMediaRequest.isConfluenceCreated()
|
|
1921
|
+
// to return false the first time it's called and true the 2nd time, to simulate how it would happen for real
|
|
1922
|
+
meeting.locusMediaRequest = {
|
|
1923
|
+
isConfluenceCreated: sinon.stub().onFirstCall().returns(false).onSecondCall().returns(true)
|
|
1924
|
+
};
|
|
1684
1925
|
});
|
|
1685
1926
|
|
|
1686
1927
|
it('should have #addMedia', () => {
|
|
@@ -1778,6 +2019,7 @@ describe('plugin-meetings', () => {
|
|
|
1778
2019
|
someReachabilityMetric2: 'some value2',
|
|
1779
2020
|
selectedCandidatePairChanges: 2,
|
|
1780
2021
|
numTransports: 1,
|
|
2022
|
+
iceCandidatesCount: 0,
|
|
1781
2023
|
}
|
|
1782
2024
|
);
|
|
1783
2025
|
});
|
|
@@ -1885,6 +2127,7 @@ describe('plugin-meetings', () => {
|
|
|
1885
2127
|
someReachabilityMetric2: 'some value2',
|
|
1886
2128
|
selectedCandidatePairChanges: 2,
|
|
1887
2129
|
numTransports: 1,
|
|
2130
|
+
iceCandidatesCount: 0,
|
|
1888
2131
|
}
|
|
1889
2132
|
);
|
|
1890
2133
|
});
|
|
@@ -2028,6 +2271,61 @@ describe('plugin-meetings', () => {
|
|
|
2028
2271
|
}
|
|
2029
2272
|
});
|
|
2030
2273
|
|
|
2274
|
+
it('sends correct CA event when times out waiting for SDP answer', async () => {
|
|
2275
|
+
const eventListeners = {};
|
|
2276
|
+
const clock = sinon.useFakeTimers();
|
|
2277
|
+
|
|
2278
|
+
// these 2 are stubbed, we need the real versions:
|
|
2279
|
+
meeting.waitForRemoteSDPAnswer.restore();
|
|
2280
|
+
meeting.setupMediaConnectionListeners.restore();
|
|
2281
|
+
|
|
2282
|
+
meeting.meetingState = 'ACTIVE';
|
|
2283
|
+
|
|
2284
|
+
// setup a mock media connection that will trigger an offer when initiateOffer() is called
|
|
2285
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
2286
|
+
initiateOffer: sinon.stub().callsFake(() => {
|
|
2287
|
+
// simulate offer being generated
|
|
2288
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
2289
|
+
|
|
2290
|
+
return Promise.resolve();
|
|
2291
|
+
}),
|
|
2292
|
+
close: sinon.stub(),
|
|
2293
|
+
on: (event, listener) => {
|
|
2294
|
+
eventListeners[event] = listener;
|
|
2295
|
+
},
|
|
2296
|
+
forceRtcMetricsSend: sinon.stub().resolves(),
|
|
2297
|
+
});
|
|
2298
|
+
|
|
2299
|
+
const getErrorPayloadForClientErrorCodeStub =
|
|
2300
|
+
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
2301
|
+
sinon
|
|
2302
|
+
.stub()
|
|
2303
|
+
.callsFake(({clientErrorCode}) => ({errorCode: clientErrorCode, fatal: true})));
|
|
2304
|
+
|
|
2305
|
+
const result = meeting.addMedia();
|
|
2306
|
+
await testUtils.flushPromises();
|
|
2307
|
+
|
|
2308
|
+
// simulate timeout waiting for the SDP answer that never comes
|
|
2309
|
+
await clock.tickAsync(ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
|
|
2310
|
+
|
|
2311
|
+
await assert.isRejected(result);
|
|
2312
|
+
|
|
2313
|
+
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
2314
|
+
clientErrorCode: 2007,
|
|
2315
|
+
});
|
|
2316
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2317
|
+
name: 'client.media-engine.remote-sdp-received',
|
|
2318
|
+
payload: {
|
|
2319
|
+
canProceed: false,
|
|
2320
|
+
errors: [{errorCode: 2007, fatal: true}],
|
|
2321
|
+
},
|
|
2322
|
+
options: {
|
|
2323
|
+
meetingId: meeting.id,
|
|
2324
|
+
rawError: sinon.match.instanceOf(Error),
|
|
2325
|
+
},
|
|
2326
|
+
});
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2031
2329
|
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
2330
|
meeting.meetingState = 'ACTIVE';
|
|
2033
2331
|
// setup the mock to cause addMedia() to fail
|
|
@@ -2187,8 +2485,13 @@ describe('plugin-meetings', () => {
|
|
|
2187
2485
|
it('should reject if waitForMediaConnectionConnected() rejects after turn server retry', async () => {
|
|
2188
2486
|
const FAKE_ERROR = {fatal: true};
|
|
2189
2487
|
const getErrorPayloadForClientErrorCodeStub =
|
|
2488
|
+
|
|
2190
2489
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
2191
2490
|
sinon.stub().returns(FAKE_ERROR));
|
|
2491
|
+
webex.meetings.reachability = {
|
|
2492
|
+
isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
|
|
2493
|
+
getReachabilityMetrics: sinon.stub().resolves(),
|
|
2494
|
+
};
|
|
2192
2495
|
const MOCK_CLIENT_ERROR_CODE = 2004;
|
|
2193
2496
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
2194
2497
|
.stub(CallDiagnosticUtils, 'generateClientErrorCodeForIceFailure')
|
|
@@ -2216,7 +2519,7 @@ describe('plugin-meetings', () => {
|
|
|
2216
2519
|
turnDiscoverySkippedReason: undefined,
|
|
2217
2520
|
});
|
|
2218
2521
|
meeting.meetingState = 'ACTIVE';
|
|
2219
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
2522
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2220
2523
|
|
|
2221
2524
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2222
2525
|
const closeMediaConnectionStub = sinon.stub();
|
|
@@ -2240,13 +2543,15 @@ describe('plugin-meetings', () => {
|
|
|
2240
2543
|
assert.calledTwice(generateClientErrorCodeForIceFailureStub);
|
|
2241
2544
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2242
2545
|
signalingState: 'unknown',
|
|
2243
|
-
|
|
2546
|
+
iceConnected: false,
|
|
2244
2547
|
turnServerUsed: false,
|
|
2548
|
+
unreachable: false,
|
|
2245
2549
|
});
|
|
2246
2550
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2247
2551
|
signalingState: 'unknown',
|
|
2248
|
-
|
|
2552
|
+
iceConnected: false,
|
|
2249
2553
|
turnServerUsed: true,
|
|
2554
|
+
unreachable: false,
|
|
2250
2555
|
});
|
|
2251
2556
|
|
|
2252
2557
|
assert.calledTwice(getErrorPayloadForClientErrorCodeStub);
|
|
@@ -2364,6 +2669,7 @@ describe('plugin-meetings', () => {
|
|
|
2364
2669
|
iceConnectionState: 'unknown',
|
|
2365
2670
|
selectedCandidatePairChanges: 2,
|
|
2366
2671
|
numTransports: 1,
|
|
2672
|
+
iceCandidatesCount: 0,
|
|
2367
2673
|
},
|
|
2368
2674
|
]);
|
|
2369
2675
|
|
|
@@ -2371,7 +2677,7 @@ describe('plugin-meetings', () => {
|
|
|
2371
2677
|
const doTurnDiscoveryCalls = meeting.roap.doTurnDiscovery.getCalls();
|
|
2372
2678
|
assert.equal(doTurnDiscoveryCalls.length, 2);
|
|
2373
2679
|
assert.deepEqual(doTurnDiscoveryCalls[0].args, [meeting, false, false]);
|
|
2374
|
-
assert.deepEqual(doTurnDiscoveryCalls[1].args, [
|
|
2680
|
+
assert.deepEqual(doTurnDiscoveryCalls[1].args.slice(1), [true, true]);
|
|
2375
2681
|
|
|
2376
2682
|
// Some clean up steps happens twice
|
|
2377
2683
|
assert.calledTwice(forceRtcMetricsSend);
|
|
@@ -2383,6 +2689,10 @@ describe('plugin-meetings', () => {
|
|
|
2383
2689
|
|
|
2384
2690
|
it('should resolve if waitForMediaConnectionConnected() rejects the first time but resolves the second time', async () => {
|
|
2385
2691
|
const FAKE_ERROR = {fatal: true};
|
|
2692
|
+
webex.meetings.reachability = {
|
|
2693
|
+
isWebexMediaBackendUnreachable: sinon.stub().onCall(0).rejects().onCall(1).resolves(true).onCall(2).resolves(false),
|
|
2694
|
+
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
2695
|
+
}
|
|
2386
2696
|
const getErrorPayloadForClientErrorCodeStub =
|
|
2387
2697
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
2388
2698
|
sinon.stub().returns(FAKE_ERROR));
|
|
@@ -2440,8 +2750,9 @@ describe('plugin-meetings', () => {
|
|
|
2440
2750
|
assert.calledOnce(generateClientErrorCodeForIceFailureStub);
|
|
2441
2751
|
assert.calledWith(generateClientErrorCodeForIceFailureStub, {
|
|
2442
2752
|
signalingState: 'unknown',
|
|
2443
|
-
|
|
2753
|
+
iceConnected: undefined,
|
|
2444
2754
|
turnServerUsed: false,
|
|
2755
|
+
unreachable: false,
|
|
2445
2756
|
});
|
|
2446
2757
|
|
|
2447
2758
|
assert.calledOnce(getErrorPayloadForClientErrorCodeStub);
|
|
@@ -2547,6 +2858,7 @@ describe('plugin-meetings', () => {
|
|
|
2547
2858
|
isMultistream: false,
|
|
2548
2859
|
retriedWithTurnServer: true,
|
|
2549
2860
|
isJoinWithMediaRetry: false,
|
|
2861
|
+
iceCandidatesCount: 0,
|
|
2550
2862
|
},
|
|
2551
2863
|
]);
|
|
2552
2864
|
meeting.roap.doTurnDiscovery;
|
|
@@ -2675,6 +2987,8 @@ describe('plugin-meetings', () => {
|
|
|
2675
2987
|
someReachabilityMetric2: 'some value2',
|
|
2676
2988
|
}),
|
|
2677
2989
|
};
|
|
2990
|
+
meeting.iceCandidatesCount = 3;
|
|
2991
|
+
|
|
2678
2992
|
await meeting.addMedia({
|
|
2679
2993
|
mediaSettings: {},
|
|
2680
2994
|
});
|
|
@@ -2694,6 +3008,7 @@ describe('plugin-meetings', () => {
|
|
|
2694
3008
|
isJoinWithMediaRetry: false,
|
|
2695
3009
|
someReachabilityMetric1: 'some value1',
|
|
2696
3010
|
someReachabilityMetric2: 'some value2',
|
|
3011
|
+
iceCandidatesCount: 3,
|
|
2697
3012
|
}
|
|
2698
3013
|
);
|
|
2699
3014
|
|
|
@@ -2715,7 +3030,63 @@ describe('plugin-meetings', () => {
|
|
|
2715
3030
|
turnDiscoverySkippedReason: undefined,
|
|
2716
3031
|
});
|
|
2717
3032
|
meeting.meetingState = 'ACTIVE';
|
|
2718
|
-
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
3033
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
3034
|
+
|
|
3035
|
+
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
3036
|
+
const closeMediaConnectionStub = sinon.stub();
|
|
3037
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
3038
|
+
close: closeMediaConnectionStub,
|
|
3039
|
+
forceRtcMetricsSend,
|
|
3040
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
3041
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
3042
|
+
on: sinon.stub(),
|
|
3043
|
+
});
|
|
3044
|
+
|
|
3045
|
+
await meeting
|
|
3046
|
+
.addMedia({
|
|
3047
|
+
mediaSettings: {},
|
|
3048
|
+
})
|
|
3049
|
+
.catch((err) => {
|
|
3050
|
+
errorThrown = err;
|
|
3051
|
+
assert.instanceOf(err, AddMediaFailed);
|
|
3052
|
+
});
|
|
3053
|
+
|
|
3054
|
+
// Check that the only metric sent is ADD_MEDIA_FAILURE
|
|
3055
|
+
assert.calledOnceWithExactly(
|
|
3056
|
+
Metrics.sendBehavioralMetric,
|
|
3057
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
3058
|
+
{
|
|
3059
|
+
correlation_id: meeting.correlationId,
|
|
3060
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3061
|
+
reason: errorThrown.message,
|
|
3062
|
+
stack: errorThrown.stack,
|
|
3063
|
+
code: errorThrown.code,
|
|
3064
|
+
turnDiscoverySkippedReason: undefined,
|
|
3065
|
+
turnServerUsed: true,
|
|
3066
|
+
retriedWithTurnServer: false,
|
|
3067
|
+
isMultistream: false,
|
|
3068
|
+
isJoinWithMediaRetry: false,
|
|
3069
|
+
signalingState: 'unknown',
|
|
3070
|
+
connectionState: 'unknown',
|
|
3071
|
+
iceConnectionState: 'unknown',
|
|
3072
|
+
selectedCandidatePairChanges: 2,
|
|
3073
|
+
numTransports: 1,
|
|
3074
|
+
iceCandidatesCount: 0,
|
|
3075
|
+
}
|
|
3076
|
+
);
|
|
3077
|
+
|
|
3078
|
+
assert.isOk(errorThrown);
|
|
3079
|
+
});
|
|
3080
|
+
|
|
3081
|
+
it('should send ICE_CANDIDATE_ERROR metric if media connection fails and ice candidate errors have been gathered', async () => {
|
|
3082
|
+
let errorThrown = undefined;
|
|
3083
|
+
|
|
3084
|
+
meeting.roap.doTurnDiscovery = sinon.stub().returns({
|
|
3085
|
+
turnServerInfo: undefined,
|
|
3086
|
+
turnDiscoverySkippedReason: undefined,
|
|
3087
|
+
});
|
|
3088
|
+
meeting.meetingState = 'ACTIVE';
|
|
3089
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
|
2719
3090
|
|
|
2720
3091
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
2721
3092
|
const closeMediaConnectionStub = sinon.stub();
|
|
@@ -2727,6 +3098,9 @@ describe('plugin-meetings', () => {
|
|
|
2727
3098
|
on: sinon.stub(),
|
|
2728
3099
|
});
|
|
2729
3100
|
|
|
3101
|
+
meeting.iceCandidateErrors.set('701_error', 2);
|
|
3102
|
+
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
3103
|
+
|
|
2730
3104
|
await meeting
|
|
2731
3105
|
.addMedia({
|
|
2732
3106
|
mediaSettings: {},
|
|
@@ -2756,6 +3130,9 @@ describe('plugin-meetings', () => {
|
|
|
2756
3130
|
iceConnectionState: 'unknown',
|
|
2757
3131
|
selectedCandidatePairChanges: 2,
|
|
2758
3132
|
numTransports: 1,
|
|
3133
|
+
'701_error': 2,
|
|
3134
|
+
'701_turn_host_lookup_received_error': 1,
|
|
3135
|
+
iceCandidatesCount: 0,
|
|
2759
3136
|
}
|
|
2760
3137
|
);
|
|
2761
3138
|
|
|
@@ -2775,7 +3152,7 @@ describe('plugin-meetings', () => {
|
|
|
2775
3152
|
|
|
2776
3153
|
statsAnalyzerStub = new EventsScope();
|
|
2777
3154
|
// mock the StatsAnalyzer constructor
|
|
2778
|
-
sinon.stub(
|
|
3155
|
+
sinon.stub(InternalMediaCoreModule, 'StatsAnalyzer').returns(statsAnalyzerStub);
|
|
2779
3156
|
|
|
2780
3157
|
await meeting.addMedia({
|
|
2781
3158
|
mediaSettings: {},
|
|
@@ -2789,8 +3166,8 @@ describe('plugin-meetings', () => {
|
|
|
2789
3166
|
it('LOCAL_MEDIA_STARTED triggers "meeting:media:local:start" event and sends metrics', async () => {
|
|
2790
3167
|
statsAnalyzerStub.emit(
|
|
2791
3168
|
{file: 'test', function: 'test'},
|
|
2792
|
-
|
|
2793
|
-
{
|
|
3169
|
+
StatsAnalyzerEventNames.LOCAL_MEDIA_STARTED,
|
|
3170
|
+
{mediaType: 'audio'}
|
|
2794
3171
|
);
|
|
2795
3172
|
|
|
2796
3173
|
assert.calledWith(
|
|
@@ -2802,7 +3179,7 @@ describe('plugin-meetings', () => {
|
|
|
2802
3179
|
},
|
|
2803
3180
|
EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
|
|
2804
3181
|
{
|
|
2805
|
-
|
|
3182
|
+
mediaType: 'audio',
|
|
2806
3183
|
}
|
|
2807
3184
|
);
|
|
2808
3185
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2817,8 +3194,8 @@ describe('plugin-meetings', () => {
|
|
|
2817
3194
|
it('LOCAL_MEDIA_STOPPED triggers the right metrics', async () => {
|
|
2818
3195
|
statsAnalyzerStub.emit(
|
|
2819
3196
|
{file: 'test', function: 'test'},
|
|
2820
|
-
|
|
2821
|
-
{
|
|
3197
|
+
StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED,
|
|
3198
|
+
{mediaType: 'video'}
|
|
2822
3199
|
);
|
|
2823
3200
|
|
|
2824
3201
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2833,8 +3210,8 @@ describe('plugin-meetings', () => {
|
|
|
2833
3210
|
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics', async () => {
|
|
2834
3211
|
statsAnalyzerStub.emit(
|
|
2835
3212
|
{file: 'test', function: 'test'},
|
|
2836
|
-
|
|
2837
|
-
{
|
|
3213
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
|
|
3214
|
+
{mediaType: 'video'}
|
|
2838
3215
|
);
|
|
2839
3216
|
|
|
2840
3217
|
assert.calledWith(
|
|
@@ -2846,7 +3223,7 @@ describe('plugin-meetings', () => {
|
|
|
2846
3223
|
},
|
|
2847
3224
|
EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
|
|
2848
3225
|
{
|
|
2849
|
-
|
|
3226
|
+
mediaType: 'video',
|
|
2850
3227
|
}
|
|
2851
3228
|
);
|
|
2852
3229
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2861,8 +3238,8 @@ describe('plugin-meetings', () => {
|
|
|
2861
3238
|
it('REMOTE_MEDIA_STOPPED triggers the right metrics', async () => {
|
|
2862
3239
|
statsAnalyzerStub.emit(
|
|
2863
3240
|
{file: 'test', function: 'test'},
|
|
2864
|
-
|
|
2865
|
-
{
|
|
3241
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
|
|
3242
|
+
{mediaType: 'audio'}
|
|
2866
3243
|
);
|
|
2867
3244
|
|
|
2868
3245
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2877,8 +3254,8 @@ describe('plugin-meetings', () => {
|
|
|
2877
3254
|
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics for share', async () => {
|
|
2878
3255
|
statsAnalyzerStub.emit(
|
|
2879
3256
|
{file: 'test', function: 'test'},
|
|
2880
|
-
|
|
2881
|
-
{
|
|
3257
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
|
|
3258
|
+
{mediaType: 'share'}
|
|
2882
3259
|
);
|
|
2883
3260
|
|
|
2884
3261
|
assert.calledWith(
|
|
@@ -2890,7 +3267,7 @@ describe('plugin-meetings', () => {
|
|
|
2890
3267
|
},
|
|
2891
3268
|
EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
|
|
2892
3269
|
{
|
|
2893
|
-
|
|
3270
|
+
mediaType: 'share',
|
|
2894
3271
|
}
|
|
2895
3272
|
);
|
|
2896
3273
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2913,8 +3290,8 @@ describe('plugin-meetings', () => {
|
|
|
2913
3290
|
it('REMOTE_MEDIA_STOPPED triggers the right metrics for share', async () => {
|
|
2914
3291
|
statsAnalyzerStub.emit(
|
|
2915
3292
|
{file: 'test', function: 'test'},
|
|
2916
|
-
|
|
2917
|
-
{
|
|
3293
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
|
|
3294
|
+
{mediaType: 'share'}
|
|
2918
3295
|
);
|
|
2919
3296
|
|
|
2920
3297
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -2935,19 +3312,18 @@ describe('plugin-meetings', () => {
|
|
|
2935
3312
|
});
|
|
2936
3313
|
|
|
2937
3314
|
it('calls submitMQE correctly', async () => {
|
|
2938
|
-
const fakeData = {intervalMetadata: {bla: 'bla'}};
|
|
3315
|
+
const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
|
|
2939
3316
|
|
|
2940
3317
|
statsAnalyzerStub.emit(
|
|
2941
3318
|
{file: 'test', function: 'test'},
|
|
2942
|
-
|
|
2943
|
-
{data: fakeData
|
|
3319
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3320
|
+
{data: fakeData}
|
|
2944
3321
|
);
|
|
2945
3322
|
|
|
2946
3323
|
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
2947
3324
|
name: 'client.mediaquality.event',
|
|
2948
3325
|
options: {
|
|
2949
3326
|
meetingId: meeting.id,
|
|
2950
|
-
networkType: 'wifi',
|
|
2951
3327
|
},
|
|
2952
3328
|
payload: {
|
|
2953
3329
|
intervals: [fakeData],
|
|
@@ -3004,7 +3380,7 @@ describe('plugin-meetings', () => {
|
|
|
3004
3380
|
it('succeeds even if getDevices() throws', async () => {
|
|
3005
3381
|
meeting.meetingState = 'ACTIVE';
|
|
3006
3382
|
|
|
3007
|
-
sinon.stub(
|
|
3383
|
+
sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
|
|
3008
3384
|
|
|
3009
3385
|
await meeting.addMedia();
|
|
3010
3386
|
});
|
|
@@ -3021,7 +3397,7 @@ describe('plugin-meetings', () => {
|
|
|
3021
3397
|
clientErrorCode: MISSING_ROAP_ANSWER_CLIENT_CODE,
|
|
3022
3398
|
expectedErrorPayload: {
|
|
3023
3399
|
errorDescription: ERROR_DESCRIPTIONS.MISSING_ROAP_ANSWER,
|
|
3024
|
-
category: '
|
|
3400
|
+
category: 'media',
|
|
3025
3401
|
},
|
|
3026
3402
|
},
|
|
3027
3403
|
{
|
|
@@ -3040,10 +3416,18 @@ describe('plugin-meetings', () => {
|
|
|
3040
3416
|
clientErrorCode: ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
|
|
3041
3417
|
expectedErrorPayload: {
|
|
3042
3418
|
errorDescription: ERROR_DESCRIPTIONS.ICE_FAILED_WITH_TURN_TLS,
|
|
3043
|
-
category: '
|
|
3419
|
+
category: 'media',
|
|
3420
|
+
},
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
clientErrorCode: ICE_AND_REACHABILITY_FAILED_CLIENT_CODE,
|
|
3424
|
+
unreachable: true,
|
|
3425
|
+
expectedErrorPayload: {
|
|
3426
|
+
errorDescription: ERROR_DESCRIPTIONS.ICE_AND_REACHABILITY_FAILED,
|
|
3427
|
+
category: 'expected',
|
|
3044
3428
|
},
|
|
3045
3429
|
},
|
|
3046
|
-
].forEach(({clientErrorCode, expectedErrorPayload}) => {
|
|
3430
|
+
].forEach(({clientErrorCode, expectedErrorPayload, unreachable}) => {
|
|
3047
3431
|
it(`should handle all ice failures correctly for ${clientErrorCode}`, async () => {
|
|
3048
3432
|
// setting the method to the real implementation
|
|
3049
3433
|
// because newMetrics is mocked completely in the webex-mock
|
|
@@ -3052,13 +3436,17 @@ describe('plugin-meetings', () => {
|
|
|
3052
3436
|
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
3053
3437
|
CD.getErrorPayloadForClientErrorCode;
|
|
3054
3438
|
|
|
3439
|
+
webex.meetings.reachability = {
|
|
3440
|
+
isWebexMediaBackendUnreachable: sinon.stub().resolves(unreachable || false),
|
|
3441
|
+
};
|
|
3442
|
+
|
|
3055
3443
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
3056
3444
|
.stub(CallDiagnosticUtils, 'generateClientErrorCodeForIceFailure')
|
|
3057
3445
|
.returns(clientErrorCode);
|
|
3058
3446
|
|
|
3059
3447
|
meeting.meetingState = 'ACTIVE';
|
|
3060
3448
|
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(
|
|
3061
|
-
|
|
3449
|
+
{iceConnected: false}
|
|
3062
3450
|
);
|
|
3063
3451
|
|
|
3064
3452
|
let errorThrown = false;
|
|
@@ -3073,8 +3461,9 @@ describe('plugin-meetings', () => {
|
|
|
3073
3461
|
|
|
3074
3462
|
assert.calledOnceWithExactly(generateClientErrorCodeForIceFailureStub, {
|
|
3075
3463
|
signalingState: 'unknown',
|
|
3076
|
-
|
|
3464
|
+
iceConnected: false,
|
|
3077
3465
|
turnServerUsed: true,
|
|
3466
|
+
unreachable: unreachable || false,
|
|
3078
3467
|
});
|
|
3079
3468
|
|
|
3080
3469
|
const submitClientEventCalls = webex.internal.newMetrics.submitClientEvent.getCalls();
|
|
@@ -3162,7 +3551,7 @@ describe('plugin-meetings', () => {
|
|
|
3162
3551
|
|
|
3163
3552
|
let clock;
|
|
3164
3553
|
|
|
3165
|
-
beforeEach(() => {
|
|
3554
|
+
beforeEach(async () => {
|
|
3166
3555
|
clock = sinon.useFakeTimers();
|
|
3167
3556
|
|
|
3168
3557
|
sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
|
|
@@ -3171,7 +3560,6 @@ describe('plugin-meetings', () => {
|
|
|
3171
3560
|
meeting.config.deviceType = 'web';
|
|
3172
3561
|
meeting.isMultistream = isMultistream;
|
|
3173
3562
|
meeting.meetingState = 'ACTIVE';
|
|
3174
|
-
meeting.mediaId = 'fake media id';
|
|
3175
3563
|
meeting.selfUrl = 'selfUrl';
|
|
3176
3564
|
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
3177
3565
|
meeting.mediaProperties.getCurrentConnectionInfo = sinon.stub().resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
|
|
@@ -3261,16 +3649,30 @@ describe('plugin-meetings', () => {
|
|
|
3261
3649
|
};
|
|
3262
3650
|
|
|
3263
3651
|
roapMediaConnectionConstructorStub = sinon
|
|
3264
|
-
.stub(
|
|
3652
|
+
.stub(InternalMediaCoreModule, 'RoapMediaConnection')
|
|
3265
3653
|
.returns(fakeRoapMediaConnection);
|
|
3266
3654
|
|
|
3267
3655
|
multistreamRoapMediaConnectionConstructorStub = sinon
|
|
3268
|
-
.stub(
|
|
3656
|
+
.stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
|
|
3269
3657
|
.returns(fakeMultistreamRoapMediaConnection);
|
|
3270
3658
|
|
|
3271
3659
|
locusMediaRequestStub = sinon
|
|
3272
3660
|
.stub(WebexPlugin.prototype, 'request')
|
|
3273
3661
|
.resolves({body: {locus: {fullState: {}}}});
|
|
3662
|
+
|
|
3663
|
+
// setup some things and mocks so that the call to join() works
|
|
3664
|
+
// (we need to call join() because it creates the LocusMediaRequest instance
|
|
3665
|
+
// that's being tested in these tests)
|
|
3666
|
+
meeting.webex.meetings.registered = true;
|
|
3667
|
+
meeting.webex.internal.device.config = {};
|
|
3668
|
+
sinon
|
|
3669
|
+
.stub(MeetingUtil, 'joinMeeting')
|
|
3670
|
+
.resolves({
|
|
3671
|
+
id: 'fake locus from mocked join request',
|
|
3672
|
+
locusUrl: 'fake locus url',
|
|
3673
|
+
mediaId: 'fake media id',
|
|
3674
|
+
});
|
|
3675
|
+
await meeting.join({enableMultistream: isMultistream});
|
|
3274
3676
|
});
|
|
3275
3677
|
|
|
3276
3678
|
afterEach(() => {
|
|
@@ -3299,13 +3701,13 @@ describe('plugin-meetings', () => {
|
|
|
3299
3701
|
|
|
3300
3702
|
for (let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx += 1) {
|
|
3301
3703
|
if (
|
|
3302
|
-
roapMediaConnectionToCheck.on.getCall(idx).args[0] ===
|
|
3704
|
+
roapMediaConnectionToCheck.on.getCall(idx).args[0] === MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND
|
|
3303
3705
|
) {
|
|
3304
3706
|
return roapMediaConnectionToCheck.on.getCall(idx).args[1];
|
|
3305
3707
|
}
|
|
3306
3708
|
}
|
|
3307
3709
|
assert.fail(
|
|
3308
|
-
'listener for "roap:messageToSend" (
|
|
3710
|
+
'listener for "roap:messageToSend" (MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND) was not registered'
|
|
3309
3711
|
);
|
|
3310
3712
|
};
|
|
3311
3713
|
|
|
@@ -5107,7 +5509,7 @@ describe('plugin-meetings', () => {
|
|
|
5107
5509
|
|
|
5108
5510
|
describe('#fetchMeetingInfo', () => {
|
|
5109
5511
|
const FAKE_DESTINATION = 'something@somecompany.com';
|
|
5110
|
-
const FAKE_TYPE =
|
|
5512
|
+
const FAKE_TYPE = DESTINATION_TYPE.SIP_URI;
|
|
5111
5513
|
const FAKE_TIMEOUT_FETCHMEETINGINFO_ID = '123456';
|
|
5112
5514
|
const FAKE_PASSWORD = '123abc';
|
|
5113
5515
|
const FAKE_CAPTCHA_CODE = 'a1b2c3XYZ';
|
|
@@ -5542,7 +5944,7 @@ describe('plugin-meetings', () => {
|
|
|
5542
5944
|
const FAKE_PASSWORD = '123456';
|
|
5543
5945
|
const FAKE_CAPTCHA_CODE = '654321';
|
|
5544
5946
|
const FAKE_DESTINATION = 'something@somecompany.com';
|
|
5545
|
-
const FAKE_TYPE =
|
|
5947
|
+
const FAKE_TYPE = DESTINATION_TYPE.SIP_URI;
|
|
5546
5948
|
const FAKE_INSTALLED_ORG_ID = '123456';
|
|
5547
5949
|
const FAKE_MEETING_INFO_LOOKUP_URL = 'meetingLookupUrl';
|
|
5548
5950
|
|
|
@@ -6275,7 +6677,7 @@ describe('plugin-meetings', () => {
|
|
|
6275
6677
|
},
|
|
6276
6678
|
'SELF_OBSERVING'
|
|
6277
6679
|
);
|
|
6278
|
-
|
|
6680
|
+
|
|
6279
6681
|
|
|
6280
6682
|
// Verify that the event handler behaves as expected
|
|
6281
6683
|
expect(meeting.statsAnalyzer.stopAnalyzer.calledOnce).to.be.true;
|
|
@@ -7079,6 +7481,10 @@ describe('plugin-meetings', () => {
|
|
|
7079
7481
|
id: 'stream',
|
|
7080
7482
|
getTracks: () => [{id: 'track', addEventListener: sinon.stub()}],
|
|
7081
7483
|
};
|
|
7484
|
+
const simulateConnectionStateChange = (newState) => {
|
|
7485
|
+
meeting.mediaProperties.webrtcMediaConnection.getConnectionState = sinon.stub().returns(newState);
|
|
7486
|
+
eventListeners[MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED]();
|
|
7487
|
+
}
|
|
7082
7488
|
|
|
7083
7489
|
beforeEach(() => {
|
|
7084
7490
|
eventListeners = {};
|
|
@@ -7088,23 +7494,27 @@ describe('plugin-meetings', () => {
|
|
|
7088
7494
|
on: sinon.stub().callsFake((event, listener) => {
|
|
7089
7495
|
eventListeners[event] = listener;
|
|
7090
7496
|
}),
|
|
7497
|
+
getConnectionState: sinon.stub().returns(ConnectionState.New),
|
|
7091
7498
|
};
|
|
7092
7499
|
MediaUtil.createMediaStream.returns(fakeStream);
|
|
7093
7500
|
});
|
|
7094
7501
|
|
|
7095
7502
|
it('should register for all the correct RoapMediaConnection events', () => {
|
|
7096
7503
|
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(eventListeners[
|
|
7504
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_STARTED]);
|
|
7505
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_DONE]);
|
|
7506
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_FAILURE]);
|
|
7507
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]);
|
|
7508
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]);
|
|
7509
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED]);
|
|
7510
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ICE_CONNECTION_STATE_CHANGED]);
|
|
7511
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]);
|
|
7512
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]);
|
|
7103
7513
|
});
|
|
7104
7514
|
|
|
7105
7515
|
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
7106
7516
|
meeting.setupMediaConnectionListeners();
|
|
7107
|
-
eventListeners[
|
|
7517
|
+
eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]({
|
|
7108
7518
|
track: 'track',
|
|
7109
7519
|
type: RemoteTrackType.AUDIO,
|
|
7110
7520
|
});
|
|
@@ -7114,7 +7524,7 @@ describe('plugin-meetings', () => {
|
|
|
7114
7524
|
stream: fakeStream,
|
|
7115
7525
|
});
|
|
7116
7526
|
|
|
7117
|
-
eventListeners[
|
|
7527
|
+
eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]({
|
|
7118
7528
|
track: 'track',
|
|
7119
7529
|
type: RemoteTrackType.VIDEO,
|
|
7120
7530
|
});
|
|
@@ -7124,7 +7534,7 @@ describe('plugin-meetings', () => {
|
|
|
7124
7534
|
stream: fakeStream,
|
|
7125
7535
|
});
|
|
7126
7536
|
|
|
7127
|
-
eventListeners[
|
|
7537
|
+
eventListeners[MediaConnectionEventNames.REMOTE_TRACK_ADDED]({
|
|
7128
7538
|
track: 'track',
|
|
7129
7539
|
type: RemoteTrackType.SCREENSHARE_VIDEO,
|
|
7130
7540
|
});
|
|
@@ -7135,13 +7545,62 @@ describe('plugin-meetings', () => {
|
|
|
7135
7545
|
});
|
|
7136
7546
|
});
|
|
7137
7547
|
|
|
7548
|
+
describe('should react on a ICE_CANDIDATE event', () => {
|
|
7549
|
+
beforeEach(() => {
|
|
7550
|
+
meeting.setupMediaConnectionListeners();
|
|
7551
|
+
});
|
|
7552
|
+
|
|
7553
|
+
it('should collect ice candidates', () => {
|
|
7554
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: 'candidate'});
|
|
7555
|
+
|
|
7556
|
+
assert.equal(meeting.iceCandidatesCount, 1);
|
|
7557
|
+
});
|
|
7558
|
+
|
|
7559
|
+
it('should not collect null ice candidates', () => {
|
|
7560
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: null});
|
|
7561
|
+
|
|
7562
|
+
assert.equal(meeting.iceCandidatesCount, 0);
|
|
7563
|
+
});
|
|
7564
|
+
});
|
|
7565
|
+
|
|
7566
|
+
describe('should react on a ICE_CANDIDATE_ERROR event', () => {
|
|
7567
|
+
beforeEach(() => {
|
|
7568
|
+
meeting.setupMediaConnectionListeners();
|
|
7569
|
+
});
|
|
7570
|
+
|
|
7571
|
+
it('should not collect skipped ice candidates error', () => {
|
|
7572
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({error: { errorCode: 600, errorText: 'Address not associated with the desired network interface.' }});
|
|
7573
|
+
|
|
7574
|
+
assert.equal(meeting.iceCandidateErrors.size, 0);
|
|
7575
|
+
});
|
|
7576
|
+
|
|
7577
|
+
it('should collect valid ice candidates error', () => {
|
|
7578
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: '' }});
|
|
7579
|
+
|
|
7580
|
+
assert.equal(meeting.iceCandidateErrors.size, 1);
|
|
7581
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7582
|
+
});
|
|
7583
|
+
|
|
7584
|
+
it('should increment counter if same valid ice candidates error collected', () => {
|
|
7585
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: '' }});
|
|
7586
|
+
|
|
7587
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: 'STUN host lookup received error.' }});
|
|
7588
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE_ERROR]({error: { errorCode: 701, errorText: 'STUN host lookup received error.' }});
|
|
7589
|
+
|
|
7590
|
+
assert.equal(meeting.iceCandidateErrors.size, 2);
|
|
7591
|
+
assert.equal(meeting.iceCandidateErrors.has('701_'), true);
|
|
7592
|
+
assert.equal(meeting.iceCandidateErrors.get('701_'), 1);
|
|
7593
|
+
assert.equal(meeting.iceCandidateErrors.has('701_stun_host_lookup_received_error'), true);
|
|
7594
|
+
assert.equal(meeting.iceCandidateErrors.get('701_stun_host_lookup_received_error'), 2);
|
|
7595
|
+
});
|
|
7596
|
+
});
|
|
7597
|
+
|
|
7138
7598
|
describe('CONNECTION_STATE_CHANGED event when state = "Connecting"', () => {
|
|
7139
7599
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = true', () => {
|
|
7140
7600
|
meeting.hasMediaConnectionConnectedAtLeastOnce = true;
|
|
7141
7601
|
meeting.setupMediaConnectionListeners();
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
});
|
|
7602
|
+
|
|
7603
|
+
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
7145
7604
|
|
|
7146
7605
|
assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
7147
7606
|
});
|
|
@@ -7149,9 +7608,8 @@ describe('plugin-meetings', () => {
|
|
|
7149
7608
|
it('sends client.ice.start correctly when hasMediaConnectionConnectedAtLeastOnce = false', () => {
|
|
7150
7609
|
meeting.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
7151
7610
|
meeting.setupMediaConnectionListeners();
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
});
|
|
7611
|
+
|
|
7612
|
+
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
7155
7613
|
|
|
7156
7614
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7157
7615
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7177,6 +7635,7 @@ describe('plugin-meetings', () => {
|
|
|
7177
7635
|
on: sinon.stub().callsFake((event, listener) => {
|
|
7178
7636
|
eventListeners[event] = listener;
|
|
7179
7637
|
}),
|
|
7638
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
7180
7639
|
};
|
|
7181
7640
|
};
|
|
7182
7641
|
|
|
@@ -7230,9 +7689,7 @@ describe('plugin-meetings', () => {
|
|
|
7230
7689
|
assert.equal(meeting.hasMediaConnectionConnectedAtLeastOnce, false);
|
|
7231
7690
|
|
|
7232
7691
|
// simulate first connection success
|
|
7233
|
-
|
|
7234
|
-
state: 'Connected',
|
|
7235
|
-
});
|
|
7692
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7236
7693
|
checkExpectedSpies({
|
|
7237
7694
|
icePhase: 'JOIN_MEETING_FINAL',
|
|
7238
7695
|
setNetworkStatusCallParams: [NETWORK_STATUS.CONNECTED],
|
|
@@ -7242,12 +7699,9 @@ describe('plugin-meetings', () => {
|
|
|
7242
7699
|
// now simulate short connection loss, client.ice.end is not sent a second time as hasMediaConnectionConnectedAtLeastOnce = true
|
|
7243
7700
|
resetSpies();
|
|
7244
7701
|
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
eventListeners[Event.CONNECTION_STATE_CHANGED]({
|
|
7249
|
-
state: 'Connected',
|
|
7250
|
-
});
|
|
7702
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7703
|
+
|
|
7704
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7251
7705
|
|
|
7252
7706
|
checkExpectedSpies({
|
|
7253
7707
|
setNetworkStatusCallParams: [NETWORK_STATUS.DISCONNECTED, NETWORK_STATUS.CONNECTED],
|
|
@@ -7255,12 +7709,9 @@ describe('plugin-meetings', () => {
|
|
|
7255
7709
|
|
|
7256
7710
|
resetSpies();
|
|
7257
7711
|
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
eventListeners[Event.CONNECTION_STATE_CHANGED]({
|
|
7262
|
-
state: 'Connected',
|
|
7263
|
-
});
|
|
7712
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7713
|
+
|
|
7714
|
+
simulateConnectionStateChange(ConnectionState.Connected);
|
|
7264
7715
|
});
|
|
7265
7716
|
});
|
|
7266
7717
|
|
|
@@ -7282,9 +7733,8 @@ describe('plugin-meetings', () => {
|
|
|
7282
7733
|
|
|
7283
7734
|
const mockDisconnectedEvent = () => {
|
|
7284
7735
|
meeting.setupMediaConnectionListeners();
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
});
|
|
7736
|
+
|
|
7737
|
+
simulateConnectionStateChange(ConnectionState.Disconnected);
|
|
7288
7738
|
};
|
|
7289
7739
|
|
|
7290
7740
|
const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => {
|
|
@@ -7348,9 +7798,8 @@ describe('plugin-meetings', () => {
|
|
|
7348
7798
|
describe('CONNECTION_STATE_CHANGED event when state = "Failed"', () => {
|
|
7349
7799
|
const mockFailedEvent = () => {
|
|
7350
7800
|
meeting.setupMediaConnectionListeners();
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
});
|
|
7801
|
+
|
|
7802
|
+
simulateConnectionStateChange(ConnectionState.Failed);
|
|
7354
7803
|
};
|
|
7355
7804
|
|
|
7356
7805
|
const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => {
|
|
@@ -7432,7 +7881,7 @@ describe('plugin-meetings', () => {
|
|
|
7432
7881
|
cause: {name: fakeRootCauseName},
|
|
7433
7882
|
});
|
|
7434
7883
|
|
|
7435
|
-
eventListeners[
|
|
7884
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7436
7885
|
|
|
7437
7886
|
checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
|
|
7438
7887
|
checkBehavioralMetricSent(
|
|
@@ -7449,7 +7898,7 @@ describe('plugin-meetings', () => {
|
|
|
7449
7898
|
cause: {name: fakeRootCauseName},
|
|
7450
7899
|
});
|
|
7451
7900
|
|
|
7452
|
-
eventListeners[
|
|
7901
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7453
7902
|
|
|
7454
7903
|
checkMetricSent('client.media-engine.remote-sdp-received', fakeError);
|
|
7455
7904
|
checkBehavioralMetricSent(
|
|
@@ -7466,7 +7915,7 @@ describe('plugin-meetings', () => {
|
|
|
7466
7915
|
cause: {name: fakeRootCauseName},
|
|
7467
7916
|
});
|
|
7468
7917
|
|
|
7469
|
-
eventListeners[
|
|
7918
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7470
7919
|
|
|
7471
7920
|
checkMetricSent('client.media-engine.remote-sdp-received', fakeError);
|
|
7472
7921
|
checkBehavioralMetricSent(
|
|
@@ -7481,7 +7930,7 @@ describe('plugin-meetings', () => {
|
|
|
7481
7930
|
// SdpError is usually without a cause
|
|
7482
7931
|
const fakeError = new Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
|
|
7483
7932
|
|
|
7484
|
-
eventListeners[
|
|
7933
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7485
7934
|
|
|
7486
7935
|
checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
|
|
7487
7936
|
// expectedMetadataType is the error name in this case
|
|
@@ -7499,7 +7948,7 @@ describe('plugin-meetings', () => {
|
|
|
7499
7948
|
name: fakeErrorName,
|
|
7500
7949
|
});
|
|
7501
7950
|
|
|
7502
|
-
eventListeners[
|
|
7951
|
+
eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
|
|
7503
7952
|
|
|
7504
7953
|
checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
|
|
7505
7954
|
// expectedMetadataType is the error name in this case
|
|
@@ -7525,7 +7974,7 @@ describe('plugin-meetings', () => {
|
|
|
7525
7974
|
};
|
|
7526
7975
|
meeting.sdpResponseTimer = '1234';
|
|
7527
7976
|
|
|
7528
|
-
eventListeners[
|
|
7977
|
+
eventListeners[MediaConnectionEventNames.REMOTE_SDP_ANSWER_PROCESSED]();
|
|
7529
7978
|
|
|
7530
7979
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7531
7980
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7553,7 +8002,7 @@ describe('plugin-meetings', () => {
|
|
|
7553
8002
|
it('handles LOCAL_SDP_OFFER_GENERATED correctly', () => {
|
|
7554
8003
|
assert.equal(meeting.deferSDPAnswer, undefined);
|
|
7555
8004
|
|
|
7556
|
-
eventListeners[
|
|
8005
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
7557
8006
|
|
|
7558
8007
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7559
8008
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7565,7 +8014,7 @@ describe('plugin-meetings', () => {
|
|
|
7565
8014
|
});
|
|
7566
8015
|
|
|
7567
8016
|
it('handles LOCAL_SDP_ANSWER_GENERATED correctly', () => {
|
|
7568
|
-
eventListeners[
|
|
8017
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_ANSWER_GENERATED]();
|
|
7569
8018
|
|
|
7570
8019
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
7571
8020
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
@@ -7575,7 +8024,7 @@ describe('plugin-meetings', () => {
|
|
|
7575
8024
|
});
|
|
7576
8025
|
});
|
|
7577
8026
|
|
|
7578
|
-
describe('handles
|
|
8027
|
+
describe('handles MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND correctly', () => {
|
|
7579
8028
|
let sendRoapOKStub;
|
|
7580
8029
|
let sendRoapMediaRequestStub;
|
|
7581
8030
|
let sendRoapAnswerStub;
|
|
@@ -7593,7 +8042,7 @@ describe('plugin-meetings', () => {
|
|
|
7593
8042
|
});
|
|
7594
8043
|
|
|
7595
8044
|
it('handles OK message correctly', () => {
|
|
7596
|
-
eventListeners[
|
|
8045
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7597
8046
|
roapMessage: {messageType: 'OK', seq: 1},
|
|
7598
8047
|
});
|
|
7599
8048
|
|
|
@@ -7608,7 +8057,7 @@ describe('plugin-meetings', () => {
|
|
|
7608
8057
|
it('handles OFFER message correctly (no answer in the http response)', async () => {
|
|
7609
8058
|
sinon.stub(meeting, 'roapMessageReceived');
|
|
7610
8059
|
|
|
7611
|
-
eventListeners[
|
|
8060
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7612
8061
|
roapMessage: {
|
|
7613
8062
|
messageType: 'OFFER',
|
|
7614
8063
|
seq: 1,
|
|
@@ -7634,7 +8083,7 @@ describe('plugin-meetings', () => {
|
|
|
7634
8083
|
sendRoapMediaRequestStub.resolves({roapAnswer: fakeAnswer});
|
|
7635
8084
|
sinon.stub(meeting, 'roapMessageReceived');
|
|
7636
8085
|
|
|
7637
|
-
eventListeners[
|
|
8086
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7638
8087
|
roapMessage: {
|
|
7639
8088
|
messageType: 'OFFER',
|
|
7640
8089
|
seq: 1,
|
|
@@ -7656,14 +8105,20 @@ describe('plugin-meetings', () => {
|
|
|
7656
8105
|
});
|
|
7657
8106
|
|
|
7658
8107
|
it('handles OFFER message correctly when request fails', async () => {
|
|
8108
|
+
const fakeError = new Error('fake error');
|
|
7659
8109
|
const clock = sinon.useFakeTimers();
|
|
7660
8110
|
sinon.spy(clock, 'clearTimeout');
|
|
7661
8111
|
meeting.deferSDPAnswer = {reject: sinon.stub()};
|
|
7662
8112
|
meeting.sdpResponseTimer = '1234';
|
|
7663
|
-
sendRoapMediaRequestStub.rejects();
|
|
8113
|
+
sendRoapMediaRequestStub.rejects(fakeError);
|
|
7664
8114
|
sinon.stub(meeting, 'roapMessageReceived');
|
|
8115
|
+
const getErrorPayloadForClientErrorCodeStub =
|
|
8116
|
+
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
8117
|
+
sinon
|
|
8118
|
+
.stub()
|
|
8119
|
+
.callsFake(({clientErrorCode}) => ({errorCode: clientErrorCode, fatal: true})));
|
|
7665
8120
|
|
|
7666
|
-
eventListeners[
|
|
8121
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7667
8122
|
roapMessage: {
|
|
7668
8123
|
messageType: 'OFFER',
|
|
7669
8124
|
seq: 1,
|
|
@@ -7686,10 +8141,25 @@ describe('plugin-meetings', () => {
|
|
|
7686
8141
|
assert.calledOnce(clock.clearTimeout);
|
|
7687
8142
|
assert.calledWith(clock.clearTimeout, '1234');
|
|
7688
8143
|
assert.equal(meeting.sdpResponseTimer, undefined);
|
|
8144
|
+
|
|
8145
|
+
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
8146
|
+
clientErrorCode: 2007,
|
|
8147
|
+
});
|
|
8148
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8149
|
+
name: 'client.media-engine.remote-sdp-received',
|
|
8150
|
+
payload: {
|
|
8151
|
+
canProceed: false,
|
|
8152
|
+
errors: [{errorCode: 2007, fatal: true}],
|
|
8153
|
+
},
|
|
8154
|
+
options: {
|
|
8155
|
+
meetingId: meeting.id,
|
|
8156
|
+
rawError: fakeError,
|
|
8157
|
+
},
|
|
8158
|
+
});
|
|
7689
8159
|
});
|
|
7690
8160
|
|
|
7691
8161
|
it('handles ANSWER message correctly', () => {
|
|
7692
|
-
eventListeners[
|
|
8162
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7693
8163
|
roapMessage: {
|
|
7694
8164
|
messageType: 'ANSWER',
|
|
7695
8165
|
seq: 10,
|
|
@@ -7710,7 +8180,7 @@ describe('plugin-meetings', () => {
|
|
|
7710
8180
|
it('sends metrics if fails to send roap ANSWER message', async () => {
|
|
7711
8181
|
sendRoapAnswerStub.rejects(new Error('sending answer failed'));
|
|
7712
8182
|
|
|
7713
|
-
await eventListeners[
|
|
8183
|
+
await eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7714
8184
|
roapMessage: {
|
|
7715
8185
|
messageType: 'ANSWER',
|
|
7716
8186
|
seq: 10,
|
|
@@ -7734,7 +8204,7 @@ describe('plugin-meetings', () => {
|
|
|
7734
8204
|
|
|
7735
8205
|
[ErrorType.CONFLICT, ErrorType.DOUBLECONFLICT].forEach((errorType) =>
|
|
7736
8206
|
it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
|
|
7737
|
-
eventListeners[
|
|
8207
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7738
8208
|
roapMessage: {
|
|
7739
8209
|
messageType: 'ERROR',
|
|
7740
8210
|
seq: 10,
|
|
@@ -7765,7 +8235,7 @@ describe('plugin-meetings', () => {
|
|
|
7765
8235
|
);
|
|
7766
8236
|
|
|
7767
8237
|
it('handles ERROR message indicating other errors correctly', () => {
|
|
7768
|
-
eventListeners[
|
|
8238
|
+
eventListeners[MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND]({
|
|
7769
8239
|
roapMessage: {
|
|
7770
8240
|
messageType: 'ERROR',
|
|
7771
8241
|
seq: 10,
|
|
@@ -7793,8 +8263,8 @@ describe('plugin-meetings', () => {
|
|
|
7793
8263
|
});
|
|
7794
8264
|
|
|
7795
8265
|
it('registers for audio and video source count changed', () => {
|
|
7796
|
-
assert.isFunction(eventListeners[
|
|
7797
|
-
assert.isFunction(eventListeners[
|
|
8266
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED]);
|
|
8267
|
+
assert.isFunction(eventListeners[MediaConnectionEventNames.AUDIO_SOURCES_COUNT_CHANGED]);
|
|
7798
8268
|
});
|
|
7799
8269
|
|
|
7800
8270
|
it('forwards the VIDEO_SOURCES_COUNT_CHANGED event as "media:remoteVideoSourceCountChanged"', () => {
|
|
@@ -7804,7 +8274,7 @@ describe('plugin-meetings', () => {
|
|
|
7804
8274
|
|
|
7805
8275
|
sinon.stub(meeting.mediaRequestManagers.video, 'setNumCurrentSources');
|
|
7806
8276
|
|
|
7807
|
-
eventListeners[
|
|
8277
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED](
|
|
7808
8278
|
numTotalSources,
|
|
7809
8279
|
numLiveSources,
|
|
7810
8280
|
mediaContent
|
|
@@ -7828,7 +8298,7 @@ describe('plugin-meetings', () => {
|
|
|
7828
8298
|
const numLiveSources = 2;
|
|
7829
8299
|
const mediaContent = 'MAIN';
|
|
7830
8300
|
|
|
7831
|
-
eventListeners[
|
|
8301
|
+
eventListeners[MediaConnectionEventNames.AUDIO_SOURCES_COUNT_CHANGED](
|
|
7832
8302
|
numTotalSources,
|
|
7833
8303
|
numLiveSources,
|
|
7834
8304
|
mediaContent
|
|
@@ -7856,7 +8326,7 @@ describe('plugin-meetings', () => {
|
|
|
7856
8326
|
'setNumCurrentSources'
|
|
7857
8327
|
);
|
|
7858
8328
|
|
|
7859
|
-
eventListeners[
|
|
8329
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED](
|
|
7860
8330
|
numTotalSources,
|
|
7861
8331
|
numLiveSources,
|
|
7862
8332
|
'MAIN'
|
|
@@ -7874,7 +8344,7 @@ describe('plugin-meetings', () => {
|
|
|
7874
8344
|
'setNumCurrentSources'
|
|
7875
8345
|
);
|
|
7876
8346
|
|
|
7877
|
-
eventListeners[
|
|
8347
|
+
eventListeners[MediaConnectionEventNames.VIDEO_SOURCES_COUNT_CHANGED](
|
|
7878
8348
|
numTotalSources,
|
|
7879
8349
|
numLiveSources,
|
|
7880
8350
|
'SLIDES'
|
|
@@ -9781,6 +10251,7 @@ describe('plugin-meetings', () => {
|
|
|
9781
10251
|
beforeEach(() => {
|
|
9782
10252
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
9783
10253
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
10254
|
+
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
9784
10255
|
webex.internal.llm.registerAndConnect = sinon
|
|
9785
10256
|
.stub()
|
|
9786
10257
|
.returns(Promise.resolve('something'));
|
|
@@ -9808,6 +10279,7 @@ describe('plugin-meetings', () => {
|
|
|
9808
10279
|
meeting.joinedWith = {state: 'JOINED'};
|
|
9809
10280
|
webex.internal.llm.isConnected.returns(true);
|
|
9810
10281
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
10282
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9811
10283
|
|
|
9812
10284
|
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
9813
10285
|
|
|
@@ -9844,6 +10316,7 @@ describe('plugin-meetings', () => {
|
|
|
9844
10316
|
meeting.joinedWith = {state: 'JOINED'};
|
|
9845
10317
|
webex.internal.llm.isConnected.returns(true);
|
|
9846
10318
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
10319
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
9847
10320
|
|
|
9848
10321
|
meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
|
|
9849
10322
|
|
|
@@ -9869,6 +10342,36 @@ describe('plugin-meetings', () => {
|
|
|
9869
10342
|
);
|
|
9870
10343
|
});
|
|
9871
10344
|
|
|
10345
|
+
it('disconnects if first if the data channel url has changed', async () => {
|
|
10346
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
10347
|
+
webex.internal.llm.isConnected.returns(true);
|
|
10348
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
10349
|
+
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
10350
|
+
|
|
10351
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a different datachannel url'}};
|
|
10352
|
+
|
|
10353
|
+
const result = await meeting.updateLLMConnection();
|
|
10354
|
+
|
|
10355
|
+
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
10356
|
+
assert.calledWith(
|
|
10357
|
+
webex.internal.llm.registerAndConnect,
|
|
10358
|
+
'a url',
|
|
10359
|
+
'a different datachannel url'
|
|
10360
|
+
);
|
|
10361
|
+
assert.equal(result, 'something');
|
|
10362
|
+
assert.calledWithExactly(
|
|
10363
|
+
meeting.webex.internal.llm.off,
|
|
10364
|
+
'event:relay.event',
|
|
10365
|
+
meeting.processRelayEvent
|
|
10366
|
+
);
|
|
10367
|
+
assert.calledTwice(meeting.webex.internal.llm.off);
|
|
10368
|
+
assert.calledOnceWithExactly(
|
|
10369
|
+
meeting.webex.internal.llm.on,
|
|
10370
|
+
'event:relay.event',
|
|
10371
|
+
meeting.processRelayEvent
|
|
10372
|
+
);
|
|
10373
|
+
});
|
|
10374
|
+
|
|
9872
10375
|
it('disconnects when the state is not JOINED', async () => {
|
|
9873
10376
|
meeting.joinedWith = {state: 'any other state'};
|
|
9874
10377
|
webex.internal.llm.isConnected.returns(true);
|