@webex/plugin-meetings 3.9.0-webinar5k.1 → 3.10.0
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 +1 -1
- package/dist/constants.js +24 -0
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +22 -5
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/locusRouteToken.js +116 -0
- package/dist/interceptors/locusRouteToken.js.map +1 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +11 -2
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +76 -322
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/parser.js +4 -1
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/properties.js +53 -5
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +14 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +467 -277
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +177 -14
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/type.js +7 -0
- package/dist/meeting/type.js.map +1 -0
- package/dist/meeting/util.js +100 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -21
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +20 -16
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/members/index.js +10 -7
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.js +7 -2
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +34 -5
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +42 -2
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/reachability/index.js +3 -3
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +23 -0
- package/dist/types/controls-options-manager/index.d.ts +9 -1
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
- package/dist/types/locus-info/index.d.ts +9 -54
- package/dist/types/media/properties.d.ts +21 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +14 -0
- package/dist/types/meeting/index.d.ts +64 -29
- package/dist/types/meeting/request.d.ts +42 -0
- package/dist/types/meeting/type.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +13 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/members/index.d.ts +12 -11
- package/dist/types/members/util.d.ts +8 -4
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/remoteMedia.d.ts +20 -1
- package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +25 -27
- package/src/constants.ts +26 -2
- package/src/controls-options-manager/index.ts +26 -5
- package/src/index.ts +2 -1
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/locusRouteToken.ts +80 -0
- package/src/locus-info/controlsUtils.ts +18 -0
- package/src/locus-info/index.ts +69 -357
- package/src/locus-info/parser.ts +5 -1
- package/src/media/properties.ts +43 -0
- package/src/meeting/in-meeting-actions.ts +29 -0
- package/src/meeting/index.ts +296 -87
- package/src/meeting/request.ts +141 -0
- package/src/meeting/type.ts +9 -0
- package/src/meeting/util.ts +107 -3
- package/src/meeting-info/meeting-info-v2.ts +24 -5
- package/src/meetings/index.ts +15 -22
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +14 -0
- package/src/members/index.ts +20 -10
- package/src/members/util.ts +20 -3
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +7 -7
- package/src/multistream/remoteMedia.ts +34 -4
- package/src/multistream/remoteMediaGroup.ts +37 -2
- package/src/reachability/index.ts +3 -3
- package/test/unit/spec/common/browser-detection.js +0 -24
- package/test/unit/spec/controls-options-manager/index.js +47 -0
- package/test/unit/spec/fixture/locus.js +1 -0
- package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
- package/test/unit/spec/locus-info/index.js +80 -361
- package/test/unit/spec/locus-info/parser.js +3 -2
- package/test/unit/spec/media/properties.ts +137 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +14 -0
- package/test/unit/spec/meeting/index.js +637 -53
- package/test/unit/spec/meeting/muteState.js +32 -6
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +171 -18
- package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
- package/test/unit/spec/meetings/index.js +12 -5
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/members/collection.js +120 -0
- package/test/unit/spec/members/index.js +107 -2
- package/test/unit/spec/members/request.js +55 -0
- package/test/unit/spec/members/utils.js +116 -14
- package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
- package/test/unit/spec/multistream/remoteMedia.ts +66 -2
- package/test/unit/spec/reachability/index.ts +158 -3
- package/test/unit/spec/roap/turnDiscovery.ts +3 -3
- package/dist/hashTree/constants.js +0 -23
- package/dist/hashTree/constants.js.map +0 -1
- package/dist/hashTree/hashTree.js +0 -516
- package/dist/hashTree/hashTree.js.map +0 -1
- package/dist/hashTree/hashTreeParser.js +0 -521
- package/dist/hashTree/hashTreeParser.js.map +0 -1
- package/dist/types/hashTree/constants.d.ts +0 -8
- package/dist/types/hashTree/hashTree.d.ts +0 -128
- package/dist/types/hashTree/hashTreeParser.d.ts +0 -152
- package/src/hashTree/constants.ts +0 -12
- package/src/hashTree/hashTree.ts +0 -460
- package/src/hashTree/hashTreeParser.ts +0 -556
- package/test/unit/spec/hashTree/hashTree.ts +0 -394
- package/test/unit/spec/hashTree/hashTreeParser.ts +0 -156
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
ConnectionState,
|
|
40
40
|
MediaConnectionEventNames,
|
|
41
41
|
StatsAnalyzerEventNames,
|
|
42
|
+
StatsMonitorEventNames,
|
|
42
43
|
Errors,
|
|
43
44
|
ErrorType,
|
|
44
45
|
RemoteTrackType,
|
|
@@ -56,6 +57,7 @@ import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/reques
|
|
|
56
57
|
import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
57
58
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
|
58
59
|
import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
|
|
60
|
+
import MembersUtil from '@webex/plugin-meetings/src/members/util';
|
|
59
61
|
import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
|
|
60
62
|
import Media from '@webex/plugin-meetings/src/media/index';
|
|
61
63
|
import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
|
|
@@ -244,6 +246,7 @@ describe('plugin-meetings', () => {
|
|
|
244
246
|
});
|
|
245
247
|
|
|
246
248
|
webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
|
|
249
|
+
webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
|
|
247
250
|
webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
|
|
248
251
|
webex.internal.services = {get: sinon.stub().returns('locus-url')};
|
|
249
252
|
webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
|
|
@@ -368,6 +371,35 @@ describe('plugin-meetings', () => {
|
|
|
368
371
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
369
372
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
370
373
|
});
|
|
374
|
+
|
|
375
|
+
it('should call the callback with the meeting that has id already set', () => {
|
|
376
|
+
let meetingIdFromCallback;
|
|
377
|
+
// check that the meeting id is already set correctly at the time when the callback is called
|
|
378
|
+
const meetingCreationCallback = sinon.stub().callsFake((meeting) => {
|
|
379
|
+
meetingIdFromCallback = meeting.id;
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
meeting = new Meeting(
|
|
383
|
+
{
|
|
384
|
+
userId: uuid1,
|
|
385
|
+
resource: uuid2,
|
|
386
|
+
deviceUrl: uuid3,
|
|
387
|
+
locus: {url: url1},
|
|
388
|
+
destination: testDestination,
|
|
389
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
390
|
+
correlationId,
|
|
391
|
+
selfId: uuid1,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
parent: webex,
|
|
395
|
+
},
|
|
396
|
+
meetingCreationCallback
|
|
397
|
+
);
|
|
398
|
+
assert.exists(meeting.id);
|
|
399
|
+
assert.calledOnceWithExactly(meetingCreationCallback, meeting);
|
|
400
|
+
assert.equal(meeting.id, meetingIdFromCallback);
|
|
401
|
+
});
|
|
402
|
+
|
|
371
403
|
it('creates MediaRequestManager instances', () => {
|
|
372
404
|
assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
|
|
373
405
|
assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
|
|
@@ -454,6 +486,18 @@ describe('plugin-meetings', () => {
|
|
|
454
486
|
});
|
|
455
487
|
});
|
|
456
488
|
|
|
489
|
+
it('pstnCorrelationId getter/setter should work correctly', () => {
|
|
490
|
+
const testPstnCorrelationId = uuid.v4();
|
|
491
|
+
|
|
492
|
+
meeting.pstnCorrelationId = testPstnCorrelationId;
|
|
493
|
+
assert.equal(meeting.pstnCorrelationId, testPstnCorrelationId);
|
|
494
|
+
assert.equal(meeting.callStateForMetrics.pstnCorrelationId, testPstnCorrelationId);
|
|
495
|
+
|
|
496
|
+
meeting.pstnCorrelationId = undefined;
|
|
497
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
498
|
+
assert.equal(meeting.callStateForMetrics.pstnCorrelationId, undefined);
|
|
499
|
+
});
|
|
500
|
+
|
|
457
501
|
describe('creates ReceiveSlot manager instance', () => {
|
|
458
502
|
let mockReceiveSlotManagerCtor;
|
|
459
503
|
let providedCreateSlotCallback;
|
|
@@ -581,7 +625,6 @@ describe('plugin-meetings', () => {
|
|
|
581
625
|
assert.isFalse(meeting.isLocusCall());
|
|
582
626
|
});
|
|
583
627
|
});
|
|
584
|
-
|
|
585
628
|
describe('#invite', () => {
|
|
586
629
|
it('should have #invite', () => {
|
|
587
630
|
assert.exists(meeting.invite);
|
|
@@ -592,8 +635,6 @@ describe('plugin-meetings', () => {
|
|
|
592
635
|
it('should proxy members #addMember and return a promise', async () => {
|
|
593
636
|
const invite = meeting.invite(uuid1, false);
|
|
594
637
|
|
|
595
|
-
assert.exists(invite.then);
|
|
596
|
-
await invite;
|
|
597
638
|
assert.calledOnce(meeting.members.addMember);
|
|
598
639
|
assert.calledWith(meeting.members.addMember, uuid1, false);
|
|
599
640
|
});
|
|
@@ -1949,21 +1990,25 @@ describe('plugin-meetings', () => {
|
|
|
1949
1990
|
});
|
|
1950
1991
|
});
|
|
1951
1992
|
|
|
1952
|
-
it('should
|
|
1993
|
+
it('should handle join failure', async () => {
|
|
1953
1994
|
MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
|
|
1995
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
|
1996
|
+
|
|
1954
1997
|
await meeting.join().catch(() => {
|
|
1955
|
-
assert.
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
);
|
|
1959
|
-
assert.
|
|
1960
|
-
webex.internal.newMetrics.submitClientEvent
|
|
1998
|
+
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
1999
|
+
|
|
2000
|
+
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2001
|
+
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2002
|
+
assert.calledWithMatch(
|
|
2003
|
+
webex.internal.newMetrics.submitClientEvent,
|
|
1961
2004
|
{
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2005
|
+
name: 'client.call.initiated',
|
|
2006
|
+
payload: {
|
|
2007
|
+
trigger: 'user-interaction',
|
|
2008
|
+
isRoapCallEnabled: true,
|
|
2009
|
+
pstnAudioType: undefined
|
|
2010
|
+
},
|
|
2011
|
+
options: {meetingId: meeting.id},
|
|
1967
2012
|
}
|
|
1968
2013
|
);
|
|
1969
2014
|
});
|
|
@@ -2172,6 +2217,7 @@ describe('plugin-meetings', () => {
|
|
|
2172
2217
|
});
|
|
2173
2218
|
meeting.audio = muteStateStub;
|
|
2174
2219
|
meeting.video = muteStateStub;
|
|
2220
|
+
sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.ipv4_and_ipv6);
|
|
2175
2221
|
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
2176
2222
|
sinon.stub(meeting, 'setupMediaConnectionListeners');
|
|
2177
2223
|
sinon.stub(meeting, 'setMercuryListener');
|
|
@@ -2243,13 +2289,24 @@ describe('plugin-meetings', () => {
|
|
|
2243
2289
|
close: sinon.stub(),
|
|
2244
2290
|
forceRtcMetricsSend,
|
|
2245
2291
|
});
|
|
2246
|
-
|
|
2292
|
+
|
|
2293
|
+
const mockStatsMonitor = {removeAllListeners: sinon.stub()};
|
|
2294
|
+
const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
|
|
2295
|
+
|
|
2296
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2247
2297
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2298
|
+
meeting.statsMonitor = mockStatsMonitor;
|
|
2299
|
+
meeting.networkQualityMonitor = mockNetworkQualityMonitor;
|
|
2248
2300
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2249
2301
|
|
|
2250
2302
|
assert.calledOnce(forceRtcMetricsSend);
|
|
2303
|
+
assert.calledOnce(mockStatsMonitor.removeAllListeners);
|
|
2304
|
+
assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
|
|
2251
2305
|
|
|
2252
2306
|
assert.isNull(meeting.statsAnalyzer);
|
|
2307
|
+
assert.isNull(meeting.statsMonitor);
|
|
2308
|
+
assert.isNull(meeting.networkQualityMonitor);
|
|
2309
|
+
|
|
2253
2310
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
2254
2311
|
assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
|
|
2255
2312
|
name: 'internal.client.add-media.turn-discovery.start',
|
|
@@ -2293,6 +2350,7 @@ describe('plugin-meetings', () => {
|
|
|
2293
2350
|
selected_subnet: null,
|
|
2294
2351
|
numTransports: 1,
|
|
2295
2352
|
iceCandidatesCount: 0,
|
|
2353
|
+
ipver: 1,
|
|
2296
2354
|
}
|
|
2297
2355
|
);
|
|
2298
2356
|
});
|
|
@@ -2340,6 +2398,7 @@ describe('plugin-meetings', () => {
|
|
|
2340
2398
|
subnet_reachable: null,
|
|
2341
2399
|
selected_cluster: null,
|
|
2342
2400
|
selected_subnet: null,
|
|
2401
|
+
ipver: 1,
|
|
2343
2402
|
})
|
|
2344
2403
|
);
|
|
2345
2404
|
|
|
@@ -2359,12 +2418,23 @@ describe('plugin-meetings', () => {
|
|
|
2359
2418
|
|
|
2360
2419
|
meeting.waitForRemoteSDPAnswer = sinon.stub().rejects();
|
|
2361
2420
|
|
|
2362
|
-
|
|
2421
|
+
const mockStatsMonitor = {removeAllListeners: sinon.stub()};
|
|
2422
|
+
const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
|
|
2423
|
+
|
|
2424
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2363
2425
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2426
|
+
meeting.statsMonitor = mockStatsMonitor;
|
|
2427
|
+
meeting.networkQualityMonitor = mockNetworkQualityMonitor;
|
|
2364
2428
|
|
|
2365
2429
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2366
2430
|
|
|
2367
2431
|
assert.isNull(meeting.statsAnalyzer);
|
|
2432
|
+
assert.isNull(meeting.statsMonitor);
|
|
2433
|
+
assert.isNull(meeting.networkQualityMonitor);
|
|
2434
|
+
|
|
2435
|
+
assert.calledOnce(mockStatsMonitor.removeAllListeners);
|
|
2436
|
+
assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
|
|
2437
|
+
|
|
2368
2438
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
2369
2439
|
assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
|
|
2370
2440
|
name: 'internal.client.add-media.turn-discovery.start',
|
|
@@ -2408,6 +2478,7 @@ describe('plugin-meetings', () => {
|
|
|
2408
2478
|
subnet_reachable: null,
|
|
2409
2479
|
selected_cluster: null,
|
|
2410
2480
|
selected_subnet: null,
|
|
2481
|
+
ipver: 1,
|
|
2411
2482
|
}
|
|
2412
2483
|
);
|
|
2413
2484
|
});
|
|
@@ -2428,8 +2499,9 @@ describe('plugin-meetings', () => {
|
|
|
2428
2499
|
},
|
|
2429
2500
|
},
|
|
2430
2501
|
});
|
|
2431
|
-
// set a statsAnalyzer on the meeting so that we can check that
|
|
2502
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2432
2503
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2504
|
+
meeting.statsMonitor = {removeAllListeners: sinon.stub()};
|
|
2433
2505
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2434
2506
|
|
|
2435
2507
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
@@ -2468,10 +2540,12 @@ describe('plugin-meetings', () => {
|
|
|
2468
2540
|
subnet_reachable: null,
|
|
2469
2541
|
selected_cluster: null,
|
|
2470
2542
|
selected_subnet: null,
|
|
2543
|
+
ipver: 1,
|
|
2471
2544
|
})
|
|
2472
2545
|
);
|
|
2473
2546
|
|
|
2474
2547
|
assert.isNull(meeting.statsAnalyzer);
|
|
2548
|
+
assert.isNull(meeting.statsMonitor);
|
|
2475
2549
|
});
|
|
2476
2550
|
|
|
2477
2551
|
it('should include the peer connection properties correctly for transcoded', async () => {
|
|
@@ -2488,8 +2562,14 @@ describe('plugin-meetings', () => {
|
|
|
2488
2562
|
},
|
|
2489
2563
|
},
|
|
2490
2564
|
});
|
|
2491
|
-
|
|
2565
|
+
|
|
2566
|
+
const mockStatsMonitor = {removeAllListeners: sinon.stub()};
|
|
2567
|
+
const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
|
|
2568
|
+
|
|
2569
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2492
2570
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2571
|
+
meeting.statsMonitor = mockStatsMonitor;
|
|
2572
|
+
meeting.networkQualityMonitor = mockNetworkQualityMonitor;
|
|
2493
2573
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2494
2574
|
|
|
2495
2575
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
@@ -2528,10 +2608,15 @@ describe('plugin-meetings', () => {
|
|
|
2528
2608
|
subnet_reachable: null,
|
|
2529
2609
|
selected_cluster: null,
|
|
2530
2610
|
selected_subnet: null,
|
|
2611
|
+
ipver: 1,
|
|
2531
2612
|
})
|
|
2532
2613
|
);
|
|
2533
2614
|
|
|
2534
2615
|
assert.isNull(meeting.statsAnalyzer);
|
|
2616
|
+
assert.isNull(meeting.statsMonitor);
|
|
2617
|
+
assert.isNull(meeting.networkQualityMonitor);
|
|
2618
|
+
assert.calledOnce(mockStatsMonitor.removeAllListeners);
|
|
2619
|
+
assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
|
|
2535
2620
|
});
|
|
2536
2621
|
|
|
2537
2622
|
it('should work the second time addMedia is called in case the first time fails', async () => {
|
|
@@ -3052,6 +3137,7 @@ describe('plugin-meetings', () => {
|
|
|
3052
3137
|
subnet_reachable: null,
|
|
3053
3138
|
selected_cluster: null,
|
|
3054
3139
|
selected_subnet: null,
|
|
3140
|
+
ipver: 1,
|
|
3055
3141
|
},
|
|
3056
3142
|
]);
|
|
3057
3143
|
|
|
@@ -3253,6 +3339,7 @@ describe('plugin-meetings', () => {
|
|
|
3253
3339
|
connectionType: 'udp',
|
|
3254
3340
|
selectedCandidatePairChanges: 2,
|
|
3255
3341
|
ipVersion: 'IPv6',
|
|
3342
|
+
ipver: 1,
|
|
3256
3343
|
numTransports: 1,
|
|
3257
3344
|
isMultistream: false,
|
|
3258
3345
|
retriedWithTurnServer: true,
|
|
@@ -3399,6 +3486,7 @@ describe('plugin-meetings', () => {
|
|
|
3399
3486
|
meeting.iceCandidatesCount = 3;
|
|
3400
3487
|
meeting.iceCandidateErrors.set('701_error', 3);
|
|
3401
3488
|
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
3489
|
+
MeetingUtil.getIpVersion.returns(IP_VERSION.only_ipv6);
|
|
3402
3490
|
|
|
3403
3491
|
await meeting.addMedia({
|
|
3404
3492
|
mediaSettings: {},
|
|
@@ -3414,6 +3502,7 @@ describe('plugin-meetings', () => {
|
|
|
3414
3502
|
connectionType: 'udp',
|
|
3415
3503
|
selectedCandidatePairChanges: 2,
|
|
3416
3504
|
ipVersion: 'IPv6',
|
|
3505
|
+
ipver: 6,
|
|
3417
3506
|
numTransports: 1,
|
|
3418
3507
|
isMultistream: false,
|
|
3419
3508
|
retriedWithTurnServer: false,
|
|
@@ -3492,6 +3581,7 @@ describe('plugin-meetings', () => {
|
|
|
3492
3581
|
selected_cluster: null,
|
|
3493
3582
|
selected_subnet: null,
|
|
3494
3583
|
iceCandidatesCount: 0,
|
|
3584
|
+
ipver: 1,
|
|
3495
3585
|
}
|
|
3496
3586
|
);
|
|
3497
3587
|
|
|
@@ -3556,6 +3646,7 @@ describe('plugin-meetings', () => {
|
|
|
3556
3646
|
selected_cluster: null,
|
|
3557
3647
|
selected_subnet: null,
|
|
3558
3648
|
iceCandidatesCount: 0,
|
|
3649
|
+
ipver: 1,
|
|
3559
3650
|
}
|
|
3560
3651
|
);
|
|
3561
3652
|
|
|
@@ -3602,6 +3693,7 @@ describe('plugin-meetings', () => {
|
|
|
3602
3693
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3603
3694
|
connectionType: 'udp',
|
|
3604
3695
|
ipVersion: 'IPv6',
|
|
3696
|
+
ipver: 1,
|
|
3605
3697
|
selectedCandidatePairChanges: 2,
|
|
3606
3698
|
numTransports: 1,
|
|
3607
3699
|
isMultistream: false,
|
|
@@ -3682,6 +3774,7 @@ describe('plugin-meetings', () => {
|
|
|
3682
3774
|
selected_cluster: 'some.cluster',
|
|
3683
3775
|
selected_subnet: '1.X.X.X',
|
|
3684
3776
|
iceCandidatesCount: 0,
|
|
3777
|
+
ipver: 1,
|
|
3685
3778
|
}
|
|
3686
3779
|
);
|
|
3687
3780
|
|
|
@@ -3987,13 +4080,14 @@ describe('plugin-meetings', () => {
|
|
|
3987
4080
|
});
|
|
3988
4081
|
});
|
|
3989
4082
|
|
|
3990
|
-
it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
|
|
4083
|
+
it('counts the number of members that are in the meeting or lobby for MEDIA_QUALITY event', async () => {
|
|
3991
4084
|
let fakeMembersCollection = {
|
|
3992
4085
|
members: {
|
|
3993
|
-
member1: {isInMeeting: true},
|
|
3994
|
-
member2: {isInMeeting: true},
|
|
3995
|
-
member3: {isInMeeting: false},
|
|
3996
|
-
|
|
4086
|
+
member1: {isInMeeting: true, isInLobby: false},
|
|
4087
|
+
member2: {isInMeeting: false, isInLobby: true},
|
|
4088
|
+
member3: {isInMeeting: false, isInLobby: false},
|
|
4089
|
+
member4: {isInMeeting: true, isInLobby: false},
|
|
4090
|
+
}
|
|
3997
4091
|
};
|
|
3998
4092
|
sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
|
|
3999
4093
|
const fakeData = {intervalMetadata: {}};
|
|
@@ -4011,11 +4105,12 @@ describe('plugin-meetings', () => {
|
|
|
4011
4105
|
},
|
|
4012
4106
|
payload: {
|
|
4013
4107
|
intervals: [
|
|
4014
|
-
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount',
|
|
4108
|
+
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 3)),
|
|
4015
4109
|
],
|
|
4016
4110
|
},
|
|
4017
4111
|
});
|
|
4018
|
-
|
|
4112
|
+
// Move member2 from lobby to neither in meeting nor lobby
|
|
4113
|
+
fakeMembersCollection.members.member2.isInLobby = false;
|
|
4019
4114
|
|
|
4020
4115
|
statsAnalyzerStub.emit(
|
|
4021
4116
|
{file: 'test', function: 'test'},
|
|
@@ -4030,7 +4125,7 @@ describe('plugin-meetings', () => {
|
|
|
4030
4125
|
},
|
|
4031
4126
|
payload: {
|
|
4032
4127
|
intervals: [
|
|
4033
|
-
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount',
|
|
4128
|
+
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
|
|
4034
4129
|
],
|
|
4035
4130
|
},
|
|
4036
4131
|
});
|
|
@@ -4057,6 +4152,132 @@ describe('plugin-meetings', () => {
|
|
|
4057
4152
|
});
|
|
4058
4153
|
});
|
|
4059
4154
|
|
|
4155
|
+
describe('handles StatsMonitor events', () => {
|
|
4156
|
+
let statsMonitorStub;
|
|
4157
|
+
let prevConfigValue;
|
|
4158
|
+
let listeners;
|
|
4159
|
+
|
|
4160
|
+
beforeEach(async () => {
|
|
4161
|
+
meeting.meetingState = 'ACTIVE';
|
|
4162
|
+
prevConfigValue = meeting.config.stats.enableStatsAnalyzer;
|
|
4163
|
+
|
|
4164
|
+
meeting.config.stats.enableStatsAnalyzer = true;
|
|
4165
|
+
|
|
4166
|
+
listeners = {};
|
|
4167
|
+
|
|
4168
|
+
statsMonitorStub = {
|
|
4169
|
+
on: sinon.stub().callsFake((event, callback) => {
|
|
4170
|
+
listeners[event] = callback;
|
|
4171
|
+
}),
|
|
4172
|
+
removeAllListeners: sinon.stub(),
|
|
4173
|
+
};
|
|
4174
|
+
|
|
4175
|
+
sinon.stub(meeting.mediaProperties, 'sendMediaIssueMetric');
|
|
4176
|
+
|
|
4177
|
+
// mock the StatsMonitor constructor
|
|
4178
|
+
sinon.stub(InternalMediaCoreModule, 'StatsMonitor').returns(statsMonitorStub);
|
|
4179
|
+
|
|
4180
|
+
await meeting.addMedia({
|
|
4181
|
+
mediaSettings: {},
|
|
4182
|
+
});
|
|
4183
|
+
});
|
|
4184
|
+
|
|
4185
|
+
afterEach(() => {
|
|
4186
|
+
meeting.config.stats.enableStatsAnalyzer = prevConfigValue;
|
|
4187
|
+
sinon.restore();
|
|
4188
|
+
});
|
|
4189
|
+
|
|
4190
|
+
describe('INBOUND_AUDIO_ISSUE event', () => {
|
|
4191
|
+
it('should not trigger event when no unmuted members exist', () => {
|
|
4192
|
+
const fakeEventData = {issueSubType: 'DECODE_RESULTS_IN_ZERO_AUDIO_LEVEL'};
|
|
4193
|
+
|
|
4194
|
+
// Setup members that are either self or muted
|
|
4195
|
+
const mutedMember = {
|
|
4196
|
+
isSelf: false,
|
|
4197
|
+
isPairedWithSelf: false,
|
|
4198
|
+
isAudioMuted: true,
|
|
4199
|
+
};
|
|
4200
|
+
const selfMember = {
|
|
4201
|
+
isSelf: true,
|
|
4202
|
+
isPairedWithSelf: false,
|
|
4203
|
+
isAudioMuted: false,
|
|
4204
|
+
};
|
|
4205
|
+
const pairedMember = {
|
|
4206
|
+
isSelf: false,
|
|
4207
|
+
isPairedWithSelf: true,
|
|
4208
|
+
isAudioMuted: false,
|
|
4209
|
+
};
|
|
4210
|
+
meeting.members.membersCollection.getAll = sinon.stub().returns({
|
|
4211
|
+
member1: mutedMember,
|
|
4212
|
+
member2: selfMember,
|
|
4213
|
+
member3: pairedMember,
|
|
4214
|
+
});
|
|
4215
|
+
|
|
4216
|
+
// Reset the stub to clear any previous calls
|
|
4217
|
+
TriggerProxy.trigger.resetHistory();
|
|
4218
|
+
|
|
4219
|
+
// Emit the event from statsMonitor
|
|
4220
|
+
listeners[StatsMonitorEventNames.INBOUND_AUDIO_ISSUE](fakeEventData);
|
|
4221
|
+
|
|
4222
|
+
assert.neverCalledWith(
|
|
4223
|
+
TriggerProxy.trigger,
|
|
4224
|
+
meeting,
|
|
4225
|
+
sinon.match.object,
|
|
4226
|
+
EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
|
|
4227
|
+
fakeEventData
|
|
4228
|
+
);
|
|
4229
|
+
assert.notCalled(meeting.mediaProperties.sendMediaIssueMetric);
|
|
4230
|
+
});
|
|
4231
|
+
|
|
4232
|
+
it('should trigger event and metric when there are multiple members and at least one is unmuted', () => {
|
|
4233
|
+
const fakeEventData = {issueSubType: 'DECODE_RESULTS_IN_ZERO_AUDIO_LEVEL'};
|
|
4234
|
+
|
|
4235
|
+
// Setup mixed members - some muted, one unmuted
|
|
4236
|
+
const mutedMember = {
|
|
4237
|
+
isSelf: false,
|
|
4238
|
+
isPairedWithSelf: false,
|
|
4239
|
+
isAudioMuted: true,
|
|
4240
|
+
};
|
|
4241
|
+
const unmutedMember = {
|
|
4242
|
+
isSelf: false,
|
|
4243
|
+
isPairedWithSelf: false,
|
|
4244
|
+
isAudioMuted: false,
|
|
4245
|
+
};
|
|
4246
|
+
const selfMember = {
|
|
4247
|
+
isSelf: true,
|
|
4248
|
+
isPairedWithSelf: false,
|
|
4249
|
+
isAudioMuted: false,
|
|
4250
|
+
};
|
|
4251
|
+
meeting.members.membersCollection.getAll = sinon.stub().returns({
|
|
4252
|
+
member1: mutedMember,
|
|
4253
|
+
member2: unmutedMember,
|
|
4254
|
+
member3: selfMember,
|
|
4255
|
+
});
|
|
4256
|
+
|
|
4257
|
+
// Reset the stub to clear any previous calls
|
|
4258
|
+
TriggerProxy.trigger.resetHistory();
|
|
4259
|
+
|
|
4260
|
+
// Emit the event from statsMonitor
|
|
4261
|
+
listeners[StatsMonitorEventNames.INBOUND_AUDIO_ISSUE](fakeEventData);
|
|
4262
|
+
|
|
4263
|
+
assert.calledWith(
|
|
4264
|
+
TriggerProxy.trigger,
|
|
4265
|
+
meeting,
|
|
4266
|
+
sinon.match.object,
|
|
4267
|
+
EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
|
|
4268
|
+
fakeEventData
|
|
4269
|
+
);
|
|
4270
|
+
|
|
4271
|
+
assert.calledOnceWithExactly(
|
|
4272
|
+
meeting.mediaProperties.sendMediaIssueMetric,
|
|
4273
|
+
'inbound_audio',
|
|
4274
|
+
fakeEventData.issueSubType,
|
|
4275
|
+
meeting.correlationId
|
|
4276
|
+
);
|
|
4277
|
+
});
|
|
4278
|
+
});
|
|
4279
|
+
});
|
|
4280
|
+
|
|
4060
4281
|
describe('bundlePolicy', () => {
|
|
4061
4282
|
const FAKE_TURN_URL = 'turns:webex.com:3478';
|
|
4062
4283
|
const FAKE_TURN_USER = 'some-turn-username';
|
|
@@ -5523,6 +5744,7 @@ describe('plugin-meetings', () => {
|
|
|
5523
5744
|
let multistreamEventListeners;
|
|
5524
5745
|
let transcodedEventListeners;
|
|
5525
5746
|
let mockStatsAnalyzerCtor;
|
|
5747
|
+
let statsMonitorStub;
|
|
5526
5748
|
|
|
5527
5749
|
const setupFakeRoapMediaConnection = (fakeRoapMediaConnection, eventListeners) => {
|
|
5528
5750
|
fakeRoapMediaConnection.on.callsFake((eventName, cb) => {
|
|
@@ -5554,6 +5776,14 @@ describe('plugin-meetings', () => {
|
|
|
5554
5776
|
return {on: sinon.stub(), stopAnalyzer: sinon.stub()};
|
|
5555
5777
|
});
|
|
5556
5778
|
|
|
5779
|
+
statsMonitorStub = {
|
|
5780
|
+
on: sinon.stub(),
|
|
5781
|
+
removeAllListeners: sinon.stub(),
|
|
5782
|
+
};
|
|
5783
|
+
|
|
5784
|
+
// mock the StatsMonitor constructor
|
|
5785
|
+
sinon.stub(InternalMediaCoreModule, 'StatsMonitor').returns(statsMonitorStub);
|
|
5786
|
+
|
|
5557
5787
|
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
5558
5788
|
sinon.stub();
|
|
5559
5789
|
|
|
@@ -5616,6 +5846,7 @@ describe('plugin-meetings', () => {
|
|
|
5616
5846
|
mockStatsAnalyzerCtor,
|
|
5617
5847
|
sinon.match({
|
|
5618
5848
|
isMultistream: true,
|
|
5849
|
+
statsMonitor: statsMonitorStub,
|
|
5619
5850
|
})
|
|
5620
5851
|
);
|
|
5621
5852
|
const initialStatsAnalyzer = mockStatsAnalyzerCtor.returnValues[0];
|
|
@@ -6498,25 +6729,36 @@ describe('plugin-meetings', () => {
|
|
|
6498
6729
|
const DIAL_IN_URL = meeting.dialInUrl;
|
|
6499
6730
|
|
|
6500
6731
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
|
6501
|
-
correlationId: meeting.
|
|
6732
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6502
6733
|
dialInUrl: DIAL_IN_URL,
|
|
6503
6734
|
locusUrl: meeting.locusUrl,
|
|
6504
6735
|
clientUrl: meeting.deviceUrl,
|
|
6505
6736
|
});
|
|
6506
6737
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
6507
6738
|
|
|
6739
|
+
// Verify pstnCorrelationId was set
|
|
6740
|
+
assert.exists(meeting.pstnCorrelationId);
|
|
6741
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
|
6742
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId
|
|
6743
|
+
|
|
6508
6744
|
meeting.meetingRequest.dialIn.resetHistory();
|
|
6509
6745
|
|
|
6510
6746
|
// try again. the dial in urls should match
|
|
6511
6747
|
await meeting.usePhoneAudio();
|
|
6512
6748
|
|
|
6513
6749
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
|
6514
|
-
correlationId: meeting.
|
|
6750
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6515
6751
|
dialInUrl: DIAL_IN_URL,
|
|
6516
6752
|
locusUrl: meeting.locusUrl,
|
|
6517
6753
|
clientUrl: meeting.deviceUrl,
|
|
6518
6754
|
});
|
|
6519
6755
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
6756
|
+
// A new PSTN correlationId should be generated for the second attempt
|
|
6757
|
+
assert.notEqual(
|
|
6758
|
+
meeting.pstnCorrelationId,
|
|
6759
|
+
firstPstnCorrelationId,
|
|
6760
|
+
'pstnCorrelationId should be regenerated on each dial-in attempt'
|
|
6761
|
+
);
|
|
6520
6762
|
});
|
|
6521
6763
|
|
|
6522
6764
|
it('given a phone number, triggers dial-out, delegating request to meetingRequest correctly', async () => {
|
|
@@ -6526,7 +6768,7 @@ describe('plugin-meetings', () => {
|
|
|
6526
6768
|
const DIAL_OUT_URL = meeting.dialOutUrl;
|
|
6527
6769
|
|
|
6528
6770
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
|
6529
|
-
correlationId: meeting.
|
|
6771
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6530
6772
|
dialOutUrl: DIAL_OUT_URL,
|
|
6531
6773
|
locusUrl: meeting.locusUrl,
|
|
6532
6774
|
clientUrl: meeting.deviceUrl,
|
|
@@ -6534,49 +6776,126 @@ describe('plugin-meetings', () => {
|
|
|
6534
6776
|
});
|
|
6535
6777
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
6536
6778
|
|
|
6779
|
+
// Verify pstnCorrelationId was set
|
|
6780
|
+
assert.exists(meeting.pstnCorrelationId);
|
|
6781
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
|
6782
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId;
|
|
6783
|
+
|
|
6537
6784
|
meeting.meetingRequest.dialOut.resetHistory();
|
|
6538
6785
|
|
|
6539
6786
|
// try again. the dial out urls should match
|
|
6540
6787
|
await meeting.usePhoneAudio(phoneNumber);
|
|
6541
6788
|
|
|
6542
6789
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
|
6543
|
-
correlationId: meeting.
|
|
6790
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6544
6791
|
dialOutUrl: DIAL_OUT_URL,
|
|
6545
6792
|
locusUrl: meeting.locusUrl,
|
|
6546
6793
|
clientUrl: meeting.deviceUrl,
|
|
6547
6794
|
phoneNumber,
|
|
6548
6795
|
});
|
|
6549
6796
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
6797
|
+
// A new PSTN correlationId should be generated for the second attempt
|
|
6798
|
+
assert.notEqual(
|
|
6799
|
+
meeting.pstnCorrelationId,
|
|
6800
|
+
firstPstnCorrelationId,
|
|
6801
|
+
'pstnCorrelationId should be regenerated on each dial-out attempt'
|
|
6802
|
+
);
|
|
6550
6803
|
});
|
|
6551
6804
|
|
|
6552
|
-
it('rejects if the request failed (dial in)', () => {
|
|
6553
|
-
const error = '
|
|
6805
|
+
it('rejects if the request failed (dial in)', async () => {
|
|
6806
|
+
const error = {error: {message: 'dial in failed'}, stack: 'error stack'};
|
|
6554
6807
|
|
|
6555
6808
|
meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
|
|
6556
6809
|
|
|
6557
|
-
|
|
6558
|
-
.usePhoneAudio()
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6810
|
+
try {
|
|
6811
|
+
await meeting.usePhoneAudio();
|
|
6812
|
+
throw new Error('Promise resolved when it should have rejected');
|
|
6813
|
+
} catch (e) {
|
|
6814
|
+
assert.equal(e, error);
|
|
6562
6815
|
|
|
6563
|
-
|
|
6816
|
+
// Verify behavioral metric was sent with dial_in_correlation_id
|
|
6817
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
|
|
6818
|
+
correlation_id: meeting.correlationId,
|
|
6819
|
+
dial_in_url: meeting.dialInUrl,
|
|
6820
|
+
dial_in_correlation_id: sinon.match.string,
|
|
6821
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
6822
|
+
client_url: meeting.deviceUrl,
|
|
6823
|
+
reason: error.error.message,
|
|
6824
|
+
stack: error.stack,
|
|
6564
6825
|
});
|
|
6826
|
+
|
|
6827
|
+
// Verify pstnCorrelationId was cleared after error
|
|
6828
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6829
|
+
}
|
|
6565
6830
|
});
|
|
6566
6831
|
|
|
6567
6832
|
it('rejects if the request failed (dial out)', async () => {
|
|
6568
|
-
const error = '
|
|
6833
|
+
const error = {error: {message: 'dial out failed'}, stack: 'error stack'};
|
|
6569
6834
|
|
|
6570
6835
|
meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
|
|
6571
6836
|
|
|
6572
|
-
|
|
6573
|
-
.usePhoneAudio('+441234567890')
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6837
|
+
try {
|
|
6838
|
+
await meeting.usePhoneAudio('+441234567890');
|
|
6839
|
+
throw new Error('Promise resolved when it should have rejected');
|
|
6840
|
+
} catch (e) {
|
|
6841
|
+
assert.equal(e, error);
|
|
6577
6842
|
|
|
6578
|
-
|
|
6843
|
+
// Verify behavioral metric was sent with dial_out_correlation_id
|
|
6844
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
|
|
6845
|
+
correlation_id: meeting.correlationId,
|
|
6846
|
+
dial_out_url: meeting.dialOutUrl,
|
|
6847
|
+
dial_out_correlation_id: sinon.match.string,
|
|
6848
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
6849
|
+
client_url: meeting.deviceUrl,
|
|
6850
|
+
reason: error.error.message,
|
|
6851
|
+
stack: error.stack,
|
|
6579
6852
|
});
|
|
6853
|
+
|
|
6854
|
+
// Verify pstnCorrelationId was cleared after error
|
|
6855
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6856
|
+
}
|
|
6857
|
+
});
|
|
6858
|
+
});
|
|
6859
|
+
|
|
6860
|
+
describe('#disconnectPhoneAudio', () => {
|
|
6861
|
+
beforeEach(() => {
|
|
6862
|
+
// Mock the MeetingUtil.disconnectPhoneAudio method
|
|
6863
|
+
sinon.stub(MeetingUtil, 'disconnectPhoneAudio').resolves();
|
|
6864
|
+
meeting.dialInUrl = 'dialin:///test-dial-in-url';
|
|
6865
|
+
meeting.dialOutUrl = 'dialout:///test-dial-out-url';
|
|
6866
|
+
meeting.dialInDeviceStatus = 'JOINED';
|
|
6867
|
+
meeting.dialOutDeviceStatus = 'JOINED';
|
|
6868
|
+
});
|
|
6869
|
+
|
|
6870
|
+
afterEach(() => {
|
|
6871
|
+
MeetingUtil.disconnectPhoneAudio.restore();
|
|
6872
|
+
});
|
|
6873
|
+
|
|
6874
|
+
it('should disconnect phone audio and clear pstnCorrelationId', async () => {
|
|
6875
|
+
meeting.pstnCorrelationId = 'test-pstn-correlation-id';
|
|
6876
|
+
|
|
6877
|
+
await meeting.disconnectPhoneAudio();
|
|
6878
|
+
|
|
6879
|
+
// Verify that pstnCorrelationId is cleared
|
|
6880
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6881
|
+
|
|
6882
|
+
// Verify that MeetingUtil.disconnectPhoneAudio was called for both dial-in and dial-out
|
|
6883
|
+
assert.calledTwice(MeetingUtil.disconnectPhoneAudio);
|
|
6884
|
+
assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialInUrl);
|
|
6885
|
+
assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialOutUrl);
|
|
6886
|
+
});
|
|
6887
|
+
|
|
6888
|
+
it('should handle case when no PSTN connection is active', async () => {
|
|
6889
|
+
meeting.dialInDeviceStatus = 'IDLE';
|
|
6890
|
+
meeting.dialOutDeviceStatus = 'IDLE';
|
|
6891
|
+
meeting.pstnCorrelationId = 'test-pstn-correlation-id';
|
|
6892
|
+
|
|
6893
|
+
await meeting.disconnectPhoneAudio();
|
|
6894
|
+
|
|
6895
|
+
// Verify that pstnCorrelationId is still cleared even when no phone connection is active
|
|
6896
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6897
|
+
// And verify no disconnect was attempted
|
|
6898
|
+
assert.notCalled(MeetingUtil.disconnectPhoneAudio);
|
|
6580
6899
|
});
|
|
6581
6900
|
});
|
|
6582
6901
|
|
|
@@ -7330,6 +7649,8 @@ describe('plugin-meetings', () => {
|
|
|
7330
7649
|
'locus-id',
|
|
7331
7650
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
|
7332
7651
|
{meetingId: meeting.id, sendCAevents: true},
|
|
7652
|
+
null,
|
|
7653
|
+
null,
|
|
7333
7654
|
null
|
|
7334
7655
|
);
|
|
7335
7656
|
assert.deepEqual(meeting.meetingInfo, {
|
|
@@ -7376,6 +7697,8 @@ describe('plugin-meetings', () => {
|
|
|
7376
7697
|
'locus-id',
|
|
7377
7698
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
|
7378
7699
|
{meetingId: meeting.id, sendCAevents: true},
|
|
7700
|
+
null,
|
|
7701
|
+
null,
|
|
7379
7702
|
null
|
|
7380
7703
|
);
|
|
7381
7704
|
assert.deepEqual(meeting.meetingInfo, {
|
|
@@ -7431,6 +7754,8 @@ describe('plugin-meetings', () => {
|
|
|
7431
7754
|
permissionToken: FAKE_PERMISSION_TOKEN,
|
|
7432
7755
|
},
|
|
7433
7756
|
{meetingId: meeting.id, sendCAevents: true},
|
|
7757
|
+
null,
|
|
7758
|
+
null,
|
|
7434
7759
|
null
|
|
7435
7760
|
);
|
|
7436
7761
|
assert.deepEqual(meeting.meetingInfo, {
|
|
@@ -8093,6 +8418,7 @@ describe('plugin-meetings', () => {
|
|
|
8093
8418
|
|
|
8094
8419
|
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
|
8095
8420
|
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
|
8421
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
8096
8422
|
meeting.mediaProperties.mediaDirection = {
|
|
8097
8423
|
sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
|
|
8098
8424
|
sendVideo: 'fake value',
|
|
@@ -8174,6 +8500,12 @@ describe('plugin-meetings', () => {
|
|
|
8174
8500
|
payload: {mediaType: 'share', shareInstanceId: meeting.localShareInstanceId},
|
|
8175
8501
|
options: {meetingId: meeting.id},
|
|
8176
8502
|
});
|
|
8503
|
+
|
|
8504
|
+
// ensure the share start timestamp is saved
|
|
8505
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
8506
|
+
key: 'internal.client.share.initiated',
|
|
8507
|
+
});
|
|
8508
|
+
|
|
8177
8509
|
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
|
8178
8510
|
|
|
8179
8511
|
assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
|
|
@@ -8192,6 +8524,11 @@ describe('plugin-meetings', () => {
|
|
|
8192
8524
|
options: {meetingId: meeting.id},
|
|
8193
8525
|
});
|
|
8194
8526
|
|
|
8527
|
+
// ensure the share start timestamp is saved
|
|
8528
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
8529
|
+
key: 'internal.client.share.initiated',
|
|
8530
|
+
});
|
|
8531
|
+
|
|
8195
8532
|
assert.calledWith(
|
|
8196
8533
|
meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
|
|
8197
8534
|
stream
|
|
@@ -8860,11 +9197,16 @@ describe('plugin-meetings', () => {
|
|
|
8860
9197
|
meeting.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
8861
9198
|
meeting.setupMediaConnectionListeners();
|
|
8862
9199
|
|
|
9200
|
+
sinon.stub(MeetingUtil, 'getCaEventLabelsForIpVersion').returns(['fake labels']);
|
|
9201
|
+
|
|
8863
9202
|
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
8864
9203
|
|
|
8865
9204
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
8866
9205
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8867
9206
|
name: 'client.ice.start',
|
|
9207
|
+
payload: {
|
|
9208
|
+
labels: ['fake labels'],
|
|
9209
|
+
},
|
|
8868
9210
|
options: {
|
|
8869
9211
|
meetingId: meeting.id,
|
|
8870
9212
|
},
|
|
@@ -10129,6 +10471,24 @@ describe('plugin-meetings', () => {
|
|
|
10129
10471
|
);
|
|
10130
10472
|
});
|
|
10131
10473
|
|
|
10474
|
+
it('listens to CONTROLS_AUTO_END_MEETING_WARNING_CHANGED', async () => {
|
|
10475
|
+
const state = {example: 'value'};
|
|
10476
|
+
|
|
10477
|
+
await meeting.locusInfo.emitScoped(
|
|
10478
|
+
{function: 'test', file: 'test'},
|
|
10479
|
+
LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED,
|
|
10480
|
+
{state}
|
|
10481
|
+
);
|
|
10482
|
+
|
|
10483
|
+
assert.calledWith(
|
|
10484
|
+
TriggerProxy.trigger,
|
|
10485
|
+
meeting,
|
|
10486
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
10487
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_AUTO_END_MEETING_WARNING_UPDATED,
|
|
10488
|
+
{state}
|
|
10489
|
+
);
|
|
10490
|
+
});
|
|
10491
|
+
|
|
10132
10492
|
it('listens to CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED', async () => {
|
|
10133
10493
|
const state = {example: 'value'};
|
|
10134
10494
|
|
|
@@ -10208,6 +10568,7 @@ describe('plugin-meetings', () => {
|
|
|
10208
10568
|
describe('#setUpLocusUrlListener', () => {
|
|
10209
10569
|
it('listens to the locus url update event', (done) => {
|
|
10210
10570
|
const newLocusUrl = 'newLocusUrl/12345';
|
|
10571
|
+
const payload = {url: newLocusUrl}
|
|
10211
10572
|
|
|
10212
10573
|
meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
|
|
10213
10574
|
meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
@@ -10221,14 +10582,14 @@ describe('plugin-meetings', () => {
|
|
|
10221
10582
|
meeting.locusInfo.emit(
|
|
10222
10583
|
{function: 'test', file: 'test'},
|
|
10223
10584
|
'LOCUS_INFO_UPDATE_URL',
|
|
10224
|
-
|
|
10585
|
+
payload
|
|
10225
10586
|
);
|
|
10226
10587
|
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
10227
10588
|
assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
|
|
10228
10589
|
assert.calledOnceWithExactly(meeting.annotation.locusUrlUpdate, newLocusUrl);
|
|
10229
10590
|
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
10230
10591
|
assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
|
|
10231
|
-
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
|
|
10592
|
+
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, false);
|
|
10232
10593
|
assert.calledWith(meeting.simultaneousInterpretation.locusUrlUpdate, newLocusUrl);
|
|
10233
10594
|
assert.calledWith(meeting.webinar.locusUrlUpdate, newLocusUrl);
|
|
10234
10595
|
assert.equal(meeting.locusUrl, newLocusUrl);
|
|
@@ -10246,6 +10607,22 @@ describe('plugin-meetings', () => {
|
|
|
10246
10607
|
{locusUrl: 'newLocusUrl/12345'}
|
|
10247
10608
|
);
|
|
10248
10609
|
|
|
10610
|
+
done();
|
|
10611
|
+
});
|
|
10612
|
+
it('update mainLocusUrl for controlsOptionManager if payload.isMainLocus as true', (done) => {
|
|
10613
|
+
const newLocusUrl = 'newLocusUrl/12345';
|
|
10614
|
+
const payload = {url: newLocusUrl, isMainLocus: true}
|
|
10615
|
+
|
|
10616
|
+
meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
10617
|
+
|
|
10618
|
+
meeting.locusInfo.emit(
|
|
10619
|
+
{function: 'test', file: 'test'},
|
|
10620
|
+
'LOCUS_INFO_UPDATE_URL',
|
|
10621
|
+
payload
|
|
10622
|
+
);
|
|
10623
|
+
|
|
10624
|
+
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, true);
|
|
10625
|
+
|
|
10249
10626
|
done();
|
|
10250
10627
|
});
|
|
10251
10628
|
});
|
|
@@ -10465,6 +10842,8 @@ describe('plugin-meetings', () => {
|
|
|
10465
10842
|
meeting.mediaProperties = {mediaDirection: {sendShare: true}};
|
|
10466
10843
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
10467
10844
|
(meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
|
|
10845
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
10846
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
|
|
10468
10847
|
});
|
|
10469
10848
|
it('should call changeMeetingFloor()', async () => {
|
|
10470
10849
|
meeting.screenShareFloorState = 'GRANTED';
|
|
@@ -10482,6 +10861,22 @@ describe('plugin-meetings', () => {
|
|
|
10482
10861
|
assert.exists(share.then);
|
|
10483
10862
|
await share;
|
|
10484
10863
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
10864
|
+
|
|
10865
|
+
// ensure the share stop timestamp is saved
|
|
10866
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
10867
|
+
key: 'internal.client.share.stopped',
|
|
10868
|
+
});
|
|
10869
|
+
|
|
10870
|
+
// ensure the CA share stopped metric is submitted with duration
|
|
10871
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
10872
|
+
name: 'client.share.stopped',
|
|
10873
|
+
payload: {
|
|
10874
|
+
mediaType: 'share',
|
|
10875
|
+
shareInstanceId: meeting.localShareInstanceId,
|
|
10876
|
+
shareDuration: 1000,
|
|
10877
|
+
},
|
|
10878
|
+
options: {meetingId: meeting.id},
|
|
10879
|
+
});
|
|
10485
10880
|
});
|
|
10486
10881
|
it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
|
|
10487
10882
|
// change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
|
|
@@ -11065,6 +11460,8 @@ describe('plugin-meetings', () => {
|
|
|
11065
11460
|
let canUserRenameOthersSpy;
|
|
11066
11461
|
let canShareWhiteBoardSpy;
|
|
11067
11462
|
let canMoveToLobbySpy;
|
|
11463
|
+
let isSpokenLanguageAutoDetectionEnabledSpy;
|
|
11464
|
+
let showAutoEndMeetingWarningSpy;
|
|
11068
11465
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
11069
11466
|
|
|
11070
11467
|
beforeEach(() => {
|
|
@@ -11096,11 +11493,15 @@ describe('plugin-meetings', () => {
|
|
|
11096
11493
|
canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
|
|
11097
11494
|
canShareWhiteBoardSpy = sinon.spy(MeetingUtil, 'canShareWhiteBoard');
|
|
11098
11495
|
canMoveToLobbySpy = sinon.spy(MeetingUtil, 'canMoveToLobby');
|
|
11496
|
+
showAutoEndMeetingWarningSpy = sinon.spy(MeetingUtil, 'showAutoEndMeetingWarning');
|
|
11497
|
+
isSpokenLanguageAutoDetectionEnabledSpy = sinon.spy(MeetingUtil, 'isSpokenLanguageAutoDetectionEnabled');
|
|
11498
|
+
|
|
11099
11499
|
});
|
|
11100
11500
|
|
|
11101
11501
|
afterEach(() => {
|
|
11102
11502
|
inMeetingActionsSetSpy.restore();
|
|
11103
11503
|
waitingForOthersToJoinSpy.restore();
|
|
11504
|
+
showAutoEndMeetingWarningSpy.restore();
|
|
11104
11505
|
});
|
|
11105
11506
|
|
|
11106
11507
|
forEach(
|
|
@@ -11648,6 +12049,8 @@ describe('plugin-meetings', () => {
|
|
|
11648
12049
|
assert.calledWith(canUserRenameOthersSpy, userDisplayHints);
|
|
11649
12050
|
assert.calledWith(canShareWhiteBoardSpy, userDisplayHints, selfUserPolicies);
|
|
11650
12051
|
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
|
12052
|
+
assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
|
|
12053
|
+
assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
|
|
11651
12054
|
|
|
11652
12055
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
11653
12056
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
@@ -12054,6 +12457,7 @@ describe('plugin-meetings', () => {
|
|
|
12054
12457
|
meeting.locusInfo.self = {url: url1};
|
|
12055
12458
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
12056
12459
|
meeting.deviceUrl = 'deviceUrl.com';
|
|
12460
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
12057
12461
|
});
|
|
12058
12462
|
it('should have #startWhiteboardShare', () => {
|
|
12059
12463
|
assert.exists(meeting.startWhiteboardShare);
|
|
@@ -12081,6 +12485,11 @@ describe('plugin-meetings', () => {
|
|
|
12081
12485
|
payload: {mediaType: 'whiteboard'},
|
|
12082
12486
|
options: {meetingId: meeting.id},
|
|
12083
12487
|
});
|
|
12488
|
+
|
|
12489
|
+
// ensure the share start timestamp is saved
|
|
12490
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
12491
|
+
key: 'internal.client.share.initiated',
|
|
12492
|
+
});
|
|
12084
12493
|
});
|
|
12085
12494
|
});
|
|
12086
12495
|
describe('#stopWhiteboardShare', () => {
|
|
@@ -12092,6 +12501,9 @@ describe('plugin-meetings', () => {
|
|
|
12092
12501
|
meeting.locusInfo.self = {url: url1};
|
|
12093
12502
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
12094
12503
|
meeting.deviceUrl = 'deviceUrl.com';
|
|
12504
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
12505
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
|
|
12506
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
|
12095
12507
|
});
|
|
12096
12508
|
it('should stop the whiteboard share', async () => {
|
|
12097
12509
|
const whiteboardShare = meeting.stopWhiteboardShare();
|
|
@@ -12106,6 +12518,21 @@ describe('plugin-meetings', () => {
|
|
|
12106
12518
|
uri: url1,
|
|
12107
12519
|
});
|
|
12108
12520
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
12521
|
+
|
|
12522
|
+
// ensure the share stop timestamp is saved
|
|
12523
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
12524
|
+
key: 'internal.client.share.stopped',
|
|
12525
|
+
});
|
|
12526
|
+
|
|
12527
|
+
// ensure the CA share stopped metric is submitted with duration
|
|
12528
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
12529
|
+
name: 'client.share.stopped',
|
|
12530
|
+
payload: {
|
|
12531
|
+
mediaType: 'whiteboard',
|
|
12532
|
+
shareDuration: 1000,
|
|
12533
|
+
},
|
|
12534
|
+
options: {meetingId: meeting.id},
|
|
12535
|
+
});
|
|
12109
12536
|
});
|
|
12110
12537
|
});
|
|
12111
12538
|
});
|
|
@@ -12178,6 +12605,9 @@ describe('plugin-meetings', () => {
|
|
|
12178
12605
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
12179
12606
|
meeting.deviceUrl = 'my-web-url';
|
|
12180
12607
|
meeting.locusInfo.info = {isWebinar: false};
|
|
12608
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
12609
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1500);
|
|
12610
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
|
12181
12611
|
});
|
|
12182
12612
|
|
|
12183
12613
|
const USER_IDS = {
|
|
@@ -12404,12 +12834,12 @@ describe('plugin-meetings', () => {
|
|
|
12404
12834
|
activeSharingId.whiteboard = beneficiaryId;
|
|
12405
12835
|
|
|
12406
12836
|
eventTrigger.share.push(
|
|
12407
|
-
meeting.webinar.selfIsAttendee
|
|
12837
|
+
meeting.webinar.selfIsAttendee || meeting.guest
|
|
12408
12838
|
? {
|
|
12409
12839
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
12410
12840
|
functionName: 'remoteShare',
|
|
12411
12841
|
eventPayload: {
|
|
12412
|
-
memberId: null,
|
|
12842
|
+
memberId: meeting.webinar.selfIsAttendee ? beneficiaryId : null,
|
|
12413
12843
|
url,
|
|
12414
12844
|
shareInstanceId,
|
|
12415
12845
|
annotationInfo: undefined,
|
|
@@ -12423,7 +12853,8 @@ describe('plugin-meetings', () => {
|
|
|
12423
12853
|
}
|
|
12424
12854
|
);
|
|
12425
12855
|
|
|
12426
|
-
shareStatus =
|
|
12856
|
+
shareStatus =
|
|
12857
|
+
meeting.webinar.selfIsAttendee || meeting.guest
|
|
12427
12858
|
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
|
12428
12859
|
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
12429
12860
|
}
|
|
@@ -12463,7 +12894,7 @@ describe('plugin-meetings', () => {
|
|
|
12463
12894
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
12464
12895
|
functionName: 'remoteShare',
|
|
12465
12896
|
eventPayload: {
|
|
12466
|
-
memberId:
|
|
12897
|
+
memberId: beneficiaryId,
|
|
12467
12898
|
url,
|
|
12468
12899
|
shareInstanceId,
|
|
12469
12900
|
annotationInfo: undefined,
|
|
@@ -12641,6 +13072,36 @@ describe('plugin-meetings', () => {
|
|
|
12641
13072
|
});
|
|
12642
13073
|
});
|
|
12643
13074
|
|
|
13075
|
+
describe('Whiteboard Share - User is guest', () => {
|
|
13076
|
+
it('User receives a remote share instead of whiteboard share', () => {
|
|
13077
|
+
// Set the guest flag
|
|
13078
|
+
meeting.guest = true;
|
|
13079
|
+
|
|
13080
|
+
// Step 1: Start sharing whiteboard A
|
|
13081
|
+
const data1 = generateData(
|
|
13082
|
+
blankPayload, // Initial payload
|
|
13083
|
+
true, // isGranting: Granting share
|
|
13084
|
+
false, // isContent: Whiteboard (not content)
|
|
13085
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
|
13086
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
|
13087
|
+
);
|
|
13088
|
+
|
|
13089
|
+
// Step 2: Stop sharing whiteboard A
|
|
13090
|
+
const data2 = generateData(
|
|
13091
|
+
data1.payload, // Updated payload from Step 1
|
|
13092
|
+
false, // isGranting: Stopping share
|
|
13093
|
+
false, // isContent: Whiteboard
|
|
13094
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
|
13095
|
+
);
|
|
13096
|
+
|
|
13097
|
+
// Validate the payload changes and status updates
|
|
13098
|
+
payloadTestHelper([data1]);
|
|
13099
|
+
|
|
13100
|
+
// Specific assertions for guest
|
|
13101
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
|
13102
|
+
});
|
|
13103
|
+
});
|
|
13104
|
+
|
|
12644
13105
|
describe('Whiteboard A --> Whiteboard B', () => {
|
|
12645
13106
|
it('Scenario #1: you share both whiteboards', () => {
|
|
12646
13107
|
const data1 = generateData(
|
|
@@ -13292,7 +13753,54 @@ describe('plugin-meetings', () => {
|
|
|
13292
13753
|
payloadTestHelper([data1, data2, data3]);
|
|
13293
13754
|
});
|
|
13294
13755
|
});
|
|
13295
|
-
|
|
13756
|
+
|
|
13757
|
+
it('should send share stopped metric when whiteboard sharing stops', () => {
|
|
13758
|
+
// Start whiteboard sharing (this won't trigger metrics)
|
|
13759
|
+
const data1 = generateData(
|
|
13760
|
+
blankPayload,
|
|
13761
|
+
true, // isGranting: true
|
|
13762
|
+
false, // isContent: false (whiteboard)
|
|
13763
|
+
USER_IDS.ME,
|
|
13764
|
+
RESOURCE_URLS.WHITEBOARD_A
|
|
13765
|
+
);
|
|
13766
|
+
|
|
13767
|
+
// Stop whiteboard sharing (this should trigger metrics)
|
|
13768
|
+
const data2 = generateData(
|
|
13769
|
+
data1.payload,
|
|
13770
|
+
false, // isGranting: false (stopping share)
|
|
13771
|
+
false, // isContent: false (whiteboard)
|
|
13772
|
+
USER_IDS.ME
|
|
13773
|
+
);
|
|
13774
|
+
|
|
13775
|
+
// Trigger the events
|
|
13776
|
+
meeting.locusInfo.emit(
|
|
13777
|
+
{function: 'test', file: 'test'},
|
|
13778
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
13779
|
+
data1.payload
|
|
13780
|
+
);
|
|
13781
|
+
|
|
13782
|
+
meeting.locusInfo.emit(
|
|
13783
|
+
{function: 'test', file: 'test'},
|
|
13784
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
13785
|
+
data2.payload
|
|
13786
|
+
);
|
|
13787
|
+
|
|
13788
|
+
// Verify metrics were called when whiteboard sharing stopped
|
|
13789
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
13790
|
+
key: 'internal.client.share.stopped',
|
|
13791
|
+
});
|
|
13792
|
+
|
|
13793
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
13794
|
+
name: 'client.share.stopped',
|
|
13795
|
+
payload: {
|
|
13796
|
+
mediaType: 'whiteboard',
|
|
13797
|
+
shareDuration: 1500, // mocked return value
|
|
13798
|
+
},
|
|
13799
|
+
options: {
|
|
13800
|
+
meetingId: meeting.id,
|
|
13801
|
+
},
|
|
13802
|
+
});
|
|
13803
|
+
});
|
|
13296
13804
|
|
|
13297
13805
|
describe('handleShareVideoStreamMuteStateChange', () => {
|
|
13298
13806
|
it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
|
|
@@ -13319,6 +13827,7 @@ describe('plugin-meetings', () => {
|
|
|
13319
13827
|
});
|
|
13320
13828
|
});
|
|
13321
13829
|
});
|
|
13830
|
+
});
|
|
13322
13831
|
|
|
13323
13832
|
describe('#startKeepAlive', () => {
|
|
13324
13833
|
let clock;
|
|
@@ -14524,4 +15033,79 @@ describe('plugin-meetings', () => {
|
|
|
14524
15033
|
);
|
|
14525
15034
|
});
|
|
14526
15035
|
});
|
|
15036
|
+
|
|
15037
|
+
describe('#notifyHost', () => {
|
|
15038
|
+
beforeEach(() => {
|
|
15039
|
+
meeting.meetingRequest.notifyHost = sinon.stub().returns(Promise.resolve());
|
|
15040
|
+
});
|
|
15041
|
+
|
|
15042
|
+
it('sends the expected request', async () => {
|
|
15043
|
+
meeting.meetingInfo.siteFullUrl = `convergedats.webex.com`;
|
|
15044
|
+
const meetingUuid = 'meeting-uuid';
|
|
15045
|
+
const displayName = ['Test', 'User'];
|
|
15046
|
+
meeting.locusId = 'locusId';
|
|
15047
|
+
|
|
15048
|
+
const notifyHostPromise = meeting.notifyHost(meetingUuid, displayName);
|
|
15049
|
+
|
|
15050
|
+
assert.exists(notifyHostPromise.then);
|
|
15051
|
+
await notifyHostPromise;
|
|
15052
|
+
|
|
15053
|
+
assert.calledOnceWithExactly(
|
|
15054
|
+
meeting.meetingRequest.notifyHost,
|
|
15055
|
+
meeting.meetingInfo.siteFullUrl,
|
|
15056
|
+
meeting.locusId,
|
|
15057
|
+
meetingUuid,
|
|
15058
|
+
displayName,
|
|
15059
|
+
);
|
|
15060
|
+
});
|
|
15061
|
+
});
|
|
15062
|
+
|
|
15063
|
+
describe('#sipCallOut', () => {
|
|
15064
|
+
beforeEach(() => {
|
|
15065
|
+
meeting.meetingRequest.sipCallOut = sinon.stub().returns(Promise.resolve({body: {}}));
|
|
15066
|
+
});
|
|
15067
|
+
|
|
15068
|
+
it('sends the expected request', async () => {
|
|
15069
|
+
const address = 'sip:user@example.com';
|
|
15070
|
+
const displayName = 'John Doe';
|
|
15071
|
+
const meetingId = 'a643beaa47f04eedac08f1310ca12366';
|
|
15072
|
+
|
|
15073
|
+
meeting.meetingInfo = {
|
|
15074
|
+
meetingId,
|
|
15075
|
+
};
|
|
15076
|
+
|
|
15077
|
+
const sipCallOutPromise = meeting.sipCallOut(address, displayName);
|
|
15078
|
+
|
|
15079
|
+
assert.exists(sipCallOutPromise.then);
|
|
15080
|
+
await sipCallOutPromise;
|
|
15081
|
+
|
|
15082
|
+
assert.calledOnceWithExactly(
|
|
15083
|
+
meeting.meetingRequest.sipCallOut,
|
|
15084
|
+
meetingId,
|
|
15085
|
+
meetingId,
|
|
15086
|
+
address,
|
|
15087
|
+
displayName
|
|
15088
|
+
);
|
|
15089
|
+
});
|
|
15090
|
+
});
|
|
15091
|
+
|
|
15092
|
+
describe('#cancelSipCallOut', () => {
|
|
15093
|
+
beforeEach(() => {
|
|
15094
|
+
meeting.meetingRequest.cancelSipCallOut = sinon.stub().returns(Promise.resolve({body: {}}));
|
|
15095
|
+
});
|
|
15096
|
+
|
|
15097
|
+
it('sends the expected request', async () => {
|
|
15098
|
+
const participantId = '12345-abcde';
|
|
15099
|
+
|
|
15100
|
+
const cancelSipCallOutPromise = meeting.cancelSipCallOut(participantId);
|
|
15101
|
+
|
|
15102
|
+
assert.exists(cancelSipCallOutPromise.then);
|
|
15103
|
+
await cancelSipCallOutPromise;
|
|
15104
|
+
|
|
15105
|
+
assert.calledOnceWithExactly(
|
|
15106
|
+
meeting.meetingRequest.cancelSipCallOut,
|
|
15107
|
+
participantId
|
|
15108
|
+
);
|
|
15109
|
+
});
|
|
15110
|
+
});
|
|
14527
15111
|
});
|