@webex/plugin-meetings 3.8.0-next.8 → 3.8.0-next.81
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 +70 -6
- package/dist/breakouts/index.js.map +1 -1
- package/dist/common/errors/webex-errors.js +12 -2
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +20 -123
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js +2 -0
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/controls-options-manager/types.js.map +1 -1
- package/dist/controls-options-manager/util.js +52 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +28 -10
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +62 -12
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +432 -418
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +17 -17
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +94 -6
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/brbState.js +6 -0
- package/dist/meeting/brbState.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +17 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +570 -302
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +0 -17
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +0 -2
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +30 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +13 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +373 -68
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +5 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/index.js +136 -1
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +14 -0
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +330 -353
- package/dist/member/util.js.map +1 -1
- package/dist/members/index.js +42 -0
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +38 -0
- package/dist/members/request.js.map +1 -1
- package/dist/members/util.js +36 -1
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +9 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/clusterReachability.js +63 -27
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +112 -47
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +14 -0
- package/dist/reachability/reachability.types.js.map +1 -1
- package/dist/reachability/request.js +19 -3
- package/dist/reachability/request.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -2
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/turnDiscovery.js +45 -27
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/roap/types.js +17 -0
- package/dist/roap/types.js.map +1 -0
- package/dist/types/common/errors/webex-errors.d.ts +7 -1
- package/dist/types/config.d.ts +3 -0
- package/dist/types/constants.d.ts +13 -85
- package/dist/types/controls-options-manager/enums.d.ts +3 -1
- package/dist/types/controls-options-manager/types.d.ts +7 -1
- package/dist/types/locus-info/index.d.ts +3 -3
- package/dist/types/locus-info/selfUtils.d.ts +216 -1
- package/dist/types/media/properties.d.ts +15 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
- package/dist/types/meeting/index.d.ts +43 -1
- package/dist/types/meeting/muteState.d.ts +0 -1
- package/dist/types/meeting/request.d.ts +12 -1
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +3 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +82 -1
- package/dist/types/meetings/index.d.ts +57 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +159 -1
- package/dist/types/members/index.d.ts +15 -0
- package/dist/types/members/request.d.ts +26 -0
- package/dist/types/members/util.d.ts +27 -0
- package/dist/types/metrics/constants.d.ts +9 -0
- package/dist/types/reachability/clusterReachability.d.ts +15 -7
- package/dist/types/reachability/index.d.ts +10 -1
- package/dist/types/reachability/reachability.types.d.ts +5 -0
- package/dist/types/roap/index.d.ts +3 -2
- package/dist/types/roap/turnDiscovery.d.ts +5 -17
- package/dist/types/roap/types.d.ts +16 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +24 -23
- package/src/breakouts/index.ts +69 -0
- package/src/common/errors/webex-errors.ts +8 -1
- package/src/config.ts +3 -0
- package/src/constants.ts +20 -90
- package/src/controls-options-manager/enums.ts +2 -0
- package/src/controls-options-manager/types.ts +11 -1
- package/src/controls-options-manager/util.ts +62 -0
- package/src/locus-info/controlsUtils.ts +44 -14
- package/src/locus-info/index.ts +56 -13
- package/src/locus-info/selfUtils.ts +496 -442
- package/src/media/index.ts +23 -21
- package/src/media/properties.ts +96 -0
- package/src/meeting/brbState.ts +7 -0
- package/src/meeting/in-meeting-actions.ts +32 -0
- package/src/meeting/index.ts +382 -93
- package/src/meeting/locusMediaRequest.ts +0 -18
- package/src/meeting/muteState.ts +0 -2
- package/src/meeting/request.ts +36 -1
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +11 -2
- package/src/meeting-info/meeting-info-v2.ts +254 -8
- package/src/meeting-info/utilv2.ts +5 -0
- package/src/meetings/index.ts +148 -1
- package/src/meetings/util.ts +18 -0
- package/src/member/index.ts +13 -2
- package/src/member/util.ts +351 -348
- package/src/members/index.ts +47 -0
- package/src/members/request.ts +44 -0
- package/src/members/util.ts +43 -1
- package/src/metrics/constants.ts +9 -0
- package/src/reachability/clusterReachability.ts +73 -26
- package/src/reachability/index.ts +70 -1
- package/src/reachability/reachability.types.ts +6 -0
- package/src/reachability/request.ts +7 -0
- package/src/reconnection-manager/index.ts +2 -2
- package/src/roap/index.ts +3 -7
- package/src/roap/turnDiscovery.ts +34 -39
- package/src/roap/types.ts +23 -0
- package/test/unit/spec/breakouts/index.ts +167 -95
- package/test/unit/spec/controls-options-manager/util.js +120 -0
- package/test/unit/spec/locus-info/controlsUtils.js +103 -9
- package/test/unit/spec/locus-info/index.js +167 -73
- package/test/unit/spec/locus-info/selfUtils.js +98 -24
- package/test/unit/spec/media/index.ts +150 -18
- package/test/unit/spec/media/properties.ts +130 -0
- package/test/unit/spec/meeting/brbState.ts +19 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
- package/test/unit/spec/meeting/index.js +557 -35
- package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
- package/test/unit/spec/meeting/muteState.js +0 -2
- package/test/unit/spec/meeting/request.js +32 -1
- package/test/unit/spec/meeting/utils.js +119 -18
- package/test/unit/spec/meeting-info/meetinginfov2.js +484 -114
- package/test/unit/spec/meeting-info/utilv2.js +19 -0
- package/test/unit/spec/meetings/index.js +146 -2
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/members/index.js +140 -26
- package/test/unit/spec/members/request.js +68 -22
- package/test/unit/spec/members/utils.js +75 -0
- package/test/unit/spec/reachability/clusterReachability.ts +88 -56
- package/test/unit/spec/reachability/index.ts +101 -0
- package/test/unit/spec/reachability/request.js +47 -2
- package/test/unit/spec/reconnection-manager/index.js +4 -4
- package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -1,6 +1,7 @@
|
|
1
1
|
/*!
|
2
2
|
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
3
3
|
*/
|
4
|
+
import {v4 as uuidv4} from 'uuid';
|
4
5
|
import 'jsdom-global/register';
|
5
6
|
import {cloneDeep, forEach, isEqual, isUndefined} from 'lodash';
|
6
7
|
import sinon from 'sinon';
|
@@ -115,9 +116,9 @@ import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagno
|
|
115
116
|
import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
|
116
117
|
|
117
118
|
import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
118
|
-
import {
|
119
|
+
import {createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
|
119
120
|
import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
|
120
|
-
import {
|
121
|
+
import {EventEmitter} from 'stream';
|
121
122
|
|
122
123
|
describe('plugin-meetings', () => {
|
123
124
|
const logger = {
|
@@ -210,6 +211,7 @@ describe('plugin-meetings', () => {
|
|
210
211
|
let membersSpy;
|
211
212
|
let meetingRequestSpy;
|
212
213
|
let correlationId;
|
214
|
+
let isoLocalClientMeetingJoinTime;
|
213
215
|
let uploadEvent;
|
214
216
|
|
215
217
|
beforeEach(() => {
|
@@ -241,6 +243,7 @@ describe('plugin-meetings', () => {
|
|
241
243
|
},
|
242
244
|
});
|
243
245
|
|
246
|
+
webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
|
244
247
|
webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
|
245
248
|
webex.internal.services = {get: sinon.stub().returns('locus-url')};
|
246
249
|
webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
|
@@ -251,6 +254,7 @@ describe('plugin-meetings', () => {
|
|
251
254
|
getReachabilityResults: sinon.stub().resolves(undefined),
|
252
255
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
253
256
|
stopReachability: sinon.stub(),
|
257
|
+
isSubnetReachable: sinon.stub().returns(true),
|
254
258
|
};
|
255
259
|
webex.internal.llm.on = sinon.stub();
|
256
260
|
webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
|
@@ -281,7 +285,7 @@ describe('plugin-meetings', () => {
|
|
281
285
|
testDestination = `testDestination-${uuid.v4()}`;
|
282
286
|
correlationId = uuid.v4();
|
283
287
|
uploadEvent = new EventEmitter();
|
284
|
-
uploadEvent.addListener('progress', () => {})
|
288
|
+
uploadEvent.addListener('progress', () => {});
|
285
289
|
|
286
290
|
meeting = new Meeting(
|
287
291
|
{
|
@@ -610,6 +614,22 @@ describe('plugin-meetings', () => {
|
|
610
614
|
assert.calledWith(meeting.members.cancelPhoneInvite, uuid1);
|
611
615
|
});
|
612
616
|
});
|
617
|
+
describe('#cancelSIPInvite', () => {
|
618
|
+
it('should have #cancelSIPInvite', () => {
|
619
|
+
assert.exists(meeting.cancelSIPInvite);
|
620
|
+
});
|
621
|
+
beforeEach(() => {
|
622
|
+
meeting.members.cancelSIPInvite = sinon.stub().returns(Promise.resolve(test1));
|
623
|
+
});
|
624
|
+
it('should proxy members #cancelSIPInvite and return a promise', async () => {
|
625
|
+
const cancel = meeting.cancelSIPInvite({memberId: uuid1});
|
626
|
+
|
627
|
+
assert.exists(cancel.then);
|
628
|
+
await cancel;
|
629
|
+
assert.calledOnce(meeting.members.cancelSIPInvite);
|
630
|
+
assert.calledWith(meeting.members.cancelSIPInvite, {memberId: uuid1});
|
631
|
+
});
|
632
|
+
});
|
613
633
|
describe('#admit', () => {
|
614
634
|
it('should have #admit', () => {
|
615
635
|
assert.exists(meeting.admit);
|
@@ -1692,10 +1712,6 @@ describe('plugin-meetings', () => {
|
|
1692
1712
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
1693
1713
|
});
|
1694
1714
|
|
1695
|
-
afterEach(() => {
|
1696
|
-
assert.exists(meeting.isoLocalClientMeetingJoinTime);
|
1697
|
-
});
|
1698
|
-
|
1699
1715
|
it('should join the meeting and return promise', async () => {
|
1700
1716
|
const join = meeting.join({pstnAudioType: 'dial-in'});
|
1701
1717
|
meeting.config.enableAutomaticLLM = true;
|
@@ -2047,7 +2063,12 @@ describe('plugin-meetings', () => {
|
|
2047
2063
|
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
2048
2064
|
meeting.mediaProperties.getCurrentConnectionInfo = sinon
|
2049
2065
|
.stub()
|
2050
|
-
.resolves({
|
2066
|
+
.resolves({
|
2067
|
+
connectionType: 'udp',
|
2068
|
+
selectedCandidatePairChanges: 2,
|
2069
|
+
numTransports: 1,
|
2070
|
+
ipVersion: 'IPv6',
|
2071
|
+
});
|
2051
2072
|
meeting.audio = muteStateStub;
|
2052
2073
|
meeting.video = muteStateStub;
|
2053
2074
|
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
@@ -2110,6 +2131,7 @@ describe('plugin-meetings', () => {
|
|
2110
2131
|
someReachabilityMetric2: 'some value2',
|
2111
2132
|
}),
|
2112
2133
|
stopReachability: sinon.stub(),
|
2134
|
+
isSubnetReachable: sinon.stub().returns(false),
|
2113
2135
|
};
|
2114
2136
|
|
2115
2137
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
@@ -2165,6 +2187,8 @@ describe('plugin-meetings', () => {
|
|
2165
2187
|
someReachabilityMetric1: 'some value1',
|
2166
2188
|
someReachabilityMetric2: 'some value2',
|
2167
2189
|
selectedCandidatePairChanges: 2,
|
2190
|
+
isSubnetReachable: null,
|
2191
|
+
selectedCluster: null,
|
2168
2192
|
numTransports: 1,
|
2169
2193
|
iceCandidatesCount: 0,
|
2170
2194
|
}
|
@@ -2211,6 +2235,8 @@ describe('plugin-meetings', () => {
|
|
2211
2235
|
signalingState: 'unknown',
|
2212
2236
|
connectionState: 'unknown',
|
2213
2237
|
iceConnectionState: 'unknown',
|
2238
|
+
isSubnetReachable: null,
|
2239
|
+
selectedCluster: null,
|
2214
2240
|
})
|
2215
2241
|
);
|
2216
2242
|
|
@@ -2225,6 +2251,7 @@ describe('plugin-meetings', () => {
|
|
2225
2251
|
someReachabilityMetric1: 'some value1',
|
2226
2252
|
someReachabilityMetric2: 'some value2',
|
2227
2253
|
}),
|
2254
|
+
isSubnetReachable: sinon.stub().returns(true),
|
2228
2255
|
};
|
2229
2256
|
|
2230
2257
|
meeting.waitForRemoteSDPAnswer = sinon.stub().rejects();
|
@@ -2275,6 +2302,8 @@ describe('plugin-meetings', () => {
|
|
2275
2302
|
selectedCandidatePairChanges: 2,
|
2276
2303
|
numTransports: 1,
|
2277
2304
|
iceCandidatesCount: 0,
|
2305
|
+
isSubnetReachable: null,
|
2306
|
+
selectedCluster: null,
|
2278
2307
|
}
|
2279
2308
|
);
|
2280
2309
|
});
|
@@ -2332,6 +2361,8 @@ describe('plugin-meetings', () => {
|
|
2332
2361
|
signalingState: 'have-local-offer',
|
2333
2362
|
connectionState: 'connecting',
|
2334
2363
|
iceConnectionState: 'checking',
|
2364
|
+
isSubnetReachable: null,
|
2365
|
+
selectedCluster: null,
|
2335
2366
|
})
|
2336
2367
|
);
|
2337
2368
|
|
@@ -2389,6 +2420,8 @@ describe('plugin-meetings', () => {
|
|
2389
2420
|
signalingState: 'have-local-offer',
|
2390
2421
|
connectionState: 'connecting',
|
2391
2422
|
iceConnectionState: 'checking',
|
2423
|
+
isSubnetReachable: null,
|
2424
|
+
selectedCluster: null,
|
2392
2425
|
})
|
2393
2426
|
);
|
2394
2427
|
|
@@ -2667,7 +2700,7 @@ describe('plugin-meetings', () => {
|
|
2667
2700
|
|
2668
2701
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
2669
2702
|
turnServerInfo: {
|
2670
|
-
|
2703
|
+
urls: [FAKE_TURN_URL],
|
2671
2704
|
username: FAKE_TURN_USER,
|
2672
2705
|
password: FAKE_TURN_PASSWORD,
|
2673
2706
|
},
|
@@ -2689,7 +2722,7 @@ describe('plugin-meetings', () => {
|
|
2689
2722
|
meeting.id,
|
2690
2723
|
sinon.match({
|
2691
2724
|
turnServerInfo: {
|
2692
|
-
|
2725
|
+
urls: [FAKE_TURN_URL],
|
2693
2726
|
username: FAKE_TURN_USER,
|
2694
2727
|
password: FAKE_TURN_PASSWORD,
|
2695
2728
|
},
|
@@ -2724,8 +2757,9 @@ describe('plugin-meetings', () => {
|
|
2724
2757
|
sinon.stub().returns(FAKE_ERROR));
|
2725
2758
|
webex.meetings.reachability = {
|
2726
2759
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
|
2727
|
-
getReachabilityMetrics: sinon.stub().resolves(),
|
2760
|
+
getReachabilityMetrics: sinon.stub().resolves({}),
|
2728
2761
|
stopReachability: sinon.stub(),
|
2762
|
+
isSubnetReachable: sinon.stub().returns(true),
|
2729
2763
|
};
|
2730
2764
|
const MOCK_CLIENT_ERROR_CODE = 2004;
|
2731
2765
|
const generateClientErrorCodeForIceFailureStub = sinon
|
@@ -2747,14 +2781,15 @@ describe('plugin-meetings', () => {
|
|
2747
2781
|
.onSecondCall()
|
2748
2782
|
.returns({
|
2749
2783
|
turnServerInfo: {
|
2750
|
-
|
2784
|
+
urls: [FAKE_TURN_URL],
|
2751
2785
|
username: FAKE_TURN_USER,
|
2752
2786
|
password: FAKE_TURN_PASSWORD,
|
2753
2787
|
},
|
2754
2788
|
turnDiscoverySkippedReason: undefined,
|
2755
2789
|
});
|
2756
2790
|
meeting.meetingState = 'ACTIVE';
|
2757
|
-
|
2791
|
+
const error = {iceConnected: false};
|
2792
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects(error);
|
2758
2793
|
|
2759
2794
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
2760
2795
|
const closeMediaConnectionStub = sinon.stub();
|
@@ -2772,6 +2807,7 @@ describe('plugin-meetings', () => {
|
|
2772
2807
|
})
|
2773
2808
|
.catch((err) => {
|
2774
2809
|
errorThrown = err;
|
2810
|
+
assert.instanceOf(err.cause, Error);
|
2775
2811
|
assert.instanceOf(err, AddMediaFailed);
|
2776
2812
|
});
|
2777
2813
|
|
@@ -2828,6 +2864,7 @@ describe('plugin-meetings', () => {
|
|
2828
2864
|
},
|
2829
2865
|
options: {
|
2830
2866
|
meetingId: meeting.id,
|
2867
|
+
rawError: error,
|
2831
2868
|
},
|
2832
2869
|
});
|
2833
2870
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
|
@@ -2839,6 +2876,7 @@ describe('plugin-meetings', () => {
|
|
2839
2876
|
},
|
2840
2877
|
options: {
|
2841
2878
|
meetingId: meeting.id,
|
2879
|
+
rawError: error,
|
2842
2880
|
},
|
2843
2881
|
});
|
2844
2882
|
|
@@ -2905,6 +2943,8 @@ describe('plugin-meetings', () => {
|
|
2905
2943
|
selectedCandidatePairChanges: 2,
|
2906
2944
|
numTransports: 1,
|
2907
2945
|
iceCandidatesCount: 0,
|
2946
|
+
isSubnetReachable: null,
|
2947
|
+
selectedCluster: null,
|
2908
2948
|
},
|
2909
2949
|
]);
|
2910
2950
|
|
@@ -2935,6 +2975,7 @@ describe('plugin-meetings', () => {
|
|
2935
2975
|
.resolves(false),
|
2936
2976
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
2937
2977
|
stopReachability: sinon.stub(),
|
2978
|
+
isSubnetReachable: sinon.stub().returns(true),
|
2938
2979
|
};
|
2939
2980
|
const getErrorPayloadForClientErrorCodeStub =
|
2940
2981
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
@@ -2959,16 +3000,19 @@ describe('plugin-meetings', () => {
|
|
2959
3000
|
.onSecondCall()
|
2960
3001
|
.returns({
|
2961
3002
|
turnServerInfo: {
|
2962
|
-
|
3003
|
+
urls: [FAKE_TURN_URL],
|
2963
3004
|
username: FAKE_TURN_USER,
|
2964
3005
|
password: FAKE_TURN_PASSWORD,
|
2965
3006
|
},
|
2966
3007
|
turnDiscoverySkippedReason: undefined,
|
2967
3008
|
});
|
3009
|
+
|
3010
|
+
const mediaConnectionError = new Error('fake error');
|
3011
|
+
|
2968
3012
|
meeting.mediaProperties.waitForMediaConnectionConnected = sinon
|
2969
3013
|
.stub()
|
2970
3014
|
.onFirstCall()
|
2971
|
-
.rejects()
|
3015
|
+
.rejects(mediaConnectionError)
|
2972
3016
|
.onSecondCall()
|
2973
3017
|
.resolves();
|
2974
3018
|
|
@@ -3037,10 +3081,14 @@ describe('plugin-meetings', () => {
|
|
3037
3081
|
},
|
3038
3082
|
options: {
|
3039
3083
|
meetingId: meeting.id,
|
3084
|
+
rawError: mediaConnectionError,
|
3040
3085
|
},
|
3041
3086
|
});
|
3042
3087
|
assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
|
3043
3088
|
name: 'client.media-engine.ready',
|
3089
|
+
payload: {
|
3090
|
+
ipVersion: 'IPv6',
|
3091
|
+
},
|
3044
3092
|
options: {
|
3045
3093
|
meetingId: meeting.id,
|
3046
3094
|
},
|
@@ -3097,11 +3145,14 @@ describe('plugin-meetings', () => {
|
|
3097
3145
|
locus_id: meeting.locusUrl.split('/').pop(),
|
3098
3146
|
connectionType: 'udp',
|
3099
3147
|
selectedCandidatePairChanges: 2,
|
3148
|
+
ipVersion: 'IPv6',
|
3100
3149
|
numTransports: 1,
|
3101
3150
|
isMultistream: false,
|
3102
3151
|
retriedWithTurnServer: true,
|
3103
3152
|
isJoinWithMediaRetry: false,
|
3104
3153
|
iceCandidatesCount: 0,
|
3154
|
+
isSubnetReachable: null,
|
3155
|
+
selectedCluster: null,
|
3105
3156
|
},
|
3106
3157
|
]);
|
3107
3158
|
meeting.roap.doTurnDiscovery;
|
@@ -3136,7 +3187,7 @@ describe('plugin-meetings', () => {
|
|
3136
3187
|
.onSecondCall()
|
3137
3188
|
.returns({
|
3138
3189
|
turnServerInfo: {
|
3139
|
-
|
3190
|
+
urls: [FAKE_TURN_URL],
|
3140
3191
|
username: FAKE_TURN_USER,
|
3141
3192
|
password: FAKE_TURN_PASSWORD,
|
3142
3193
|
},
|
@@ -3188,7 +3239,7 @@ describe('plugin-meetings', () => {
|
|
3188
3239
|
.onSecondCall()
|
3189
3240
|
.returns({
|
3190
3241
|
turnServerInfo: {
|
3191
|
-
|
3242
|
+
urls: [FAKE_TURN_URL],
|
3192
3243
|
username: FAKE_TURN_USER,
|
3193
3244
|
password: FAKE_TURN_PASSWORD,
|
3194
3245
|
},
|
@@ -3230,7 +3281,13 @@ describe('plugin-meetings', () => {
|
|
3230
3281
|
someReachabilityMetric2: 'some value2',
|
3231
3282
|
}),
|
3232
3283
|
stopReachability: sinon.stub(),
|
3284
|
+
isSubnetReachable: sinon.stub().returns(true),
|
3233
3285
|
};
|
3286
|
+
meeting.mediaConnections = [
|
3287
|
+
{
|
3288
|
+
mediaAgentCluster: 'some.cluster',
|
3289
|
+
}
|
3290
|
+
]
|
3234
3291
|
meeting.iceCandidatesCount = 3;
|
3235
3292
|
meeting.iceCandidateErrors.set('701_error', 3);
|
3236
3293
|
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
@@ -3248,6 +3305,7 @@ describe('plugin-meetings', () => {
|
|
3248
3305
|
locus_id: meeting.locusUrl.split('/').pop(),
|
3249
3306
|
connectionType: 'udp',
|
3250
3307
|
selectedCandidatePairChanges: 2,
|
3308
|
+
ipVersion: 'IPv6',
|
3251
3309
|
numTransports: 1,
|
3252
3310
|
isMultistream: false,
|
3253
3311
|
retriedWithTurnServer: false,
|
@@ -3257,6 +3315,8 @@ describe('plugin-meetings', () => {
|
|
3257
3315
|
iceCandidatesCount: 3,
|
3258
3316
|
'701_error': 3,
|
3259
3317
|
'701_turn_host_lookup_received_error': 1,
|
3318
|
+
isSubnetReachable: null,
|
3319
|
+
selectedCluster: 'some.cluster',
|
3260
3320
|
}
|
3261
3321
|
);
|
3262
3322
|
|
@@ -3319,6 +3379,8 @@ describe('plugin-meetings', () => {
|
|
3319
3379
|
iceConnectionState: 'unknown',
|
3320
3380
|
selectedCandidatePairChanges: 2,
|
3321
3381
|
numTransports: 1,
|
3382
|
+
isSubnetReachable: null,
|
3383
|
+
selectedCluster: null,
|
3322
3384
|
iceCandidatesCount: 0,
|
3323
3385
|
}
|
3324
3386
|
);
|
@@ -3380,6 +3442,120 @@ describe('plugin-meetings', () => {
|
|
3380
3442
|
numTransports: 1,
|
3381
3443
|
'701_error': 2,
|
3382
3444
|
'701_turn_host_lookup_received_error': 1,
|
3445
|
+
isSubnetReachable: null,
|
3446
|
+
selectedCluster: null,
|
3447
|
+
iceCandidatesCount: 0,
|
3448
|
+
}
|
3449
|
+
);
|
3450
|
+
|
3451
|
+
assert.isOk(errorThrown);
|
3452
|
+
});
|
3453
|
+
|
3454
|
+
it('should send valid isSubnetReachability if media connection success', async () => {
|
3455
|
+
meeting.roap.doTurnDiscovery = sinon.stub().returns({
|
3456
|
+
turnServerInfo: undefined,
|
3457
|
+
turnDiscoverySkippedReason: undefined,
|
3458
|
+
});
|
3459
|
+
meeting.meetingState = 'ACTIVE';
|
3460
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.resolves();
|
3461
|
+
meeting.webex.meetings.reachability = {
|
3462
|
+
getReachabilityMetrics: sinon.stub().resolves({
|
3463
|
+
reachability_public_udp_success: 5,
|
3464
|
+
}),
|
3465
|
+
stopReachability: sinon.stub(),
|
3466
|
+
isSubnetReachable: sinon.stub().returns(false),
|
3467
|
+
};
|
3468
|
+
|
3469
|
+
const forceRtcMetricsSend = sinon.stub().resolves();
|
3470
|
+
const closeMediaConnectionStub = sinon.stub();
|
3471
|
+
Media.createMediaConnection = sinon.stub().returns({
|
3472
|
+
close: closeMediaConnectionStub,
|
3473
|
+
forceRtcMetricsSend,
|
3474
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
3475
|
+
initiateOffer: sinon.stub().resolves({}),
|
3476
|
+
on: sinon.stub(),
|
3477
|
+
});
|
3478
|
+
|
3479
|
+
await meeting.addMedia({
|
3480
|
+
mediaSettings: {},
|
3481
|
+
});
|
3482
|
+
|
3483
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
|
3484
|
+
correlation_id: meeting.correlationId,
|
3485
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
3486
|
+
connectionType: 'udp',
|
3487
|
+
ipVersion: 'IPv6',
|
3488
|
+
selectedCandidatePairChanges: 2,
|
3489
|
+
numTransports: 1,
|
3490
|
+
isMultistream: false,
|
3491
|
+
retriedWithTurnServer: false,
|
3492
|
+
isJoinWithMediaRetry: false,
|
3493
|
+
iceCandidatesCount: 0,
|
3494
|
+
reachability_public_udp_success: 5,
|
3495
|
+
isSubnetReachable: false,
|
3496
|
+
selectedCluster: null,
|
3497
|
+
});
|
3498
|
+
});
|
3499
|
+
|
3500
|
+
it('should send valid isSubnetReachability if media connection fails', async () => {
|
3501
|
+
let errorThrown = undefined;
|
3502
|
+
|
3503
|
+
meeting.roap.doTurnDiscovery = sinon.stub().returns({
|
3504
|
+
turnServerInfo: undefined,
|
3505
|
+
turnDiscoverySkippedReason: undefined,
|
3506
|
+
});
|
3507
|
+
meeting.meetingState = 'ACTIVE';
|
3508
|
+
meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
|
3509
|
+
meeting.webex.meetings.reachability = {
|
3510
|
+
getReachabilityMetrics: sinon.stub().resolves({
|
3511
|
+
reachability_public_udp_success: 5,
|
3512
|
+
}),
|
3513
|
+
stopReachability: sinon.stub(),
|
3514
|
+
isSubnetReachable: sinon.stub().returns(true),
|
3515
|
+
};
|
3516
|
+
|
3517
|
+
const forceRtcMetricsSend = sinon.stub().resolves();
|
3518
|
+
const closeMediaConnectionStub = sinon.stub();
|
3519
|
+
Media.createMediaConnection = sinon.stub().returns({
|
3520
|
+
close: closeMediaConnectionStub,
|
3521
|
+
forceRtcMetricsSend,
|
3522
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
3523
|
+
initiateOffer: sinon.stub().resolves({}),
|
3524
|
+
on: sinon.stub(),
|
3525
|
+
});
|
3526
|
+
|
3527
|
+
await meeting
|
3528
|
+
.addMedia({
|
3529
|
+
mediaSettings: {},
|
3530
|
+
})
|
3531
|
+
.catch((err) => {
|
3532
|
+
errorThrown = err;
|
3533
|
+
assert.instanceOf(err, AddMediaFailed);
|
3534
|
+
});
|
3535
|
+
|
3536
|
+
// Check that the only metric sent is ADD_MEDIA_FAILURE
|
3537
|
+
assert.calledOnceWithExactly(
|
3538
|
+
Metrics.sendBehavioralMetric,
|
3539
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
3540
|
+
{
|
3541
|
+
correlation_id: meeting.correlationId,
|
3542
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
3543
|
+
reason: errorThrown.message,
|
3544
|
+
stack: errorThrown.stack,
|
3545
|
+
code: errorThrown.code,
|
3546
|
+
turnDiscoverySkippedReason: undefined,
|
3547
|
+
turnServerUsed: true,
|
3548
|
+
retriedWithTurnServer: false,
|
3549
|
+
isMultistream: false,
|
3550
|
+
isJoinWithMediaRetry: false,
|
3551
|
+
signalingState: 'unknown',
|
3552
|
+
connectionState: 'unknown',
|
3553
|
+
iceConnectionState: 'unknown',
|
3554
|
+
selectedCandidatePairChanges: 2,
|
3555
|
+
numTransports: 1,
|
3556
|
+
reachability_public_udp_success: 5,
|
3557
|
+
isSubnetReachable: true,
|
3558
|
+
selectedCluster: null,
|
3383
3559
|
iceCandidatesCount: 0,
|
3384
3560
|
}
|
3385
3561
|
);
|
@@ -3399,6 +3575,8 @@ describe('plugin-meetings', () => {
|
|
3399
3575
|
meeting.config.stats.enableStatsAnalyzer = true;
|
3400
3576
|
|
3401
3577
|
statsAnalyzerStub = new EventsScope();
|
3578
|
+
statsAnalyzerStub.getNetworkType = sinon.stub().returns('wifi');
|
3579
|
+
|
3402
3580
|
// mock the StatsAnalyzer constructor
|
3403
3581
|
sinon.stub(InternalMediaCoreModule, 'StatsAnalyzer').returns(statsAnalyzerStub);
|
3404
3582
|
|
@@ -3439,6 +3617,40 @@ describe('plugin-meetings', () => {
|
|
3439
3617
|
});
|
3440
3618
|
});
|
3441
3619
|
|
3620
|
+
it('LOCAL_MEDIA_STARTED triggers "meeting:media:local:start" event and does not send metric because we already have', async () => {
|
3621
|
+
meeting.shareCAEventSentStatus = {
|
3622
|
+
transmitStart: true,
|
3623
|
+
transmitStop: false,
|
3624
|
+
receiveStart: false,
|
3625
|
+
receiveStop: false,
|
3626
|
+
};
|
3627
|
+
statsAnalyzerStub.emit(
|
3628
|
+
{file: 'test', function: 'test'},
|
3629
|
+
StatsAnalyzerEventNames.LOCAL_MEDIA_STARTED,
|
3630
|
+
{mediaType: 'share'}
|
3631
|
+
);
|
3632
|
+
|
3633
|
+
assert.calledWith(
|
3634
|
+
TriggerProxy.trigger,
|
3635
|
+
sinon.match.instanceOf(Meeting),
|
3636
|
+
{
|
3637
|
+
file: 'meeting/index',
|
3638
|
+
function: 'addMedia',
|
3639
|
+
},
|
3640
|
+
EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
|
3641
|
+
{
|
3642
|
+
mediaType: 'share',
|
3643
|
+
}
|
3644
|
+
);
|
3645
|
+
assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
|
3646
|
+
name: 'client.media.tx.start',
|
3647
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
3648
|
+
options: {
|
3649
|
+
meetingId: meeting.id,
|
3650
|
+
},
|
3651
|
+
});
|
3652
|
+
});
|
3653
|
+
|
3442
3654
|
it('LOCAL_MEDIA_STOPPED triggers the right metrics', async () => {
|
3443
3655
|
statsAnalyzerStub.emit(
|
3444
3656
|
{file: 'test', function: 'test'},
|
@@ -3455,6 +3667,28 @@ describe('plugin-meetings', () => {
|
|
3455
3667
|
});
|
3456
3668
|
});
|
3457
3669
|
|
3670
|
+
it('LOCAL_MEDIA_STOPPED does not send metric because we already have', async () => {
|
3671
|
+
meeting.shareCAEventSentStatus = {
|
3672
|
+
transmitStart: false,
|
3673
|
+
transmitStop: true,
|
3674
|
+
receiveStart: false,
|
3675
|
+
receiveStop: false,
|
3676
|
+
};
|
3677
|
+
statsAnalyzerStub.emit(
|
3678
|
+
{file: 'test', function: 'test'},
|
3679
|
+
StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED,
|
3680
|
+
{mediaType: 'share'}
|
3681
|
+
);
|
3682
|
+
|
3683
|
+
assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
|
3684
|
+
name: 'client.media.tx.stop',
|
3685
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
3686
|
+
options: {
|
3687
|
+
meetingId: meeting.id,
|
3688
|
+
},
|
3689
|
+
});
|
3690
|
+
});
|
3691
|
+
|
3458
3692
|
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics', async () => {
|
3459
3693
|
statsAnalyzerStub.emit(
|
3460
3694
|
{file: 'test', function: 'test'},
|
@@ -3535,6 +3769,47 @@ describe('plugin-meetings', () => {
|
|
3535
3769
|
});
|
3536
3770
|
});
|
3537
3771
|
|
3772
|
+
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and does not send metric because we already have', async () => {
|
3773
|
+
meeting.shareCAEventSentStatus = {
|
3774
|
+
transmitStart: false,
|
3775
|
+
transmitStop: false,
|
3776
|
+
receiveStart: true,
|
3777
|
+
receiveStop: false,
|
3778
|
+
};
|
3779
|
+
statsAnalyzerStub.emit(
|
3780
|
+
{file: 'test', function: 'test'},
|
3781
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
|
3782
|
+
{mediaType: 'share'}
|
3783
|
+
);
|
3784
|
+
|
3785
|
+
assert.calledWith(
|
3786
|
+
TriggerProxy.trigger,
|
3787
|
+
sinon.match.instanceOf(Meeting),
|
3788
|
+
{
|
3789
|
+
file: 'meeting/index',
|
3790
|
+
function: 'addMedia',
|
3791
|
+
},
|
3792
|
+
EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
|
3793
|
+
{
|
3794
|
+
mediaType: 'share',
|
3795
|
+
}
|
3796
|
+
);
|
3797
|
+
assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
|
3798
|
+
name: 'client.media.render.start',
|
3799
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
3800
|
+
options: {
|
3801
|
+
meetingId: meeting.id,
|
3802
|
+
},
|
3803
|
+
});
|
3804
|
+
assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
|
3805
|
+
name: 'client.media.rx.start',
|
3806
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
3807
|
+
options: {
|
3808
|
+
meetingId: meeting.id,
|
3809
|
+
},
|
3810
|
+
});
|
3811
|
+
});
|
3812
|
+
|
3538
3813
|
it('REMOTE_MEDIA_STOPPED triggers the right metrics for share', async () => {
|
3539
3814
|
statsAnalyzerStub.emit(
|
3540
3815
|
{file: 'test', function: 'test'},
|
@@ -3559,6 +3834,34 @@ describe('plugin-meetings', () => {
|
|
3559
3834
|
});
|
3560
3835
|
});
|
3561
3836
|
|
3837
|
+
it('REMOTE_MEDIA_STOPPED does not send metric because we already have', async () => {
|
3838
|
+
meeting.shareCAEventSentStatus = {
|
3839
|
+
transmitStart: false,
|
3840
|
+
transmitStop: false,
|
3841
|
+
receiveStart: true,
|
3842
|
+
receiveStop: true,
|
3843
|
+
};
|
3844
|
+
statsAnalyzerStub.emit(
|
3845
|
+
{file: 'test', function: 'test'},
|
3846
|
+
StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
|
3847
|
+
{mediaType: 'share'}
|
3848
|
+
);
|
3849
|
+
assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
|
3850
|
+
name: 'client.media.render.stop',
|
3851
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
3852
|
+
options: {
|
3853
|
+
meetingId: meeting.id,
|
3854
|
+
},
|
3855
|
+
});
|
3856
|
+
assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
|
3857
|
+
name: 'client.media.rx.stop',
|
3858
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
3859
|
+
options: {
|
3860
|
+
meetingId: meeting.id,
|
3861
|
+
},
|
3862
|
+
});
|
3863
|
+
});
|
3864
|
+
|
3562
3865
|
it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
|
3563
3866
|
let fakeMembersCollection = {
|
3564
3867
|
members: {
|
@@ -3568,7 +3871,7 @@ describe('plugin-meetings', () => {
|
|
3568
3871
|
},
|
3569
3872
|
};
|
3570
3873
|
sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
|
3571
|
-
const fakeData = {intervalMetadata: {}
|
3874
|
+
const fakeData = {intervalMetadata: {}};
|
3572
3875
|
|
3573
3876
|
statsAnalyzerStub.emit(
|
3574
3877
|
{file: 'test', function: 'test'},
|
@@ -3609,7 +3912,7 @@ describe('plugin-meetings', () => {
|
|
3609
3912
|
});
|
3610
3913
|
|
3611
3914
|
it('calls submitMQE correctly', async () => {
|
3612
|
-
const fakeData = {intervalMetadata: {bla: 'bla'}
|
3915
|
+
const fakeData = {intervalMetadata: {bla: 'bla'}};
|
3613
3916
|
|
3614
3917
|
statsAnalyzerStub.emit(
|
3615
3918
|
{file: 'test', function: 'test'},
|
@@ -3640,7 +3943,7 @@ describe('plugin-meetings', () => {
|
|
3640
3943
|
|
3641
3944
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
3642
3945
|
turnServerInfo: {
|
3643
|
-
|
3946
|
+
urls: [FAKE_TURN_URL],
|
3644
3947
|
username: FAKE_TURN_USER,
|
3645
3948
|
password: FAKE_TURN_PASSWORD,
|
3646
3949
|
},
|
@@ -3666,7 +3969,7 @@ describe('plugin-meetings', () => {
|
|
3666
3969
|
meeting.id,
|
3667
3970
|
sinon.match({
|
3668
3971
|
turnServerInfo: {
|
3669
|
-
|
3972
|
+
urls: [FAKE_TURN_URL],
|
3670
3973
|
username: FAKE_TURN_USER,
|
3671
3974
|
password: FAKE_TURN_PASSWORD,
|
3672
3975
|
},
|
@@ -3817,6 +4120,9 @@ describe('plugin-meetings', () => {
|
|
3817
4120
|
},
|
3818
4121
|
options: {
|
3819
4122
|
meetingId: meeting.id,
|
4123
|
+
rawError: {
|
4124
|
+
iceConnected: false,
|
4125
|
+
},
|
3820
4126
|
},
|
3821
4127
|
},
|
3822
4128
|
]);
|
@@ -3887,9 +4193,29 @@ describe('plugin-meetings', () => {
|
|
3887
4193
|
} catch (err) {
|
3888
4194
|
assert.instanceOf(err, Error);
|
3889
4195
|
assert.equal(err.message, 'setBrb failed');
|
3890
|
-
assert.isRejected(
|
4196
|
+
assert.isRejected(Promise.reject());
|
3891
4197
|
}
|
3892
4198
|
});
|
4199
|
+
|
4200
|
+
it('updates remote mute state when brb is enabled', async () => {
|
4201
|
+
meeting.audio = {handleServerRemoteMuteUpdate: sinon.stub()};
|
4202
|
+
|
4203
|
+
await meeting.beRightBack(true);
|
4204
|
+
|
4205
|
+
sinon.assert.calledOnceWithExactly(
|
4206
|
+
meeting.audio.handleServerRemoteMuteUpdate,
|
4207
|
+
meeting,
|
4208
|
+
true
|
4209
|
+
);
|
4210
|
+
});
|
4211
|
+
|
4212
|
+
it('does not update remote mute state when brb is disabled', async () => {
|
4213
|
+
meeting.audio = {handleServerRemoteMuteUpdate: sinon.stub()};
|
4214
|
+
|
4215
|
+
await meeting.beRightBack(false);
|
4216
|
+
|
4217
|
+
assert.notCalled(meeting.audio.handleServerRemoteMuteUpdate);
|
4218
|
+
});
|
3893
4219
|
});
|
3894
4220
|
});
|
3895
4221
|
|
@@ -3943,7 +4269,10 @@ describe('plugin-meetings', () => {
|
|
3943
4269
|
.resolves({id: 'fake clientMediaPreferences'});
|
3944
4270
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
3945
4271
|
turnServerInfo: {
|
3946
|
-
|
4272
|
+
urls: [
|
4273
|
+
'turns:turn-server-url1:443?transport=tcp',
|
4274
|
+
'turns:turn-server-url2:443?transport=tcp',
|
4275
|
+
],
|
3947
4276
|
username: 'turn user',
|
3948
4277
|
password: 'turn password',
|
3949
4278
|
},
|
@@ -3961,12 +4290,10 @@ describe('plugin-meetings', () => {
|
|
3961
4290
|
expectedMediaConnectionConfig = {
|
3962
4291
|
iceServers: [
|
3963
4292
|
{
|
3964
|
-
urls:
|
3965
|
-
|
3966
|
-
|
3967
|
-
|
3968
|
-
{
|
3969
|
-
urls: 'turns:turn-server-url:443?transport=tcp',
|
4293
|
+
urls: [
|
4294
|
+
'turns:turn-server-url1:443?transport=tcp',
|
4295
|
+
'turns:turn-server-url2:443?transport=tcp',
|
4296
|
+
],
|
3970
4297
|
username: 'turn user',
|
3971
4298
|
credential: 'turn password',
|
3972
4299
|
},
|
@@ -4048,9 +4375,11 @@ describe('plugin-meetings', () => {
|
|
4048
4375
|
.stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
|
4049
4376
|
.returns(fakeMultistreamRoapMediaConnection);
|
4050
4377
|
|
4051
|
-
locusMediaRequestStub = sinon
|
4052
|
-
|
4053
|
-
|
4378
|
+
locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({
|
4379
|
+
body: {locus: {fullState: {}}},
|
4380
|
+
upload: sinon.match.instanceOf(EventEmitter),
|
4381
|
+
download: sinon.match.instanceOf(EventEmitter),
|
4382
|
+
});
|
4054
4383
|
|
4055
4384
|
// setup some things and mocks so that the call to join() works
|
4056
4385
|
// (we need to call join() because it creates the LocusMediaRequest instance
|
@@ -5234,7 +5563,10 @@ describe('plugin-meetings', () => {
|
|
5234
5563
|
// and check that when we fallback to transcoded we still do another TURN discovery
|
5235
5564
|
await runCheck(
|
5236
5565
|
{
|
5237
|
-
|
5566
|
+
urls: [
|
5567
|
+
'turns:turn-server-url1:443?transport=tcp',
|
5568
|
+
'turns:turn-server-url2:443?transport=tcp',
|
5569
|
+
],
|
5238
5570
|
username: 'turn user',
|
5239
5571
|
password: 'turn password',
|
5240
5572
|
},
|
@@ -5248,7 +5580,10 @@ describe('plugin-meetings', () => {
|
|
5248
5580
|
// but doing it just for completeness
|
5249
5581
|
await runCheck(
|
5250
5582
|
{
|
5251
|
-
|
5583
|
+
urls: [
|
5584
|
+
'turns:turn-server-url1:443?transport=tcp',
|
5585
|
+
'turns:turn-server-url2:443?transport=tcp',
|
5586
|
+
],
|
5252
5587
|
username: 'turn user',
|
5253
5588
|
password: 'turn password',
|
5254
5589
|
},
|
@@ -7530,6 +7865,27 @@ describe('plugin-meetings', () => {
|
|
7530
7865
|
});
|
7531
7866
|
});
|
7532
7867
|
|
7868
|
+
describe('#setIsoLocalClientMeetingJoinTime', () => {
|
7869
|
+
it('should fallback to system clock ISO string when given an undefined value', () => {
|
7870
|
+
const currentSystemTime = new Date().toISOString();
|
7871
|
+
meeting.isoLocalClientMeetingJoinTime = undefined;
|
7872
|
+
assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
|
7873
|
+
});
|
7874
|
+
|
7875
|
+
it('should fallback to system clock ISO string when given an invalid value', () => {
|
7876
|
+
const currentSystemTime = new Date().toISOString();
|
7877
|
+
meeting.isoLocalClientMeetingJoinTime = 'invalid-date';
|
7878
|
+
assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
|
7879
|
+
});
|
7880
|
+
|
7881
|
+
it('should set the isoLocalClientMeetingJoinTime correctly for a valid date string', () => {
|
7882
|
+
const validDateString = 'Tue, 01 Apr 2025 13:00:36 GMT';
|
7883
|
+
const expectedISOString = new Date(validDateString).toISOString();
|
7884
|
+
meeting.isoLocalClientMeetingJoinTime = validDateString;
|
7885
|
+
assert.equal(meeting.isoLocalClientMeetingJoinTime, expectedISOString);
|
7886
|
+
});
|
7887
|
+
});
|
7888
|
+
|
7533
7889
|
describe('#updateCallStateForMetrics', () => {
|
7534
7890
|
it('should update the callState, overriding existing values', () => {
|
7535
7891
|
assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
|
@@ -7611,6 +7967,12 @@ describe('plugin-meetings', () => {
|
|
7611
7967
|
meeting.audio = {handleLocalStreamChange: sinon.stub()};
|
7612
7968
|
meeting.video = {handleLocalStreamChange: sinon.stub()};
|
7613
7969
|
meeting.statsAnalyzer = {updateMediaStatus: sinon.stub()};
|
7970
|
+
meeting.shareCAEventSentStatus = {
|
7971
|
+
transmitStart: false,
|
7972
|
+
transmitStop: false,
|
7973
|
+
receiveStart: false,
|
7974
|
+
receiveStop: false,
|
7975
|
+
};
|
7614
7976
|
fakeMultistreamRoapMediaConnection = {
|
7615
7977
|
createSendSlot: () => {
|
7616
7978
|
return {
|
@@ -7678,6 +8040,9 @@ describe('plugin-meetings', () => {
|
|
7678
8040
|
});
|
7679
8041
|
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
7680
8042
|
|
8043
|
+
assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
|
8044
|
+
assert.equal(meeting.shareCAEventSentStatus.transmitStop, false);
|
8045
|
+
|
7681
8046
|
assert.calledWith(meeting.statsAnalyzer.updateMediaStatus, {
|
7682
8047
|
expected: {sendShare: true},
|
7683
8048
|
});
|
@@ -7698,18 +8063,23 @@ describe('plugin-meetings', () => {
|
|
7698
8063
|
assert.equal(meeting.mediaProperties.shareAudioStream, stream);
|
7699
8064
|
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
7700
8065
|
|
8066
|
+
assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
|
8067
|
+
assert.equal(meeting.shareCAEventSentStatus.transmitStop, false);
|
8068
|
+
|
7701
8069
|
assert.calledWith(meeting.statsAnalyzer.updateMediaStatus, {
|
7702
8070
|
expected: {sendShare: true},
|
7703
8071
|
});
|
7704
8072
|
};
|
7705
8073
|
|
7706
8074
|
it('requests screen share floor and publishes the screen share video stream', async () => {
|
8075
|
+
meeting.shareCAEventSentStatus.transmitStart = true;
|
7707
8076
|
await meeting.publishStreams({screenShare: {video: videoShareStream}});
|
7708
8077
|
|
7709
8078
|
checkScreenShareVideoPublished(videoShareStream);
|
7710
8079
|
});
|
7711
8080
|
|
7712
8081
|
it('requests screen share floor and publishes the screen share audio stream', async () => {
|
8082
|
+
meeting.shareCAEventSentStatus.transmitStart = true;
|
7713
8083
|
await meeting.publishStreams({screenShare: {audio: audioShareStream}});
|
7714
8084
|
|
7715
8085
|
checkScreenShareAudioPublished(audioShareStream);
|
@@ -8596,13 +8966,19 @@ describe('plugin-meetings', () => {
|
|
8596
8966
|
const fakeErrorMessage = 'test error';
|
8597
8967
|
const fakeRootCauseName = 'root cause name';
|
8598
8968
|
const fakeErrorName = 'test error name';
|
8969
|
+
let clock;
|
8599
8970
|
|
8600
8971
|
beforeEach(() => {
|
8972
|
+
clock = sinon.useFakeTimers();
|
8601
8973
|
meeting.setupMediaConnectionListeners();
|
8602
8974
|
webex.internal.newMetrics.submitClientEvent.resetHistory();
|
8603
8975
|
Metrics.sendBehavioralMetric.resetHistory();
|
8604
8976
|
});
|
8605
8977
|
|
8978
|
+
afterEach(() => {
|
8979
|
+
clock.restore();
|
8980
|
+
});
|
8981
|
+
|
8606
8982
|
const checkMetricSent = (event, error) => {
|
8607
8983
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
8608
8984
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
@@ -8671,6 +9047,13 @@ describe('plugin-meetings', () => {
|
|
8671
9047
|
});
|
8672
9048
|
|
8673
9049
|
it('should send metrics for SdpAnswerHandlingError error', () => {
|
9050
|
+
meeting.sdpResponseTimer = '1234';
|
9051
|
+
meeting.deferSDPAnswer = {
|
9052
|
+
reject: sinon.stub(),
|
9053
|
+
};
|
9054
|
+
|
9055
|
+
const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
|
9056
|
+
|
8674
9057
|
const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
|
8675
9058
|
name: fakeErrorName,
|
8676
9059
|
cause: {name: fakeRootCauseName},
|
@@ -8685,6 +9068,8 @@ describe('plugin-meetings', () => {
|
|
8685
9068
|
fakeErrorMessage,
|
8686
9069
|
fakeRootCauseName
|
8687
9070
|
);
|
9071
|
+
assert.calledOnce(meeting.deferSDPAnswer.reject);
|
9072
|
+
assert.calledOnce(clearTimeoutSpy);
|
8688
9073
|
});
|
8689
9074
|
|
8690
9075
|
it('should send metrics for SdpError error', () => {
|
@@ -9590,6 +9975,42 @@ describe('plugin-meetings', () => {
|
|
9590
9975
|
);
|
9591
9976
|
});
|
9592
9977
|
|
9978
|
+
it('listens to CONTROLS_ANNOTATION_CHANGED', async () => {
|
9979
|
+
const state = {example: 'value'};
|
9980
|
+
|
9981
|
+
await meeting.locusInfo.emitScoped(
|
9982
|
+
{function: 'test', file: 'test'},
|
9983
|
+
LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED,
|
9984
|
+
{state}
|
9985
|
+
);
|
9986
|
+
|
9987
|
+
assert.calledWith(
|
9988
|
+
TriggerProxy.trigger,
|
9989
|
+
meeting,
|
9990
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
9991
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
|
9992
|
+
{state}
|
9993
|
+
);
|
9994
|
+
});
|
9995
|
+
|
9996
|
+
it('listens to CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED', async () => {
|
9997
|
+
const state = {example: 'value'};
|
9998
|
+
|
9999
|
+
await meeting.locusInfo.emitScoped(
|
10000
|
+
{function: 'test', file: 'test'},
|
10001
|
+
LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED,
|
10002
|
+
{state}
|
10003
|
+
);
|
10004
|
+
|
10005
|
+
assert.calledWith(
|
10006
|
+
TriggerProxy.trigger,
|
10007
|
+
meeting,
|
10008
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
10009
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
|
10010
|
+
{state}
|
10011
|
+
);
|
10012
|
+
});
|
10013
|
+
|
9593
10014
|
it('listens to the locus interpretation update event', () => {
|
9594
10015
|
const interpretation = {
|
9595
10016
|
siLanguages: [{languageCode: 20, languageName: 'en'}],
|
@@ -10485,9 +10906,11 @@ describe('plugin-meetings', () => {
|
|
10485
10906
|
let canUserLowerSomeoneElsesHandSpy;
|
10486
10907
|
let waitingForOthersToJoinSpy;
|
10487
10908
|
let canSendReactionsSpy;
|
10909
|
+
let requiresPostMeetingDataConsentPromptSpy;
|
10488
10910
|
let canUserRenameSelfAndObservedSpy;
|
10489
10911
|
let canUserRenameOthersSpy;
|
10490
10912
|
let canShareWhiteBoardSpy;
|
10913
|
+
let canMoveToLobbySpy;
|
10491
10914
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
10492
10915
|
|
10493
10916
|
beforeEach(() => {
|
@@ -10512,8 +10935,13 @@ describe('plugin-meetings', () => {
|
|
10512
10935
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
10513
10936
|
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
10514
10937
|
canUserRenameSelfAndObservedSpy = sinon.spy(MeetingUtil, 'canUserRenameSelfAndObserved');
|
10938
|
+
requiresPostMeetingDataConsentPromptSpy = sinon.spy(
|
10939
|
+
MeetingUtil,
|
10940
|
+
'requiresPostMeetingDataConsentPrompt'
|
10941
|
+
);
|
10515
10942
|
canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
|
10516
10943
|
canShareWhiteBoardSpy = sinon.spy(MeetingUtil, 'canShareWhiteBoard');
|
10944
|
+
canMoveToLobbySpy = sinon.spy(MeetingUtil, 'canMoveToLobby');
|
10517
10945
|
});
|
10518
10946
|
|
10519
10947
|
afterEach(() => {
|
@@ -10611,6 +11039,16 @@ describe('plugin-meetings', () => {
|
|
10611
11039
|
requiredDisplayHints: [],
|
10612
11040
|
requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
|
10613
11041
|
},
|
11042
|
+
{
|
11043
|
+
actionName: 'canRealtimeCloseCaption',
|
11044
|
+
requiredDisplayHints: [],
|
11045
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
|
11046
|
+
},
|
11047
|
+
{
|
11048
|
+
actionName: 'canRealtimeCloseCaptionManual',
|
11049
|
+
requiredDisplayHints: [],
|
11050
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
|
11051
|
+
},
|
10614
11052
|
{
|
10615
11053
|
actionName: 'canChat',
|
10616
11054
|
requiredDisplayHints: [],
|
@@ -10640,6 +11078,11 @@ describe('plugin-meetings', () => {
|
|
10640
11078
|
requiredDisplayHints: [],
|
10641
11079
|
requiredPolicies: [SELF_POLICY.SUPPORT_POLLING_AND_QA],
|
10642
11080
|
},
|
11081
|
+
{
|
11082
|
+
actionName: 'canShareWhiteBoard',
|
11083
|
+
requiredDisplayHints: [DISPLAY_HINTS.SHARE_WHITEBOARD],
|
11084
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_WHITEBOARD],
|
11085
|
+
},
|
10643
11086
|
],
|
10644
11087
|
({
|
10645
11088
|
actionName,
|
@@ -11047,8 +11490,10 @@ describe('plugin-meetings', () => {
|
|
11047
11490
|
assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
|
11048
11491
|
assert.calledWith(canSendReactionsSpy, null, userDisplayHints);
|
11049
11492
|
assert.calledWith(canUserRenameSelfAndObservedSpy, userDisplayHints);
|
11493
|
+
assert.calledWith(requiresPostMeetingDataConsentPromptSpy, userDisplayHints);
|
11050
11494
|
assert.calledWith(canUserRenameOthersSpy, userDisplayHints);
|
11051
|
-
assert.calledWith(canShareWhiteBoardSpy, userDisplayHints);
|
11495
|
+
assert.calledWith(canShareWhiteBoardSpy, userDisplayHints, selfUserPolicies);
|
11496
|
+
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
11052
11497
|
|
11053
11498
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
11054
11499
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
@@ -11142,6 +11587,22 @@ describe('plugin-meetings', () => {
|
|
11142
11587
|
requiredPolicies: [SELF_POLICY.SUPPORT_VOIP],
|
11143
11588
|
policies: selfUserPolicies,
|
11144
11589
|
});
|
11590
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
11591
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
|
11592
|
+
displayHints: userDisplayHints,
|
11593
|
+
});
|
11594
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
11595
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
|
11596
|
+
displayHints: userDisplayHints,
|
11597
|
+
});
|
11598
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
11599
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
|
11600
|
+
displayHints: userDisplayHints,
|
11601
|
+
});
|
11602
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
11603
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
|
11604
|
+
displayHints: userDisplayHints,
|
11605
|
+
});
|
11145
11606
|
|
11146
11607
|
assert.calledWith(
|
11147
11608
|
TriggerProxy.trigger,
|
@@ -11988,6 +12449,8 @@ describe('plugin-meetings', () => {
|
|
11988
12449
|
// Set the webinar attendee flag
|
11989
12450
|
meeting.webinar = {selfIsAttendee: true};
|
11990
12451
|
meeting.locusInfo.info.isWebinar = true;
|
12452
|
+
meeting.shareCAEventSentStatus.receiveStart = true;
|
12453
|
+
meeting.shareCAEventSentStatus.receiveStop = true;
|
11991
12454
|
|
11992
12455
|
// Step 1: Start sharing whiteboard A
|
11993
12456
|
const data1 = generateData(
|
@@ -12011,6 +12474,8 @@ describe('plugin-meetings', () => {
|
|
12011
12474
|
|
12012
12475
|
// Specific assertions for webinar attendee status
|
12013
12476
|
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
12477
|
+
assert.equal(meeting.shareCAEventSentStatus.receiveStart, false);
|
12478
|
+
assert.equal(meeting.shareCAEventSentStatus.receiveStop, false);
|
12014
12479
|
});
|
12015
12480
|
});
|
12016
12481
|
|
@@ -12666,6 +13131,31 @@ describe('plugin-meetings', () => {
|
|
12666
13131
|
});
|
12667
13132
|
});
|
12668
13133
|
});
|
13134
|
+
|
13135
|
+
describe('handleShareVideoStreamMuteStateChange', () => {
|
13136
|
+
it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
|
13137
|
+
meeting.isMultistream = true;
|
13138
|
+
meeting.statsAnalyzer = {shareVideoEncoderImplementation: 'OpenH264'};
|
13139
|
+
meeting.mediaProperties.shareVideoStream = {
|
13140
|
+
getSettings: sinon.stub().returns({displaySurface: 'monitor', frameRate: 30}),
|
13141
|
+
};
|
13142
|
+
|
13143
|
+
meeting.handleShareVideoStreamMuteStateChange(true);
|
13144
|
+
|
13145
|
+
assert.calledOnceWithExactly(
|
13146
|
+
Metrics.sendBehavioralMetric,
|
13147
|
+
BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE,
|
13148
|
+
{
|
13149
|
+
correlationId: meeting.correlationId,
|
13150
|
+
muted: true,
|
13151
|
+
encoderImplementation: 'OpenH264',
|
13152
|
+
displaySurface: 'monitor',
|
13153
|
+
isMultistream: true,
|
13154
|
+
frameRate: 30,
|
13155
|
+
}
|
13156
|
+
);
|
13157
|
+
});
|
13158
|
+
});
|
12669
13159
|
});
|
12670
13160
|
|
12671
13161
|
describe('#startKeepAlive', () => {
|
@@ -12833,6 +13323,38 @@ describe('plugin-meetings', () => {
|
|
12833
13323
|
});
|
12834
13324
|
});
|
12835
13325
|
|
13326
|
+
describe('#setPostMeetingDataConsent', () => {
|
13327
|
+
it('should have #setPostMeetingDataConsent', () => {
|
13328
|
+
assert.exists(meeting.setPostMeetingDataConsent);
|
13329
|
+
});
|
13330
|
+
|
13331
|
+
beforeEach(() => {
|
13332
|
+
meeting.meetingRequest.setPostMeetingDataConsent = sinon
|
13333
|
+
.stub()
|
13334
|
+
.returns(Promise.resolve());
|
13335
|
+
});
|
13336
|
+
|
13337
|
+
[true, false].forEach((accept) => {
|
13338
|
+
it(`should send consent with ${accept}`, async () => {
|
13339
|
+
const id = uuidv4();
|
13340
|
+
meeting.locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${accept}`;
|
13341
|
+
meeting.deviceUrl = `https://wdm-test.wbx2.com/wdm/api/v1/devices/${accept}`;
|
13342
|
+
meeting.members.selfId = id;
|
13343
|
+
|
13344
|
+
const consentPromise = meeting.setPostMeetingDataConsent(accept);
|
13345
|
+
|
13346
|
+
assert.exists(consentPromise.then);
|
13347
|
+
await consentPromise;
|
13348
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.setPostMeetingDataConsent, {
|
13349
|
+
locusUrl: `https://locus-test.wbx2.com/locus/api/v1/loci/${accept}`,
|
13350
|
+
postMeetingDataConsent: accept,
|
13351
|
+
selfId: id,
|
13352
|
+
deviceUrl: `https://wdm-test.wbx2.com/wdm/api/v1/devices/${accept}`,
|
13353
|
+
});
|
13354
|
+
});
|
13355
|
+
});
|
13356
|
+
});
|
13357
|
+
|
12836
13358
|
describe('#sendReaction', () => {
|
12837
13359
|
it('should have #sendReaction', () => {
|
12838
13360
|
assert.exists(meeting.sendReaction);
|