@webex/plugin-meetings 3.9.0-webinar5k.1 → 3.10.0-multi-llms.1
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/common/errors/webex-errors.js +21 -1
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/constants.js +25 -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 +9 -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/index.js +5 -0
- package/dist/media/index.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 +468 -278
- 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/common/errors/webex-errors.d.ts +12 -0
- package/dist/types/constants.d.ts +23 -0
- package/dist/types/controls-options-manager/index.d.ts +9 -1
- package/dist/types/index.d.ts +2 -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 +26 -28
- package/src/common/errors/webex-errors.ts +19 -0
- package/src/constants.ts +26 -2
- package/src/controls-options-manager/index.ts +26 -5
- package/src/index.ts +4 -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/index.ts +6 -0
- package/src/media/properties.ts +43 -0
- package/src/meeting/in-meeting-actions.ts +29 -0
- package/src/meeting/index.ts +299 -88
- 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/index.ts +140 -9
- 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 +679 -82
- 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 +170 -17
- package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
- package/test/unit/spec/meetings/index.js +12 -7
- 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';
|
|
@@ -95,6 +97,7 @@ import PermissionError from '../../../../src/common/errors/permission';
|
|
|
95
97
|
import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
|
|
96
98
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
97
99
|
import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';
|
|
100
|
+
import {SdpResponseTimeoutError} from '@webex/plugin-meetings/src/common/errors/webex-errors';
|
|
98
101
|
import testUtils from '../../../utils/testUtils';
|
|
99
102
|
import {
|
|
100
103
|
MeetingInfoV2CaptchaError,
|
|
@@ -244,6 +247,7 @@ describe('plugin-meetings', () => {
|
|
|
244
247
|
});
|
|
245
248
|
|
|
246
249
|
webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
|
|
250
|
+
webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
|
|
247
251
|
webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
|
|
248
252
|
webex.internal.services = {get: sinon.stub().returns('locus-url')};
|
|
249
253
|
webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
|
|
@@ -368,6 +372,35 @@ describe('plugin-meetings', () => {
|
|
|
368
372
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
369
373
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
370
374
|
});
|
|
375
|
+
|
|
376
|
+
it('should call the callback with the meeting that has id already set', () => {
|
|
377
|
+
let meetingIdFromCallback;
|
|
378
|
+
// check that the meeting id is already set correctly at the time when the callback is called
|
|
379
|
+
const meetingCreationCallback = sinon.stub().callsFake((meeting) => {
|
|
380
|
+
meetingIdFromCallback = meeting.id;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
meeting = new Meeting(
|
|
384
|
+
{
|
|
385
|
+
userId: uuid1,
|
|
386
|
+
resource: uuid2,
|
|
387
|
+
deviceUrl: uuid3,
|
|
388
|
+
locus: {url: url1},
|
|
389
|
+
destination: testDestination,
|
|
390
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
|
391
|
+
correlationId,
|
|
392
|
+
selfId: uuid1,
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
parent: webex,
|
|
396
|
+
},
|
|
397
|
+
meetingCreationCallback
|
|
398
|
+
);
|
|
399
|
+
assert.exists(meeting.id);
|
|
400
|
+
assert.calledOnceWithExactly(meetingCreationCallback, meeting);
|
|
401
|
+
assert.equal(meeting.id, meetingIdFromCallback);
|
|
402
|
+
});
|
|
403
|
+
|
|
371
404
|
it('creates MediaRequestManager instances', () => {
|
|
372
405
|
assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
|
|
373
406
|
assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
|
|
@@ -454,6 +487,18 @@ describe('plugin-meetings', () => {
|
|
|
454
487
|
});
|
|
455
488
|
});
|
|
456
489
|
|
|
490
|
+
it('pstnCorrelationId getter/setter should work correctly', () => {
|
|
491
|
+
const testPstnCorrelationId = uuid.v4();
|
|
492
|
+
|
|
493
|
+
meeting.pstnCorrelationId = testPstnCorrelationId;
|
|
494
|
+
assert.equal(meeting.pstnCorrelationId, testPstnCorrelationId);
|
|
495
|
+
assert.equal(meeting.callStateForMetrics.pstnCorrelationId, testPstnCorrelationId);
|
|
496
|
+
|
|
497
|
+
meeting.pstnCorrelationId = undefined;
|
|
498
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
499
|
+
assert.equal(meeting.callStateForMetrics.pstnCorrelationId, undefined);
|
|
500
|
+
});
|
|
501
|
+
|
|
457
502
|
describe('creates ReceiveSlot manager instance', () => {
|
|
458
503
|
let mockReceiveSlotManagerCtor;
|
|
459
504
|
let providedCreateSlotCallback;
|
|
@@ -581,7 +626,6 @@ describe('plugin-meetings', () => {
|
|
|
581
626
|
assert.isFalse(meeting.isLocusCall());
|
|
582
627
|
});
|
|
583
628
|
});
|
|
584
|
-
|
|
585
629
|
describe('#invite', () => {
|
|
586
630
|
it('should have #invite', () => {
|
|
587
631
|
assert.exists(meeting.invite);
|
|
@@ -592,8 +636,6 @@ describe('plugin-meetings', () => {
|
|
|
592
636
|
it('should proxy members #addMember and return a promise', async () => {
|
|
593
637
|
const invite = meeting.invite(uuid1, false);
|
|
594
638
|
|
|
595
|
-
assert.exists(invite.then);
|
|
596
|
-
await invite;
|
|
597
639
|
assert.calledOnce(meeting.members.addMember);
|
|
598
640
|
assert.calledWith(meeting.members.addMember, uuid1, false);
|
|
599
641
|
});
|
|
@@ -1949,23 +1991,24 @@ describe('plugin-meetings', () => {
|
|
|
1949
1991
|
});
|
|
1950
1992
|
});
|
|
1951
1993
|
|
|
1952
|
-
it('should
|
|
1994
|
+
it('should handle join failure', async () => {
|
|
1953
1995
|
MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
|
|
1996
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
|
1997
|
+
|
|
1954
1998
|
await meeting.join().catch(() => {
|
|
1955
|
-
assert.
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
);
|
|
1959
|
-
assert.
|
|
1960
|
-
|
|
1961
|
-
{
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
);
|
|
1999
|
+
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
2000
|
+
|
|
2001
|
+
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2002
|
+
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2003
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2004
|
+
name: 'client.call.initiated',
|
|
2005
|
+
payload: {
|
|
2006
|
+
trigger: 'user-interaction',
|
|
2007
|
+
isRoapCallEnabled: true,
|
|
2008
|
+
pstnAudioType: undefined,
|
|
2009
|
+
},
|
|
2010
|
+
options: {meetingId: meeting.id},
|
|
2011
|
+
});
|
|
1969
2012
|
});
|
|
1970
2013
|
});
|
|
1971
2014
|
it('should fail if password is required', async () => {
|
|
@@ -2172,6 +2215,7 @@ describe('plugin-meetings', () => {
|
|
|
2172
2215
|
});
|
|
2173
2216
|
meeting.audio = muteStateStub;
|
|
2174
2217
|
meeting.video = muteStateStub;
|
|
2218
|
+
sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.ipv4_and_ipv6);
|
|
2175
2219
|
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
2176
2220
|
sinon.stub(meeting, 'setupMediaConnectionListeners');
|
|
2177
2221
|
sinon.stub(meeting, 'setMercuryListener');
|
|
@@ -2243,13 +2287,24 @@ describe('plugin-meetings', () => {
|
|
|
2243
2287
|
close: sinon.stub(),
|
|
2244
2288
|
forceRtcMetricsSend,
|
|
2245
2289
|
});
|
|
2246
|
-
|
|
2290
|
+
|
|
2291
|
+
const mockStatsMonitor = {removeAllListeners: sinon.stub()};
|
|
2292
|
+
const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
|
|
2293
|
+
|
|
2294
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2247
2295
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2296
|
+
meeting.statsMonitor = mockStatsMonitor;
|
|
2297
|
+
meeting.networkQualityMonitor = mockNetworkQualityMonitor;
|
|
2248
2298
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2249
2299
|
|
|
2250
2300
|
assert.calledOnce(forceRtcMetricsSend);
|
|
2301
|
+
assert.calledOnce(mockStatsMonitor.removeAllListeners);
|
|
2302
|
+
assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
|
|
2251
2303
|
|
|
2252
2304
|
assert.isNull(meeting.statsAnalyzer);
|
|
2305
|
+
assert.isNull(meeting.statsMonitor);
|
|
2306
|
+
assert.isNull(meeting.networkQualityMonitor);
|
|
2307
|
+
|
|
2253
2308
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
2254
2309
|
assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
|
|
2255
2310
|
name: 'internal.client.add-media.turn-discovery.start',
|
|
@@ -2293,6 +2348,7 @@ describe('plugin-meetings', () => {
|
|
|
2293
2348
|
selected_subnet: null,
|
|
2294
2349
|
numTransports: 1,
|
|
2295
2350
|
iceCandidatesCount: 0,
|
|
2351
|
+
ipver: 1,
|
|
2296
2352
|
}
|
|
2297
2353
|
);
|
|
2298
2354
|
});
|
|
@@ -2340,6 +2396,7 @@ describe('plugin-meetings', () => {
|
|
|
2340
2396
|
subnet_reachable: null,
|
|
2341
2397
|
selected_cluster: null,
|
|
2342
2398
|
selected_subnet: null,
|
|
2399
|
+
ipver: 1,
|
|
2343
2400
|
})
|
|
2344
2401
|
);
|
|
2345
2402
|
|
|
@@ -2359,12 +2416,23 @@ describe('plugin-meetings', () => {
|
|
|
2359
2416
|
|
|
2360
2417
|
meeting.waitForRemoteSDPAnswer = sinon.stub().rejects();
|
|
2361
2418
|
|
|
2362
|
-
|
|
2419
|
+
const mockStatsMonitor = {removeAllListeners: sinon.stub()};
|
|
2420
|
+
const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
|
|
2421
|
+
|
|
2422
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2363
2423
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2424
|
+
meeting.statsMonitor = mockStatsMonitor;
|
|
2425
|
+
meeting.networkQualityMonitor = mockNetworkQualityMonitor;
|
|
2364
2426
|
|
|
2365
2427
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2366
2428
|
|
|
2367
2429
|
assert.isNull(meeting.statsAnalyzer);
|
|
2430
|
+
assert.isNull(meeting.statsMonitor);
|
|
2431
|
+
assert.isNull(meeting.networkQualityMonitor);
|
|
2432
|
+
|
|
2433
|
+
assert.calledOnce(mockStatsMonitor.removeAllListeners);
|
|
2434
|
+
assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
|
|
2435
|
+
|
|
2368
2436
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
2369
2437
|
assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
|
|
2370
2438
|
name: 'internal.client.add-media.turn-discovery.start',
|
|
@@ -2408,6 +2476,7 @@ describe('plugin-meetings', () => {
|
|
|
2408
2476
|
subnet_reachable: null,
|
|
2409
2477
|
selected_cluster: null,
|
|
2410
2478
|
selected_subnet: null,
|
|
2479
|
+
ipver: 1,
|
|
2411
2480
|
}
|
|
2412
2481
|
);
|
|
2413
2482
|
});
|
|
@@ -2428,8 +2497,9 @@ describe('plugin-meetings', () => {
|
|
|
2428
2497
|
},
|
|
2429
2498
|
},
|
|
2430
2499
|
});
|
|
2431
|
-
// set a statsAnalyzer on the meeting so that we can check that
|
|
2500
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2432
2501
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2502
|
+
meeting.statsMonitor = {removeAllListeners: sinon.stub()};
|
|
2433
2503
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2434
2504
|
|
|
2435
2505
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
@@ -2468,10 +2538,12 @@ describe('plugin-meetings', () => {
|
|
|
2468
2538
|
subnet_reachable: null,
|
|
2469
2539
|
selected_cluster: null,
|
|
2470
2540
|
selected_subnet: null,
|
|
2541
|
+
ipver: 1,
|
|
2471
2542
|
})
|
|
2472
2543
|
);
|
|
2473
2544
|
|
|
2474
2545
|
assert.isNull(meeting.statsAnalyzer);
|
|
2546
|
+
assert.isNull(meeting.statsMonitor);
|
|
2475
2547
|
});
|
|
2476
2548
|
|
|
2477
2549
|
it('should include the peer connection properties correctly for transcoded', async () => {
|
|
@@ -2488,8 +2560,14 @@ describe('plugin-meetings', () => {
|
|
|
2488
2560
|
},
|
|
2489
2561
|
},
|
|
2490
2562
|
});
|
|
2491
|
-
|
|
2563
|
+
|
|
2564
|
+
const mockStatsMonitor = {removeAllListeners: sinon.stub()};
|
|
2565
|
+
const mockNetworkQualityMonitor = {removeAllListeners: sinon.stub()};
|
|
2566
|
+
|
|
2567
|
+
// set a statsAnalyzer and statsMonitor on the meeting so that we can check that they get reset to null
|
|
2492
2568
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2569
|
+
meeting.statsMonitor = mockStatsMonitor;
|
|
2570
|
+
meeting.networkQualityMonitor = mockNetworkQualityMonitor;
|
|
2493
2571
|
const error = await assert.isRejected(meeting.addMedia());
|
|
2494
2572
|
|
|
2495
2573
|
assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
|
|
@@ -2528,10 +2606,15 @@ describe('plugin-meetings', () => {
|
|
|
2528
2606
|
subnet_reachable: null,
|
|
2529
2607
|
selected_cluster: null,
|
|
2530
2608
|
selected_subnet: null,
|
|
2609
|
+
ipver: 1,
|
|
2531
2610
|
})
|
|
2532
2611
|
);
|
|
2533
2612
|
|
|
2534
2613
|
assert.isNull(meeting.statsAnalyzer);
|
|
2614
|
+
assert.isNull(meeting.statsMonitor);
|
|
2615
|
+
assert.isNull(meeting.networkQualityMonitor);
|
|
2616
|
+
assert.calledOnce(mockStatsMonitor.removeAllListeners);
|
|
2617
|
+
assert.calledOnce(mockNetworkQualityMonitor.removeAllListeners);
|
|
2535
2618
|
});
|
|
2536
2619
|
|
|
2537
2620
|
it('should work the second time addMedia is called in case the first time fails', async () => {
|
|
@@ -2594,7 +2677,11 @@ describe('plugin-meetings', () => {
|
|
|
2594
2677
|
// simulate timeout waiting for the SDP answer that never comes
|
|
2595
2678
|
await clock.tickAsync(ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
|
|
2596
2679
|
|
|
2597
|
-
await assert.isRejected(
|
|
2680
|
+
await assert.isRejected(
|
|
2681
|
+
result,
|
|
2682
|
+
SdpResponseTimeoutError,
|
|
2683
|
+
'Timed out waiting for REMOTE SDP ANSWER'
|
|
2684
|
+
);
|
|
2598
2685
|
|
|
2599
2686
|
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
2600
2687
|
clientErrorCode: 2007,
|
|
@@ -3052,6 +3139,7 @@ describe('plugin-meetings', () => {
|
|
|
3052
3139
|
subnet_reachable: null,
|
|
3053
3140
|
selected_cluster: null,
|
|
3054
3141
|
selected_subnet: null,
|
|
3142
|
+
ipver: 1,
|
|
3055
3143
|
},
|
|
3056
3144
|
]);
|
|
3057
3145
|
|
|
@@ -3253,6 +3341,7 @@ describe('plugin-meetings', () => {
|
|
|
3253
3341
|
connectionType: 'udp',
|
|
3254
3342
|
selectedCandidatePairChanges: 2,
|
|
3255
3343
|
ipVersion: 'IPv6',
|
|
3344
|
+
ipver: 1,
|
|
3256
3345
|
numTransports: 1,
|
|
3257
3346
|
isMultistream: false,
|
|
3258
3347
|
retriedWithTurnServer: true,
|
|
@@ -3399,6 +3488,7 @@ describe('plugin-meetings', () => {
|
|
|
3399
3488
|
meeting.iceCandidatesCount = 3;
|
|
3400
3489
|
meeting.iceCandidateErrors.set('701_error', 3);
|
|
3401
3490
|
meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
|
|
3491
|
+
MeetingUtil.getIpVersion.returns(IP_VERSION.only_ipv6);
|
|
3402
3492
|
|
|
3403
3493
|
await meeting.addMedia({
|
|
3404
3494
|
mediaSettings: {},
|
|
@@ -3414,6 +3504,7 @@ describe('plugin-meetings', () => {
|
|
|
3414
3504
|
connectionType: 'udp',
|
|
3415
3505
|
selectedCandidatePairChanges: 2,
|
|
3416
3506
|
ipVersion: 'IPv6',
|
|
3507
|
+
ipver: 6,
|
|
3417
3508
|
numTransports: 1,
|
|
3418
3509
|
isMultistream: false,
|
|
3419
3510
|
retriedWithTurnServer: false,
|
|
@@ -3492,6 +3583,7 @@ describe('plugin-meetings', () => {
|
|
|
3492
3583
|
selected_cluster: null,
|
|
3493
3584
|
selected_subnet: null,
|
|
3494
3585
|
iceCandidatesCount: 0,
|
|
3586
|
+
ipver: 1,
|
|
3495
3587
|
}
|
|
3496
3588
|
);
|
|
3497
3589
|
|
|
@@ -3556,6 +3648,7 @@ describe('plugin-meetings', () => {
|
|
|
3556
3648
|
selected_cluster: null,
|
|
3557
3649
|
selected_subnet: null,
|
|
3558
3650
|
iceCandidatesCount: 0,
|
|
3651
|
+
ipver: 1,
|
|
3559
3652
|
}
|
|
3560
3653
|
);
|
|
3561
3654
|
|
|
@@ -3602,6 +3695,7 @@ describe('plugin-meetings', () => {
|
|
|
3602
3695
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3603
3696
|
connectionType: 'udp',
|
|
3604
3697
|
ipVersion: 'IPv6',
|
|
3698
|
+
ipver: 1,
|
|
3605
3699
|
selectedCandidatePairChanges: 2,
|
|
3606
3700
|
numTransports: 1,
|
|
3607
3701
|
isMultistream: false,
|
|
@@ -3682,6 +3776,7 @@ describe('plugin-meetings', () => {
|
|
|
3682
3776
|
selected_cluster: 'some.cluster',
|
|
3683
3777
|
selected_subnet: '1.X.X.X',
|
|
3684
3778
|
iceCandidatesCount: 0,
|
|
3779
|
+
ipver: 1,
|
|
3685
3780
|
}
|
|
3686
3781
|
);
|
|
3687
3782
|
|
|
@@ -3987,12 +4082,13 @@ describe('plugin-meetings', () => {
|
|
|
3987
4082
|
});
|
|
3988
4083
|
});
|
|
3989
4084
|
|
|
3990
|
-
it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
|
|
4085
|
+
it('counts the number of members that are in the meeting or lobby for MEDIA_QUALITY event', async () => {
|
|
3991
4086
|
let fakeMembersCollection = {
|
|
3992
4087
|
members: {
|
|
3993
|
-
member1: {isInMeeting: true},
|
|
3994
|
-
member2: {isInMeeting: true},
|
|
3995
|
-
member3: {isInMeeting: false},
|
|
4088
|
+
member1: {isInMeeting: true, isInLobby: false},
|
|
4089
|
+
member2: {isInMeeting: false, isInLobby: true},
|
|
4090
|
+
member3: {isInMeeting: false, isInLobby: false},
|
|
4091
|
+
member4: {isInMeeting: true, isInLobby: false},
|
|
3996
4092
|
},
|
|
3997
4093
|
};
|
|
3998
4094
|
sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
|
|
@@ -4011,11 +4107,12 @@ describe('plugin-meetings', () => {
|
|
|
4011
4107
|
},
|
|
4012
4108
|
payload: {
|
|
4013
4109
|
intervals: [
|
|
4014
|
-
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount',
|
|
4110
|
+
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 3)),
|
|
4015
4111
|
],
|
|
4016
4112
|
},
|
|
4017
4113
|
});
|
|
4018
|
-
|
|
4114
|
+
// Move member2 from lobby to neither in meeting nor lobby
|
|
4115
|
+
fakeMembersCollection.members.member2.isInLobby = false;
|
|
4019
4116
|
|
|
4020
4117
|
statsAnalyzerStub.emit(
|
|
4021
4118
|
{file: 'test', function: 'test'},
|
|
@@ -4030,7 +4127,7 @@ describe('plugin-meetings', () => {
|
|
|
4030
4127
|
},
|
|
4031
4128
|
payload: {
|
|
4032
4129
|
intervals: [
|
|
4033
|
-
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount',
|
|
4130
|
+
sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
|
|
4034
4131
|
],
|
|
4035
4132
|
},
|
|
4036
4133
|
});
|
|
@@ -4057,6 +4154,132 @@ describe('plugin-meetings', () => {
|
|
|
4057
4154
|
});
|
|
4058
4155
|
});
|
|
4059
4156
|
|
|
4157
|
+
describe('handles StatsMonitor events', () => {
|
|
4158
|
+
let statsMonitorStub;
|
|
4159
|
+
let prevConfigValue;
|
|
4160
|
+
let listeners;
|
|
4161
|
+
|
|
4162
|
+
beforeEach(async () => {
|
|
4163
|
+
meeting.meetingState = 'ACTIVE';
|
|
4164
|
+
prevConfigValue = meeting.config.stats.enableStatsAnalyzer;
|
|
4165
|
+
|
|
4166
|
+
meeting.config.stats.enableStatsAnalyzer = true;
|
|
4167
|
+
|
|
4168
|
+
listeners = {};
|
|
4169
|
+
|
|
4170
|
+
statsMonitorStub = {
|
|
4171
|
+
on: sinon.stub().callsFake((event, callback) => {
|
|
4172
|
+
listeners[event] = callback;
|
|
4173
|
+
}),
|
|
4174
|
+
removeAllListeners: sinon.stub(),
|
|
4175
|
+
};
|
|
4176
|
+
|
|
4177
|
+
sinon.stub(meeting.mediaProperties, 'sendMediaIssueMetric');
|
|
4178
|
+
|
|
4179
|
+
// mock the StatsMonitor constructor
|
|
4180
|
+
sinon.stub(InternalMediaCoreModule, 'StatsMonitor').returns(statsMonitorStub);
|
|
4181
|
+
|
|
4182
|
+
await meeting.addMedia({
|
|
4183
|
+
mediaSettings: {},
|
|
4184
|
+
});
|
|
4185
|
+
});
|
|
4186
|
+
|
|
4187
|
+
afterEach(() => {
|
|
4188
|
+
meeting.config.stats.enableStatsAnalyzer = prevConfigValue;
|
|
4189
|
+
sinon.restore();
|
|
4190
|
+
});
|
|
4191
|
+
|
|
4192
|
+
describe('INBOUND_AUDIO_ISSUE event', () => {
|
|
4193
|
+
it('should not trigger event when no unmuted members exist', () => {
|
|
4194
|
+
const fakeEventData = {issueSubType: 'DECODE_RESULTS_IN_ZERO_AUDIO_LEVEL'};
|
|
4195
|
+
|
|
4196
|
+
// Setup members that are either self or muted
|
|
4197
|
+
const mutedMember = {
|
|
4198
|
+
isSelf: false,
|
|
4199
|
+
isPairedWithSelf: false,
|
|
4200
|
+
isAudioMuted: true,
|
|
4201
|
+
};
|
|
4202
|
+
const selfMember = {
|
|
4203
|
+
isSelf: true,
|
|
4204
|
+
isPairedWithSelf: false,
|
|
4205
|
+
isAudioMuted: false,
|
|
4206
|
+
};
|
|
4207
|
+
const pairedMember = {
|
|
4208
|
+
isSelf: false,
|
|
4209
|
+
isPairedWithSelf: true,
|
|
4210
|
+
isAudioMuted: false,
|
|
4211
|
+
};
|
|
4212
|
+
meeting.members.membersCollection.getAll = sinon.stub().returns({
|
|
4213
|
+
member1: mutedMember,
|
|
4214
|
+
member2: selfMember,
|
|
4215
|
+
member3: pairedMember,
|
|
4216
|
+
});
|
|
4217
|
+
|
|
4218
|
+
// Reset the stub to clear any previous calls
|
|
4219
|
+
TriggerProxy.trigger.resetHistory();
|
|
4220
|
+
|
|
4221
|
+
// Emit the event from statsMonitor
|
|
4222
|
+
listeners[StatsMonitorEventNames.INBOUND_AUDIO_ISSUE](fakeEventData);
|
|
4223
|
+
|
|
4224
|
+
assert.neverCalledWith(
|
|
4225
|
+
TriggerProxy.trigger,
|
|
4226
|
+
meeting,
|
|
4227
|
+
sinon.match.object,
|
|
4228
|
+
EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
|
|
4229
|
+
fakeEventData
|
|
4230
|
+
);
|
|
4231
|
+
assert.notCalled(meeting.mediaProperties.sendMediaIssueMetric);
|
|
4232
|
+
});
|
|
4233
|
+
|
|
4234
|
+
it('should trigger event and metric when there are multiple members and at least one is unmuted', () => {
|
|
4235
|
+
const fakeEventData = {issueSubType: 'DECODE_RESULTS_IN_ZERO_AUDIO_LEVEL'};
|
|
4236
|
+
|
|
4237
|
+
// Setup mixed members - some muted, one unmuted
|
|
4238
|
+
const mutedMember = {
|
|
4239
|
+
isSelf: false,
|
|
4240
|
+
isPairedWithSelf: false,
|
|
4241
|
+
isAudioMuted: true,
|
|
4242
|
+
};
|
|
4243
|
+
const unmutedMember = {
|
|
4244
|
+
isSelf: false,
|
|
4245
|
+
isPairedWithSelf: false,
|
|
4246
|
+
isAudioMuted: false,
|
|
4247
|
+
};
|
|
4248
|
+
const selfMember = {
|
|
4249
|
+
isSelf: true,
|
|
4250
|
+
isPairedWithSelf: false,
|
|
4251
|
+
isAudioMuted: false,
|
|
4252
|
+
};
|
|
4253
|
+
meeting.members.membersCollection.getAll = sinon.stub().returns({
|
|
4254
|
+
member1: mutedMember,
|
|
4255
|
+
member2: unmutedMember,
|
|
4256
|
+
member3: selfMember,
|
|
4257
|
+
});
|
|
4258
|
+
|
|
4259
|
+
// Reset the stub to clear any previous calls
|
|
4260
|
+
TriggerProxy.trigger.resetHistory();
|
|
4261
|
+
|
|
4262
|
+
// Emit the event from statsMonitor
|
|
4263
|
+
listeners[StatsMonitorEventNames.INBOUND_AUDIO_ISSUE](fakeEventData);
|
|
4264
|
+
|
|
4265
|
+
assert.calledWith(
|
|
4266
|
+
TriggerProxy.trigger,
|
|
4267
|
+
meeting,
|
|
4268
|
+
sinon.match.object,
|
|
4269
|
+
EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
|
|
4270
|
+
fakeEventData
|
|
4271
|
+
);
|
|
4272
|
+
|
|
4273
|
+
assert.calledOnceWithExactly(
|
|
4274
|
+
meeting.mediaProperties.sendMediaIssueMetric,
|
|
4275
|
+
'inbound_audio',
|
|
4276
|
+
fakeEventData.issueSubType,
|
|
4277
|
+
meeting.correlationId
|
|
4278
|
+
);
|
|
4279
|
+
});
|
|
4280
|
+
});
|
|
4281
|
+
});
|
|
4282
|
+
|
|
4060
4283
|
describe('bundlePolicy', () => {
|
|
4061
4284
|
const FAKE_TURN_URL = 'turns:webex.com:3478';
|
|
4062
4285
|
const FAKE_TURN_USER = 'some-turn-username';
|
|
@@ -5523,6 +5746,7 @@ describe('plugin-meetings', () => {
|
|
|
5523
5746
|
let multistreamEventListeners;
|
|
5524
5747
|
let transcodedEventListeners;
|
|
5525
5748
|
let mockStatsAnalyzerCtor;
|
|
5749
|
+
let statsMonitorStub;
|
|
5526
5750
|
|
|
5527
5751
|
const setupFakeRoapMediaConnection = (fakeRoapMediaConnection, eventListeners) => {
|
|
5528
5752
|
fakeRoapMediaConnection.on.callsFake((eventName, cb) => {
|
|
@@ -5554,6 +5778,14 @@ describe('plugin-meetings', () => {
|
|
|
5554
5778
|
return {on: sinon.stub(), stopAnalyzer: sinon.stub()};
|
|
5555
5779
|
});
|
|
5556
5780
|
|
|
5781
|
+
statsMonitorStub = {
|
|
5782
|
+
on: sinon.stub(),
|
|
5783
|
+
removeAllListeners: sinon.stub(),
|
|
5784
|
+
};
|
|
5785
|
+
|
|
5786
|
+
// mock the StatsMonitor constructor
|
|
5787
|
+
sinon.stub(InternalMediaCoreModule, 'StatsMonitor').returns(statsMonitorStub);
|
|
5788
|
+
|
|
5557
5789
|
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
5558
5790
|
sinon.stub();
|
|
5559
5791
|
|
|
@@ -5616,6 +5848,7 @@ describe('plugin-meetings', () => {
|
|
|
5616
5848
|
mockStatsAnalyzerCtor,
|
|
5617
5849
|
sinon.match({
|
|
5618
5850
|
isMultistream: true,
|
|
5851
|
+
statsMonitor: statsMonitorStub,
|
|
5619
5852
|
})
|
|
5620
5853
|
);
|
|
5621
5854
|
const initialStatsAnalyzer = mockStatsAnalyzerCtor.returnValues[0];
|
|
@@ -6498,25 +6731,36 @@ describe('plugin-meetings', () => {
|
|
|
6498
6731
|
const DIAL_IN_URL = meeting.dialInUrl;
|
|
6499
6732
|
|
|
6500
6733
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
|
6501
|
-
correlationId: meeting.
|
|
6734
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6502
6735
|
dialInUrl: DIAL_IN_URL,
|
|
6503
6736
|
locusUrl: meeting.locusUrl,
|
|
6504
6737
|
clientUrl: meeting.deviceUrl,
|
|
6505
6738
|
});
|
|
6506
6739
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
6507
6740
|
|
|
6741
|
+
// Verify pstnCorrelationId was set
|
|
6742
|
+
assert.exists(meeting.pstnCorrelationId);
|
|
6743
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
|
6744
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId;
|
|
6745
|
+
|
|
6508
6746
|
meeting.meetingRequest.dialIn.resetHistory();
|
|
6509
6747
|
|
|
6510
6748
|
// try again. the dial in urls should match
|
|
6511
6749
|
await meeting.usePhoneAudio();
|
|
6512
6750
|
|
|
6513
6751
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
|
6514
|
-
correlationId: meeting.
|
|
6752
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6515
6753
|
dialInUrl: DIAL_IN_URL,
|
|
6516
6754
|
locusUrl: meeting.locusUrl,
|
|
6517
6755
|
clientUrl: meeting.deviceUrl,
|
|
6518
6756
|
});
|
|
6519
6757
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
6758
|
+
// A new PSTN correlationId should be generated for the second attempt
|
|
6759
|
+
assert.notEqual(
|
|
6760
|
+
meeting.pstnCorrelationId,
|
|
6761
|
+
firstPstnCorrelationId,
|
|
6762
|
+
'pstnCorrelationId should be regenerated on each dial-in attempt'
|
|
6763
|
+
);
|
|
6520
6764
|
});
|
|
6521
6765
|
|
|
6522
6766
|
it('given a phone number, triggers dial-out, delegating request to meetingRequest correctly', async () => {
|
|
@@ -6526,7 +6770,7 @@ describe('plugin-meetings', () => {
|
|
|
6526
6770
|
const DIAL_OUT_URL = meeting.dialOutUrl;
|
|
6527
6771
|
|
|
6528
6772
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
|
6529
|
-
correlationId: meeting.
|
|
6773
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6530
6774
|
dialOutUrl: DIAL_OUT_URL,
|
|
6531
6775
|
locusUrl: meeting.locusUrl,
|
|
6532
6776
|
clientUrl: meeting.deviceUrl,
|
|
@@ -6534,49 +6778,134 @@ describe('plugin-meetings', () => {
|
|
|
6534
6778
|
});
|
|
6535
6779
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
6536
6780
|
|
|
6781
|
+
// Verify pstnCorrelationId was set
|
|
6782
|
+
assert.exists(meeting.pstnCorrelationId);
|
|
6783
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
|
6784
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId;
|
|
6785
|
+
|
|
6537
6786
|
meeting.meetingRequest.dialOut.resetHistory();
|
|
6538
6787
|
|
|
6539
6788
|
// try again. the dial out urls should match
|
|
6540
6789
|
await meeting.usePhoneAudio(phoneNumber);
|
|
6541
6790
|
|
|
6542
6791
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
|
6543
|
-
correlationId: meeting.
|
|
6792
|
+
correlationId: meeting.pstnCorrelationId,
|
|
6544
6793
|
dialOutUrl: DIAL_OUT_URL,
|
|
6545
6794
|
locusUrl: meeting.locusUrl,
|
|
6546
6795
|
clientUrl: meeting.deviceUrl,
|
|
6547
6796
|
phoneNumber,
|
|
6548
6797
|
});
|
|
6549
6798
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
6799
|
+
// A new PSTN correlationId should be generated for the second attempt
|
|
6800
|
+
assert.notEqual(
|
|
6801
|
+
meeting.pstnCorrelationId,
|
|
6802
|
+
firstPstnCorrelationId,
|
|
6803
|
+
'pstnCorrelationId should be regenerated on each dial-out attempt'
|
|
6804
|
+
);
|
|
6550
6805
|
});
|
|
6551
6806
|
|
|
6552
|
-
it('rejects if the request failed (dial in)', () => {
|
|
6553
|
-
const error = '
|
|
6807
|
+
it('rejects if the request failed (dial in)', async () => {
|
|
6808
|
+
const error = {error: {message: 'dial in failed'}, stack: 'error stack'};
|
|
6554
6809
|
|
|
6555
6810
|
meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
|
|
6556
6811
|
|
|
6557
|
-
|
|
6558
|
-
.usePhoneAudio()
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6812
|
+
try {
|
|
6813
|
+
await meeting.usePhoneAudio();
|
|
6814
|
+
throw new Error('Promise resolved when it should have rejected');
|
|
6815
|
+
} catch (e) {
|
|
6816
|
+
assert.equal(e, error);
|
|
6562
6817
|
|
|
6563
|
-
|
|
6564
|
-
|
|
6818
|
+
// Verify behavioral metric was sent with dial_in_correlation_id
|
|
6819
|
+
assert.calledWith(
|
|
6820
|
+
Metrics.sendBehavioralMetric,
|
|
6821
|
+
BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE,
|
|
6822
|
+
{
|
|
6823
|
+
correlation_id: meeting.correlationId,
|
|
6824
|
+
dial_in_url: meeting.dialInUrl,
|
|
6825
|
+
dial_in_correlation_id: sinon.match.string,
|
|
6826
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
6827
|
+
client_url: meeting.deviceUrl,
|
|
6828
|
+
reason: error.error.message,
|
|
6829
|
+
stack: error.stack,
|
|
6830
|
+
}
|
|
6831
|
+
);
|
|
6832
|
+
|
|
6833
|
+
// Verify pstnCorrelationId was cleared after error
|
|
6834
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6835
|
+
}
|
|
6565
6836
|
});
|
|
6566
6837
|
|
|
6567
6838
|
it('rejects if the request failed (dial out)', async () => {
|
|
6568
|
-
const error = '
|
|
6839
|
+
const error = {error: {message: 'dial out failed'}, stack: 'error stack'};
|
|
6569
6840
|
|
|
6570
6841
|
meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
|
|
6571
6842
|
|
|
6572
|
-
|
|
6573
|
-
.usePhoneAudio('+441234567890')
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6843
|
+
try {
|
|
6844
|
+
await meeting.usePhoneAudio('+441234567890');
|
|
6845
|
+
throw new Error('Promise resolved when it should have rejected');
|
|
6846
|
+
} catch (e) {
|
|
6847
|
+
assert.equal(e, error);
|
|
6577
6848
|
|
|
6578
|
-
|
|
6579
|
-
|
|
6849
|
+
// Verify behavioral metric was sent with dial_out_correlation_id
|
|
6850
|
+
assert.calledWith(
|
|
6851
|
+
Metrics.sendBehavioralMetric,
|
|
6852
|
+
BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE,
|
|
6853
|
+
{
|
|
6854
|
+
correlation_id: meeting.correlationId,
|
|
6855
|
+
dial_out_url: meeting.dialOutUrl,
|
|
6856
|
+
dial_out_correlation_id: sinon.match.string,
|
|
6857
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
6858
|
+
client_url: meeting.deviceUrl,
|
|
6859
|
+
reason: error.error.message,
|
|
6860
|
+
stack: error.stack,
|
|
6861
|
+
}
|
|
6862
|
+
);
|
|
6863
|
+
|
|
6864
|
+
// Verify pstnCorrelationId was cleared after error
|
|
6865
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6866
|
+
}
|
|
6867
|
+
});
|
|
6868
|
+
});
|
|
6869
|
+
|
|
6870
|
+
describe('#disconnectPhoneAudio', () => {
|
|
6871
|
+
beforeEach(() => {
|
|
6872
|
+
// Mock the MeetingUtil.disconnectPhoneAudio method
|
|
6873
|
+
sinon.stub(MeetingUtil, 'disconnectPhoneAudio').resolves();
|
|
6874
|
+
meeting.dialInUrl = 'dialin:///test-dial-in-url';
|
|
6875
|
+
meeting.dialOutUrl = 'dialout:///test-dial-out-url';
|
|
6876
|
+
meeting.dialInDeviceStatus = 'JOINED';
|
|
6877
|
+
meeting.dialOutDeviceStatus = 'JOINED';
|
|
6878
|
+
});
|
|
6879
|
+
|
|
6880
|
+
afterEach(() => {
|
|
6881
|
+
MeetingUtil.disconnectPhoneAudio.restore();
|
|
6882
|
+
});
|
|
6883
|
+
|
|
6884
|
+
it('should disconnect phone audio and clear pstnCorrelationId', async () => {
|
|
6885
|
+
meeting.pstnCorrelationId = 'test-pstn-correlation-id';
|
|
6886
|
+
|
|
6887
|
+
await meeting.disconnectPhoneAudio();
|
|
6888
|
+
|
|
6889
|
+
// Verify that pstnCorrelationId is cleared
|
|
6890
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6891
|
+
|
|
6892
|
+
// Verify that MeetingUtil.disconnectPhoneAudio was called for both dial-in and dial-out
|
|
6893
|
+
assert.calledTwice(MeetingUtil.disconnectPhoneAudio);
|
|
6894
|
+
assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialInUrl);
|
|
6895
|
+
assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialOutUrl);
|
|
6896
|
+
});
|
|
6897
|
+
|
|
6898
|
+
it('should handle case when no PSTN connection is active', async () => {
|
|
6899
|
+
meeting.dialInDeviceStatus = 'IDLE';
|
|
6900
|
+
meeting.dialOutDeviceStatus = 'IDLE';
|
|
6901
|
+
meeting.pstnCorrelationId = 'test-pstn-correlation-id';
|
|
6902
|
+
|
|
6903
|
+
await meeting.disconnectPhoneAudio();
|
|
6904
|
+
|
|
6905
|
+
// Verify that pstnCorrelationId is still cleared even when no phone connection is active
|
|
6906
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
|
6907
|
+
// And verify no disconnect was attempted
|
|
6908
|
+
assert.notCalled(MeetingUtil.disconnectPhoneAudio);
|
|
6580
6909
|
});
|
|
6581
6910
|
});
|
|
6582
6911
|
|
|
@@ -7330,6 +7659,8 @@ describe('plugin-meetings', () => {
|
|
|
7330
7659
|
'locus-id',
|
|
7331
7660
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
|
7332
7661
|
{meetingId: meeting.id, sendCAevents: true},
|
|
7662
|
+
null,
|
|
7663
|
+
null,
|
|
7333
7664
|
null
|
|
7334
7665
|
);
|
|
7335
7666
|
assert.deepEqual(meeting.meetingInfo, {
|
|
@@ -7376,6 +7707,8 @@ describe('plugin-meetings', () => {
|
|
|
7376
7707
|
'locus-id',
|
|
7377
7708
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
|
7378
7709
|
{meetingId: meeting.id, sendCAevents: true},
|
|
7710
|
+
null,
|
|
7711
|
+
null,
|
|
7379
7712
|
null
|
|
7380
7713
|
);
|
|
7381
7714
|
assert.deepEqual(meeting.meetingInfo, {
|
|
@@ -7431,6 +7764,8 @@ describe('plugin-meetings', () => {
|
|
|
7431
7764
|
permissionToken: FAKE_PERMISSION_TOKEN,
|
|
7432
7765
|
},
|
|
7433
7766
|
{meetingId: meeting.id, sendCAevents: true},
|
|
7767
|
+
null,
|
|
7768
|
+
null,
|
|
7434
7769
|
null
|
|
7435
7770
|
);
|
|
7436
7771
|
assert.deepEqual(meeting.meetingInfo, {
|
|
@@ -8093,6 +8428,7 @@ describe('plugin-meetings', () => {
|
|
|
8093
8428
|
|
|
8094
8429
|
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
|
8095
8430
|
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
|
8431
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
8096
8432
|
meeting.mediaProperties.mediaDirection = {
|
|
8097
8433
|
sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
|
|
8098
8434
|
sendVideo: 'fake value',
|
|
@@ -8174,6 +8510,12 @@ describe('plugin-meetings', () => {
|
|
|
8174
8510
|
payload: {mediaType: 'share', shareInstanceId: meeting.localShareInstanceId},
|
|
8175
8511
|
options: {meetingId: meeting.id},
|
|
8176
8512
|
});
|
|
8513
|
+
|
|
8514
|
+
// ensure the share start timestamp is saved
|
|
8515
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
8516
|
+
key: 'internal.client.share.initiated',
|
|
8517
|
+
});
|
|
8518
|
+
|
|
8177
8519
|
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
|
8178
8520
|
|
|
8179
8521
|
assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
|
|
@@ -8192,6 +8534,11 @@ describe('plugin-meetings', () => {
|
|
|
8192
8534
|
options: {meetingId: meeting.id},
|
|
8193
8535
|
});
|
|
8194
8536
|
|
|
8537
|
+
// ensure the share start timestamp is saved
|
|
8538
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
8539
|
+
key: 'internal.client.share.initiated',
|
|
8540
|
+
});
|
|
8541
|
+
|
|
8195
8542
|
assert.calledWith(
|
|
8196
8543
|
meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
|
|
8197
8544
|
stream
|
|
@@ -8860,11 +9207,16 @@ describe('plugin-meetings', () => {
|
|
|
8860
9207
|
meeting.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
8861
9208
|
meeting.setupMediaConnectionListeners();
|
|
8862
9209
|
|
|
9210
|
+
sinon.stub(MeetingUtil, 'getCaEventLabelsForIpVersion').returns(['fake labels']);
|
|
9211
|
+
|
|
8863
9212
|
simulateConnectionStateChange(ConnectionState.Connecting);
|
|
8864
9213
|
|
|
8865
9214
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
8866
9215
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8867
9216
|
name: 'client.ice.start',
|
|
9217
|
+
payload: {
|
|
9218
|
+
labels: ['fake labels'],
|
|
9219
|
+
},
|
|
8868
9220
|
options: {
|
|
8869
9221
|
meetingId: meeting.id,
|
|
8870
9222
|
},
|
|
@@ -10129,6 +10481,24 @@ describe('plugin-meetings', () => {
|
|
|
10129
10481
|
);
|
|
10130
10482
|
});
|
|
10131
10483
|
|
|
10484
|
+
it('listens to CONTROLS_AUTO_END_MEETING_WARNING_CHANGED', async () => {
|
|
10485
|
+
const state = {example: 'value'};
|
|
10486
|
+
|
|
10487
|
+
await meeting.locusInfo.emitScoped(
|
|
10488
|
+
{function: 'test', file: 'test'},
|
|
10489
|
+
LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED,
|
|
10490
|
+
{state}
|
|
10491
|
+
);
|
|
10492
|
+
|
|
10493
|
+
assert.calledWith(
|
|
10494
|
+
TriggerProxy.trigger,
|
|
10495
|
+
meeting,
|
|
10496
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
10497
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_AUTO_END_MEETING_WARNING_UPDATED,
|
|
10498
|
+
{state}
|
|
10499
|
+
);
|
|
10500
|
+
});
|
|
10501
|
+
|
|
10132
10502
|
it('listens to CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED', async () => {
|
|
10133
10503
|
const state = {example: 'value'};
|
|
10134
10504
|
|
|
@@ -10208,6 +10578,7 @@ describe('plugin-meetings', () => {
|
|
|
10208
10578
|
describe('#setUpLocusUrlListener', () => {
|
|
10209
10579
|
it('listens to the locus url update event', (done) => {
|
|
10210
10580
|
const newLocusUrl = 'newLocusUrl/12345';
|
|
10581
|
+
const payload = {url: newLocusUrl};
|
|
10211
10582
|
|
|
10212
10583
|
meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
|
|
10213
10584
|
meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
@@ -10221,14 +10592,14 @@ describe('plugin-meetings', () => {
|
|
|
10221
10592
|
meeting.locusInfo.emit(
|
|
10222
10593
|
{function: 'test', file: 'test'},
|
|
10223
10594
|
'LOCUS_INFO_UPDATE_URL',
|
|
10224
|
-
|
|
10595
|
+
payload
|
|
10225
10596
|
);
|
|
10226
10597
|
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
10227
10598
|
assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
|
|
10228
10599
|
assert.calledOnceWithExactly(meeting.annotation.locusUrlUpdate, newLocusUrl);
|
|
10229
10600
|
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
10230
10601
|
assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
|
|
10231
|
-
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
|
|
10602
|
+
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, false);
|
|
10232
10603
|
assert.calledWith(meeting.simultaneousInterpretation.locusUrlUpdate, newLocusUrl);
|
|
10233
10604
|
assert.calledWith(meeting.webinar.locusUrlUpdate, newLocusUrl);
|
|
10234
10605
|
assert.equal(meeting.locusUrl, newLocusUrl);
|
|
@@ -10246,6 +10617,22 @@ describe('plugin-meetings', () => {
|
|
|
10246
10617
|
{locusUrl: 'newLocusUrl/12345'}
|
|
10247
10618
|
);
|
|
10248
10619
|
|
|
10620
|
+
done();
|
|
10621
|
+
});
|
|
10622
|
+
it('update mainLocusUrl for controlsOptionManager if payload.isMainLocus as true', (done) => {
|
|
10623
|
+
const newLocusUrl = 'newLocusUrl/12345';
|
|
10624
|
+
const payload = {url: newLocusUrl, isMainLocus: true};
|
|
10625
|
+
|
|
10626
|
+
meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
10627
|
+
|
|
10628
|
+
meeting.locusInfo.emit(
|
|
10629
|
+
{function: 'test', file: 'test'},
|
|
10630
|
+
'LOCUS_INFO_UPDATE_URL',
|
|
10631
|
+
payload
|
|
10632
|
+
);
|
|
10633
|
+
|
|
10634
|
+
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, true);
|
|
10635
|
+
|
|
10249
10636
|
done();
|
|
10250
10637
|
});
|
|
10251
10638
|
});
|
|
@@ -10465,6 +10852,10 @@ describe('plugin-meetings', () => {
|
|
|
10465
10852
|
meeting.mediaProperties = {mediaDirection: {sendShare: true}};
|
|
10466
10853
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
10467
10854
|
(meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
|
|
10855
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
10856
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon
|
|
10857
|
+
.stub()
|
|
10858
|
+
.returns(1000);
|
|
10468
10859
|
});
|
|
10469
10860
|
it('should call changeMeetingFloor()', async () => {
|
|
10470
10861
|
meeting.screenShareFloorState = 'GRANTED';
|
|
@@ -10482,6 +10873,22 @@ describe('plugin-meetings', () => {
|
|
|
10482
10873
|
assert.exists(share.then);
|
|
10483
10874
|
await share;
|
|
10484
10875
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
10876
|
+
|
|
10877
|
+
// ensure the share stop timestamp is saved
|
|
10878
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
10879
|
+
key: 'internal.client.share.stopped',
|
|
10880
|
+
});
|
|
10881
|
+
|
|
10882
|
+
// ensure the CA share stopped metric is submitted with duration
|
|
10883
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
10884
|
+
name: 'client.share.stopped',
|
|
10885
|
+
payload: {
|
|
10886
|
+
mediaType: 'share',
|
|
10887
|
+
shareInstanceId: meeting.localShareInstanceId,
|
|
10888
|
+
shareDuration: 1000,
|
|
10889
|
+
},
|
|
10890
|
+
options: {meetingId: meeting.id},
|
|
10891
|
+
});
|
|
10485
10892
|
});
|
|
10486
10893
|
it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
|
|
10487
10894
|
// change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
|
|
@@ -11065,6 +11472,8 @@ describe('plugin-meetings', () => {
|
|
|
11065
11472
|
let canUserRenameOthersSpy;
|
|
11066
11473
|
let canShareWhiteBoardSpy;
|
|
11067
11474
|
let canMoveToLobbySpy;
|
|
11475
|
+
let isSpokenLanguageAutoDetectionEnabledSpy;
|
|
11476
|
+
let showAutoEndMeetingWarningSpy;
|
|
11068
11477
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
11069
11478
|
|
|
11070
11479
|
beforeEach(() => {
|
|
@@ -11096,11 +11505,17 @@ describe('plugin-meetings', () => {
|
|
|
11096
11505
|
canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
|
|
11097
11506
|
canShareWhiteBoardSpy = sinon.spy(MeetingUtil, 'canShareWhiteBoard');
|
|
11098
11507
|
canMoveToLobbySpy = sinon.spy(MeetingUtil, 'canMoveToLobby');
|
|
11508
|
+
showAutoEndMeetingWarningSpy = sinon.spy(MeetingUtil, 'showAutoEndMeetingWarning');
|
|
11509
|
+
isSpokenLanguageAutoDetectionEnabledSpy = sinon.spy(
|
|
11510
|
+
MeetingUtil,
|
|
11511
|
+
'isSpokenLanguageAutoDetectionEnabled'
|
|
11512
|
+
);
|
|
11099
11513
|
});
|
|
11100
11514
|
|
|
11101
11515
|
afterEach(() => {
|
|
11102
11516
|
inMeetingActionsSetSpy.restore();
|
|
11103
11517
|
waitingForOthersToJoinSpy.restore();
|
|
11518
|
+
showAutoEndMeetingWarningSpy.restore();
|
|
11104
11519
|
});
|
|
11105
11520
|
|
|
11106
11521
|
forEach(
|
|
@@ -11648,6 +12063,8 @@ describe('plugin-meetings', () => {
|
|
|
11648
12063
|
assert.calledWith(canUserRenameOthersSpy, userDisplayHints);
|
|
11649
12064
|
assert.calledWith(canShareWhiteBoardSpy, userDisplayHints, selfUserPolicies);
|
|
11650
12065
|
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
|
12066
|
+
assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
|
|
12067
|
+
assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
|
|
11651
12068
|
|
|
11652
12069
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
11653
12070
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
@@ -12054,6 +12471,7 @@ describe('plugin-meetings', () => {
|
|
|
12054
12471
|
meeting.locusInfo.self = {url: url1};
|
|
12055
12472
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
12056
12473
|
meeting.deviceUrl = 'deviceUrl.com';
|
|
12474
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
12057
12475
|
});
|
|
12058
12476
|
it('should have #startWhiteboardShare', () => {
|
|
12059
12477
|
assert.exists(meeting.startWhiteboardShare);
|
|
@@ -12081,6 +12499,11 @@ describe('plugin-meetings', () => {
|
|
|
12081
12499
|
payload: {mediaType: 'whiteboard'},
|
|
12082
12500
|
options: {meetingId: meeting.id},
|
|
12083
12501
|
});
|
|
12502
|
+
|
|
12503
|
+
// ensure the share start timestamp is saved
|
|
12504
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
12505
|
+
key: 'internal.client.share.initiated',
|
|
12506
|
+
});
|
|
12084
12507
|
});
|
|
12085
12508
|
});
|
|
12086
12509
|
describe('#stopWhiteboardShare', () => {
|
|
@@ -12092,6 +12515,11 @@ describe('plugin-meetings', () => {
|
|
|
12092
12515
|
meeting.locusInfo.self = {url: url1};
|
|
12093
12516
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
12094
12517
|
meeting.deviceUrl = 'deviceUrl.com';
|
|
12518
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
12519
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon
|
|
12520
|
+
.stub()
|
|
12521
|
+
.returns(1000);
|
|
12522
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
|
12095
12523
|
});
|
|
12096
12524
|
it('should stop the whiteboard share', async () => {
|
|
12097
12525
|
const whiteboardShare = meeting.stopWhiteboardShare();
|
|
@@ -12106,6 +12534,21 @@ describe('plugin-meetings', () => {
|
|
|
12106
12534
|
uri: url1,
|
|
12107
12535
|
});
|
|
12108
12536
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
12537
|
+
|
|
12538
|
+
// ensure the share stop timestamp is saved
|
|
12539
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
12540
|
+
key: 'internal.client.share.stopped',
|
|
12541
|
+
});
|
|
12542
|
+
|
|
12543
|
+
// ensure the CA share stopped metric is submitted with duration
|
|
12544
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
12545
|
+
name: 'client.share.stopped',
|
|
12546
|
+
payload: {
|
|
12547
|
+
mediaType: 'whiteboard',
|
|
12548
|
+
shareDuration: 1000,
|
|
12549
|
+
},
|
|
12550
|
+
options: {meetingId: meeting.id},
|
|
12551
|
+
});
|
|
12109
12552
|
});
|
|
12110
12553
|
});
|
|
12111
12554
|
});
|
|
@@ -12178,6 +12621,11 @@ describe('plugin-meetings', () => {
|
|
|
12178
12621
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
12179
12622
|
meeting.deviceUrl = 'my-web-url';
|
|
12180
12623
|
meeting.locusInfo.info = {isWebinar: false};
|
|
12624
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
12625
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon
|
|
12626
|
+
.stub()
|
|
12627
|
+
.returns(1500);
|
|
12628
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
|
12181
12629
|
});
|
|
12182
12630
|
|
|
12183
12631
|
const USER_IDS = {
|
|
@@ -12404,12 +12852,12 @@ describe('plugin-meetings', () => {
|
|
|
12404
12852
|
activeSharingId.whiteboard = beneficiaryId;
|
|
12405
12853
|
|
|
12406
12854
|
eventTrigger.share.push(
|
|
12407
|
-
meeting.webinar.selfIsAttendee
|
|
12855
|
+
meeting.webinar.selfIsAttendee || meeting.guest
|
|
12408
12856
|
? {
|
|
12409
12857
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
12410
12858
|
functionName: 'remoteShare',
|
|
12411
12859
|
eventPayload: {
|
|
12412
|
-
memberId: null,
|
|
12860
|
+
memberId: meeting.webinar.selfIsAttendee ? beneficiaryId : null,
|
|
12413
12861
|
url,
|
|
12414
12862
|
shareInstanceId,
|
|
12415
12863
|
annotationInfo: undefined,
|
|
@@ -12423,9 +12871,10 @@ describe('plugin-meetings', () => {
|
|
|
12423
12871
|
}
|
|
12424
12872
|
);
|
|
12425
12873
|
|
|
12426
|
-
shareStatus =
|
|
12427
|
-
|
|
12428
|
-
|
|
12874
|
+
shareStatus =
|
|
12875
|
+
meeting.webinar.selfIsAttendee || meeting.guest
|
|
12876
|
+
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
|
12877
|
+
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
12429
12878
|
}
|
|
12430
12879
|
|
|
12431
12880
|
if (eventTrigger.member) {
|
|
@@ -12463,7 +12912,7 @@ describe('plugin-meetings', () => {
|
|
|
12463
12912
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
12464
12913
|
functionName: 'remoteShare',
|
|
12465
12914
|
eventPayload: {
|
|
12466
|
-
memberId:
|
|
12915
|
+
memberId: beneficiaryId,
|
|
12467
12916
|
url,
|
|
12468
12917
|
shareInstanceId,
|
|
12469
12918
|
annotationInfo: undefined,
|
|
@@ -12641,6 +13090,36 @@ describe('plugin-meetings', () => {
|
|
|
12641
13090
|
});
|
|
12642
13091
|
});
|
|
12643
13092
|
|
|
13093
|
+
describe('Whiteboard Share - User is guest', () => {
|
|
13094
|
+
it('User receives a remote share instead of whiteboard share', () => {
|
|
13095
|
+
// Set the guest flag
|
|
13096
|
+
meeting.guest = true;
|
|
13097
|
+
|
|
13098
|
+
// Step 1: Start sharing whiteboard A
|
|
13099
|
+
const data1 = generateData(
|
|
13100
|
+
blankPayload, // Initial payload
|
|
13101
|
+
true, // isGranting: Granting share
|
|
13102
|
+
false, // isContent: Whiteboard (not content)
|
|
13103
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
|
13104
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
|
13105
|
+
);
|
|
13106
|
+
|
|
13107
|
+
// Step 2: Stop sharing whiteboard A
|
|
13108
|
+
const data2 = generateData(
|
|
13109
|
+
data1.payload, // Updated payload from Step 1
|
|
13110
|
+
false, // isGranting: Stopping share
|
|
13111
|
+
false, // isContent: Whiteboard
|
|
13112
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
|
13113
|
+
);
|
|
13114
|
+
|
|
13115
|
+
// Validate the payload changes and status updates
|
|
13116
|
+
payloadTestHelper([data1]);
|
|
13117
|
+
|
|
13118
|
+
// Specific assertions for guest
|
|
13119
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
|
13120
|
+
});
|
|
13121
|
+
});
|
|
13122
|
+
|
|
12644
13123
|
describe('Whiteboard A --> Whiteboard B', () => {
|
|
12645
13124
|
it('Scenario #1: you share both whiteboards', () => {
|
|
12646
13125
|
const data1 = generateData(
|
|
@@ -13292,30 +13771,78 @@ describe('plugin-meetings', () => {
|
|
|
13292
13771
|
payloadTestHelper([data1, data2, data3]);
|
|
13293
13772
|
});
|
|
13294
13773
|
});
|
|
13295
|
-
});
|
|
13296
13774
|
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13775
|
+
it('should send share stopped metric when whiteboard sharing stops', () => {
|
|
13776
|
+
// Start whiteboard sharing (this won't trigger metrics)
|
|
13777
|
+
const data1 = generateData(
|
|
13778
|
+
blankPayload,
|
|
13779
|
+
true, // isGranting: true
|
|
13780
|
+
false, // isContent: false (whiteboard)
|
|
13781
|
+
USER_IDS.ME,
|
|
13782
|
+
RESOURCE_URLS.WHITEBOARD_A
|
|
13783
|
+
);
|
|
13304
13784
|
|
|
13305
|
-
|
|
13785
|
+
// Stop whiteboard sharing (this should trigger metrics)
|
|
13786
|
+
const data2 = generateData(
|
|
13787
|
+
data1.payload,
|
|
13788
|
+
false, // isGranting: false (stopping share)
|
|
13789
|
+
false, // isContent: false (whiteboard)
|
|
13790
|
+
USER_IDS.ME
|
|
13791
|
+
);
|
|
13306
13792
|
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13793
|
+
// Trigger the events
|
|
13794
|
+
meeting.locusInfo.emit(
|
|
13795
|
+
{function: 'test', file: 'test'},
|
|
13796
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
13797
|
+
data1.payload
|
|
13798
|
+
);
|
|
13799
|
+
|
|
13800
|
+
meeting.locusInfo.emit(
|
|
13801
|
+
{function: 'test', file: 'test'},
|
|
13802
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
13803
|
+
data2.payload
|
|
13318
13804
|
);
|
|
13805
|
+
|
|
13806
|
+
// Verify metrics were called when whiteboard sharing stopped
|
|
13807
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
|
13808
|
+
key: 'internal.client.share.stopped',
|
|
13809
|
+
});
|
|
13810
|
+
|
|
13811
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
|
13812
|
+
name: 'client.share.stopped',
|
|
13813
|
+
payload: {
|
|
13814
|
+
mediaType: 'whiteboard',
|
|
13815
|
+
shareDuration: 1500, // mocked return value
|
|
13816
|
+
},
|
|
13817
|
+
options: {
|
|
13818
|
+
meetingId: meeting.id,
|
|
13819
|
+
},
|
|
13820
|
+
});
|
|
13821
|
+
});
|
|
13822
|
+
|
|
13823
|
+
describe('handleShareVideoStreamMuteStateChange', () => {
|
|
13824
|
+
it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
|
|
13825
|
+
meeting.isMultistream = true;
|
|
13826
|
+
meeting.statsAnalyzer = {shareVideoEncoderImplementation: 'OpenH264'};
|
|
13827
|
+
meeting.mediaProperties.shareVideoStream = {
|
|
13828
|
+
getSettings: sinon.stub().returns({displaySurface: 'monitor', frameRate: 30}),
|
|
13829
|
+
};
|
|
13830
|
+
|
|
13831
|
+
meeting.handleShareVideoStreamMuteStateChange(true);
|
|
13832
|
+
|
|
13833
|
+
assert.calledOnceWithExactly(
|
|
13834
|
+
Metrics.sendBehavioralMetric,
|
|
13835
|
+
BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE,
|
|
13836
|
+
{
|
|
13837
|
+
correlationId: meeting.correlationId,
|
|
13838
|
+
muted: true,
|
|
13839
|
+
encoderImplementation: 'OpenH264',
|
|
13840
|
+
displaySurface: 'monitor',
|
|
13841
|
+
isMultistream: true,
|
|
13842
|
+
frameRate: 30,
|
|
13843
|
+
}
|
|
13844
|
+
);
|
|
13845
|
+
});
|
|
13319
13846
|
});
|
|
13320
13847
|
});
|
|
13321
13848
|
});
|
|
@@ -14517,11 +15044,81 @@ describe('plugin-meetings', () => {
|
|
|
14517
15044
|
assert.exists(unsetStagePromise.then);
|
|
14518
15045
|
await unsetStagePromise;
|
|
14519
15046
|
|
|
15047
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.synchronizeStage, locusUrl, {
|
|
15048
|
+
overrideDefault: false,
|
|
15049
|
+
});
|
|
15050
|
+
});
|
|
15051
|
+
});
|
|
15052
|
+
|
|
15053
|
+
describe('#notifyHost', () => {
|
|
15054
|
+
beforeEach(() => {
|
|
15055
|
+
meeting.meetingRequest.notifyHost = sinon.stub().returns(Promise.resolve());
|
|
15056
|
+
});
|
|
15057
|
+
|
|
15058
|
+
it('sends the expected request', async () => {
|
|
15059
|
+
meeting.meetingInfo.siteFullUrl = `convergedats.webex.com`;
|
|
15060
|
+
const meetingUuid = 'meeting-uuid';
|
|
15061
|
+
const displayName = ['Test', 'User'];
|
|
15062
|
+
meeting.locusId = 'locusId';
|
|
15063
|
+
|
|
15064
|
+
const notifyHostPromise = meeting.notifyHost(meetingUuid, displayName);
|
|
15065
|
+
|
|
15066
|
+
assert.exists(notifyHostPromise.then);
|
|
15067
|
+
await notifyHostPromise;
|
|
15068
|
+
|
|
14520
15069
|
assert.calledOnceWithExactly(
|
|
14521
|
-
meeting.meetingRequest.
|
|
14522
|
-
|
|
14523
|
-
|
|
15070
|
+
meeting.meetingRequest.notifyHost,
|
|
15071
|
+
meeting.meetingInfo.siteFullUrl,
|
|
15072
|
+
meeting.locusId,
|
|
15073
|
+
meetingUuid,
|
|
15074
|
+
displayName
|
|
15075
|
+
);
|
|
15076
|
+
});
|
|
15077
|
+
});
|
|
15078
|
+
|
|
15079
|
+
describe('#sipCallOut', () => {
|
|
15080
|
+
beforeEach(() => {
|
|
15081
|
+
meeting.meetingRequest.sipCallOut = sinon.stub().returns(Promise.resolve({body: {}}));
|
|
15082
|
+
});
|
|
15083
|
+
|
|
15084
|
+
it('sends the expected request', async () => {
|
|
15085
|
+
const address = 'sip:user@example.com';
|
|
15086
|
+
const displayName = 'John Doe';
|
|
15087
|
+
const meetingId = 'a643beaa47f04eedac08f1310ca12366';
|
|
15088
|
+
|
|
15089
|
+
meeting.meetingInfo = {
|
|
15090
|
+
meetingId,
|
|
15091
|
+
};
|
|
15092
|
+
|
|
15093
|
+
const sipCallOutPromise = meeting.sipCallOut(address, displayName);
|
|
15094
|
+
|
|
15095
|
+
assert.exists(sipCallOutPromise.then);
|
|
15096
|
+
await sipCallOutPromise;
|
|
15097
|
+
|
|
15098
|
+
assert.calledOnceWithExactly(
|
|
15099
|
+
meeting.meetingRequest.sipCallOut,
|
|
15100
|
+
meetingId,
|
|
15101
|
+
meetingId,
|
|
15102
|
+
address,
|
|
15103
|
+
displayName
|
|
14524
15104
|
);
|
|
14525
15105
|
});
|
|
14526
15106
|
});
|
|
15107
|
+
|
|
15108
|
+
describe('#cancelSipCallOut', () => {
|
|
15109
|
+
beforeEach(() => {
|
|
15110
|
+
meeting.meetingRequest.cancelSipCallOut = sinon.stub().returns(Promise.resolve({body: {}}));
|
|
15111
|
+
});
|
|
15112
|
+
|
|
15113
|
+
it('sends the expected request', async () => {
|
|
15114
|
+
const participantId = '12345-abcde';
|
|
15115
|
+
|
|
15116
|
+
const cancelSipCallOutPromise = meeting.cancelSipCallOut(participantId);
|
|
15117
|
+
|
|
15118
|
+
assert.exists(cancelSipCallOutPromise.then);
|
|
15119
|
+
await cancelSipCallOutPromise;
|
|
15120
|
+
|
|
15121
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.cancelSipCallOut, participantId);
|
|
15122
|
+
});
|
|
15123
|
+
});
|
|
14527
15124
|
});
|