@webex/plugin-meetings 3.9.0-webinar5k.1 → 3.9.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 +16 -0
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +40 -328
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +6 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +196 -160
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +5 -2
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/type.js +7 -0
- package/dist/meeting/type.js.map +1 -0
- package/dist/meeting/util.js +79 -10
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +37 -39
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/members/collection.js +0 -13
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +21 -40
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.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/types/constants.d.ts +16 -0
- package/dist/types/locus-info/index.d.ts +3 -102
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +23 -28
- package/dist/types/meeting/type.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +6 -3
- package/dist/types/member/types.d.ts +0 -1
- package/dist/types/members/collection.d.ts +0 -6
- package/dist/types/members/index.d.ts +7 -16
- package/dist/types/members/util.d.ts +2 -1
- 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 +22 -24
- package/src/constants.ts +16 -2
- package/src/locus-info/index.ts +39 -409
- package/src/meeting/in-meeting-actions.ts +13 -0
- package/src/meeting/index.ts +92 -63
- package/src/meeting/muteState.ts +6 -2
- package/src/meeting/type.ts +9 -0
- package/src/meeting/util.ts +93 -19
- package/src/meetings/index.ts +6 -19
- package/src/member/types.ts +0 -1
- package/src/members/collection.ts +0 -11
- package/src/members/index.ts +10 -33
- package/src/members/util.ts +2 -1
- package/src/multistream/mediaRequestManager.ts +7 -7
- package/src/multistream/remoteMedia.ts +34 -4
- package/src/multistream/remoteMediaGroup.ts +37 -2
- package/test/unit/spec/locus-info/index.js +8 -365
- package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
- package/test/unit/spec/meeting/index.js +254 -38
- package/test/unit/spec/meeting/utils.js +122 -1
- package/test/unit/spec/meetings/index.js +2 -0
- package/test/unit/spec/members/index.js +37 -1
- package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
- package/test/unit/spec/multistream/remoteMedia.ts +66 -2
- 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
@@ -56,6 +56,7 @@ import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/reques
|
|
56
56
|
import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
57
57
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
58
58
|
import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
|
59
|
+
import MembersUtil from '@webex/plugin-meetings/src/members/util';
|
59
60
|
import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
|
60
61
|
import Media from '@webex/plugin-meetings/src/media/index';
|
61
62
|
import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
|
@@ -244,6 +245,7 @@ describe('plugin-meetings', () => {
|
|
244
245
|
});
|
245
246
|
|
246
247
|
webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
|
248
|
+
webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
|
247
249
|
webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
|
248
250
|
webex.internal.services = {get: sinon.stub().returns('locus-url')};
|
249
251
|
webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
|
@@ -368,6 +370,35 @@ describe('plugin-meetings', () => {
|
|
368
370
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
369
371
|
assert.instanceOf(meeting.webinar, Webinar);
|
370
372
|
});
|
373
|
+
|
374
|
+
it('should call the callback with the meeting that has id already set', () => {
|
375
|
+
let meetingIdFromCallback;
|
376
|
+
// check that the meeting id is already set correctly at the time when the callback is called
|
377
|
+
const meetingCreationCallback = sinon.stub().callsFake((meeting) => {
|
378
|
+
meetingIdFromCallback = meeting.id;
|
379
|
+
});
|
380
|
+
|
381
|
+
meeting = new Meeting(
|
382
|
+
{
|
383
|
+
userId: uuid1,
|
384
|
+
resource: uuid2,
|
385
|
+
deviceUrl: uuid3,
|
386
|
+
locus: {url: url1},
|
387
|
+
destination: testDestination,
|
388
|
+
destinationType: DESTINATION_TYPE.MEETING_ID,
|
389
|
+
correlationId,
|
390
|
+
selfId: uuid1,
|
391
|
+
},
|
392
|
+
{
|
393
|
+
parent: webex,
|
394
|
+
},
|
395
|
+
meetingCreationCallback
|
396
|
+
);
|
397
|
+
assert.exists(meeting.id);
|
398
|
+
assert.calledOnceWithExactly(meetingCreationCallback, meeting);
|
399
|
+
assert.equal(meeting.id, meetingIdFromCallback);
|
400
|
+
});
|
401
|
+
|
371
402
|
it('creates MediaRequestManager instances', () => {
|
372
403
|
assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
|
373
404
|
assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
|
@@ -454,6 +485,18 @@ describe('plugin-meetings', () => {
|
|
454
485
|
});
|
455
486
|
});
|
456
487
|
|
488
|
+
it('pstnCorrelationId getter/setter should work correctly', () => {
|
489
|
+
const testPstnCorrelationId = uuid.v4();
|
490
|
+
|
491
|
+
meeting.pstnCorrelationId = testPstnCorrelationId;
|
492
|
+
assert.equal(meeting.pstnCorrelationId, testPstnCorrelationId);
|
493
|
+
assert.equal(meeting.callStateForMetrics.pstnCorrelationId, testPstnCorrelationId);
|
494
|
+
|
495
|
+
meeting.pstnCorrelationId = undefined;
|
496
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
497
|
+
assert.equal(meeting.callStateForMetrics.pstnCorrelationId, undefined);
|
498
|
+
});
|
499
|
+
|
457
500
|
describe('creates ReceiveSlot manager instance', () => {
|
458
501
|
let mockReceiveSlotManagerCtor;
|
459
502
|
let providedCreateSlotCallback;
|
@@ -581,7 +624,6 @@ describe('plugin-meetings', () => {
|
|
581
624
|
assert.isFalse(meeting.isLocusCall());
|
582
625
|
});
|
583
626
|
});
|
584
|
-
|
585
627
|
describe('#invite', () => {
|
586
628
|
it('should have #invite', () => {
|
587
629
|
assert.exists(meeting.invite);
|
@@ -592,8 +634,6 @@ describe('plugin-meetings', () => {
|
|
592
634
|
it('should proxy members #addMember and return a promise', async () => {
|
593
635
|
const invite = meeting.invite(uuid1, false);
|
594
636
|
|
595
|
-
assert.exists(invite.then);
|
596
|
-
await invite;
|
597
637
|
assert.calledOnce(meeting.members.addMember);
|
598
638
|
assert.calledWith(meeting.members.addMember, uuid1, false);
|
599
639
|
});
|
@@ -1949,21 +1989,25 @@ describe('plugin-meetings', () => {
|
|
1949
1989
|
});
|
1950
1990
|
});
|
1951
1991
|
|
1952
|
-
it('should
|
1992
|
+
it('should handle join failure', async () => {
|
1953
1993
|
MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
|
1994
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
1995
|
+
|
1954
1996
|
await meeting.join().catch(() => {
|
1955
|
-
assert.
|
1956
|
-
|
1957
|
-
|
1958
|
-
);
|
1959
|
-
assert.
|
1960
|
-
webex.internal.newMetrics.submitClientEvent
|
1997
|
+
assert.calledOnce(MeetingUtil.joinMeeting);
|
1998
|
+
|
1999
|
+
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
2000
|
+
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
2001
|
+
assert.calledWithMatch(
|
2002
|
+
webex.internal.newMetrics.submitClientEvent,
|
1961
2003
|
{
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
2004
|
+
name: 'client.call.initiated',
|
2005
|
+
payload: {
|
2006
|
+
trigger: 'user-interaction',
|
2007
|
+
isRoapCallEnabled: true,
|
2008
|
+
pstnAudioType: undefined
|
2009
|
+
},
|
2010
|
+
options: {meetingId: meeting.id},
|
1967
2011
|
}
|
1968
2012
|
);
|
1969
2013
|
});
|
@@ -6498,12 +6542,17 @@ describe('plugin-meetings', () => {
|
|
6498
6542
|
const DIAL_IN_URL = meeting.dialInUrl;
|
6499
6543
|
|
6500
6544
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
6501
|
-
correlationId: meeting.
|
6545
|
+
correlationId: meeting.pstnCorrelationId,
|
6502
6546
|
dialInUrl: DIAL_IN_URL,
|
6503
6547
|
locusUrl: meeting.locusUrl,
|
6504
6548
|
clientUrl: meeting.deviceUrl,
|
6505
6549
|
});
|
6506
6550
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
6551
|
+
|
6552
|
+
// Verify pstnCorrelationId was set
|
6553
|
+
assert.exists(meeting.pstnCorrelationId);
|
6554
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
6555
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId
|
6507
6556
|
|
6508
6557
|
meeting.meetingRequest.dialIn.resetHistory();
|
6509
6558
|
|
@@ -6511,12 +6560,18 @@ describe('plugin-meetings', () => {
|
|
6511
6560
|
await meeting.usePhoneAudio();
|
6512
6561
|
|
6513
6562
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
6514
|
-
correlationId: meeting.
|
6563
|
+
correlationId: meeting.pstnCorrelationId,
|
6515
6564
|
dialInUrl: DIAL_IN_URL,
|
6516
6565
|
locusUrl: meeting.locusUrl,
|
6517
6566
|
clientUrl: meeting.deviceUrl,
|
6518
6567
|
});
|
6519
6568
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
6569
|
+
// A new PSTN correlationId should be generated for the second attempt
|
6570
|
+
assert.notEqual(
|
6571
|
+
meeting.pstnCorrelationId,
|
6572
|
+
firstPstnCorrelationId,
|
6573
|
+
'pstnCorrelationId should be regenerated on each dial-in attempt'
|
6574
|
+
);
|
6520
6575
|
});
|
6521
6576
|
|
6522
6577
|
it('given a phone number, triggers dial-out, delegating request to meetingRequest correctly', async () => {
|
@@ -6526,7 +6581,7 @@ describe('plugin-meetings', () => {
|
|
6526
6581
|
const DIAL_OUT_URL = meeting.dialOutUrl;
|
6527
6582
|
|
6528
6583
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
6529
|
-
correlationId: meeting.
|
6584
|
+
correlationId: meeting.pstnCorrelationId,
|
6530
6585
|
dialOutUrl: DIAL_OUT_URL,
|
6531
6586
|
locusUrl: meeting.locusUrl,
|
6532
6587
|
clientUrl: meeting.deviceUrl,
|
@@ -6534,49 +6589,126 @@ describe('plugin-meetings', () => {
|
|
6534
6589
|
});
|
6535
6590
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
6536
6591
|
|
6592
|
+
// Verify pstnCorrelationId was set
|
6593
|
+
assert.exists(meeting.pstnCorrelationId);
|
6594
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
6595
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId;
|
6596
|
+
|
6537
6597
|
meeting.meetingRequest.dialOut.resetHistory();
|
6538
6598
|
|
6539
6599
|
// try again. the dial out urls should match
|
6540
6600
|
await meeting.usePhoneAudio(phoneNumber);
|
6541
6601
|
|
6542
6602
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
6543
|
-
correlationId: meeting.
|
6603
|
+
correlationId: meeting.pstnCorrelationId,
|
6544
6604
|
dialOutUrl: DIAL_OUT_URL,
|
6545
6605
|
locusUrl: meeting.locusUrl,
|
6546
6606
|
clientUrl: meeting.deviceUrl,
|
6547
6607
|
phoneNumber,
|
6548
6608
|
});
|
6549
6609
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
6610
|
+
// A new PSTN correlationId should be generated for the second attempt
|
6611
|
+
assert.notEqual(
|
6612
|
+
meeting.pstnCorrelationId,
|
6613
|
+
firstPstnCorrelationId,
|
6614
|
+
'pstnCorrelationId should be regenerated on each dial-out attempt'
|
6615
|
+
);
|
6550
6616
|
});
|
6551
6617
|
|
6552
|
-
it('rejects if the request failed (dial in)', () => {
|
6553
|
-
const error = '
|
6618
|
+
it('rejects if the request failed (dial in)', async () => {
|
6619
|
+
const error = {error: {message: 'dial in failed'}, stack: 'error stack'};
|
6554
6620
|
|
6555
6621
|
meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
|
6556
6622
|
|
6557
|
-
|
6558
|
-
.usePhoneAudio()
|
6559
|
-
|
6560
|
-
|
6561
|
-
|
6562
|
-
|
6563
|
-
|
6623
|
+
try {
|
6624
|
+
await meeting.usePhoneAudio();
|
6625
|
+
throw new Error('Promise resolved when it should have rejected');
|
6626
|
+
} catch (e) {
|
6627
|
+
assert.equal(e, error);
|
6628
|
+
|
6629
|
+
// Verify behavioral metric was sent with dial_in_correlation_id
|
6630
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
|
6631
|
+
correlation_id: meeting.correlationId,
|
6632
|
+
dial_in_url: meeting.dialInUrl,
|
6633
|
+
dial_in_correlation_id: sinon.match.string,
|
6634
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
6635
|
+
client_url: meeting.deviceUrl,
|
6636
|
+
reason: error.error.message,
|
6637
|
+
stack: error.stack,
|
6564
6638
|
});
|
6639
|
+
|
6640
|
+
// Verify pstnCorrelationId was cleared after error
|
6641
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
6642
|
+
}
|
6565
6643
|
});
|
6566
6644
|
|
6567
6645
|
it('rejects if the request failed (dial out)', async () => {
|
6568
|
-
const error = '
|
6646
|
+
const error = {error: {message: 'dial out failed'}, stack: 'error stack'};
|
6569
6647
|
|
6570
6648
|
meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
|
6571
6649
|
|
6572
|
-
|
6573
|
-
.usePhoneAudio('+441234567890')
|
6574
|
-
|
6575
|
-
|
6576
|
-
|
6577
|
-
|
6578
|
-
|
6650
|
+
try {
|
6651
|
+
await meeting.usePhoneAudio('+441234567890');
|
6652
|
+
throw new Error('Promise resolved when it should have rejected');
|
6653
|
+
} catch (e) {
|
6654
|
+
assert.equal(e, error);
|
6655
|
+
|
6656
|
+
// Verify behavioral metric was sent with dial_out_correlation_id
|
6657
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
|
6658
|
+
correlation_id: meeting.correlationId,
|
6659
|
+
dial_out_url: meeting.dialOutUrl,
|
6660
|
+
dial_out_correlation_id: sinon.match.string,
|
6661
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
6662
|
+
client_url: meeting.deviceUrl,
|
6663
|
+
reason: error.error.message,
|
6664
|
+
stack: error.stack,
|
6579
6665
|
});
|
6666
|
+
|
6667
|
+
// Verify pstnCorrelationId was cleared after error
|
6668
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
6669
|
+
}
|
6670
|
+
});
|
6671
|
+
});
|
6672
|
+
|
6673
|
+
describe('#disconnectPhoneAudio', () => {
|
6674
|
+
beforeEach(() => {
|
6675
|
+
// Mock the MeetingUtil.disconnectPhoneAudio method
|
6676
|
+
sinon.stub(MeetingUtil, 'disconnectPhoneAudio').resolves();
|
6677
|
+
meeting.dialInUrl = 'dialin:///test-dial-in-url';
|
6678
|
+
meeting.dialOutUrl = 'dialout:///test-dial-out-url';
|
6679
|
+
meeting.dialInDeviceStatus = 'JOINED';
|
6680
|
+
meeting.dialOutDeviceStatus = 'JOINED';
|
6681
|
+
});
|
6682
|
+
|
6683
|
+
afterEach(() => {
|
6684
|
+
MeetingUtil.disconnectPhoneAudio.restore();
|
6685
|
+
});
|
6686
|
+
|
6687
|
+
it('should disconnect phone audio and clear pstnCorrelationId', async () => {
|
6688
|
+
meeting.pstnCorrelationId = 'test-pstn-correlation-id';
|
6689
|
+
|
6690
|
+
await meeting.disconnectPhoneAudio();
|
6691
|
+
|
6692
|
+
// Verify that pstnCorrelationId is cleared
|
6693
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
6694
|
+
|
6695
|
+
// Verify that MeetingUtil.disconnectPhoneAudio was called for both dial-in and dial-out
|
6696
|
+
assert.calledTwice(MeetingUtil.disconnectPhoneAudio);
|
6697
|
+
assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialInUrl);
|
6698
|
+
assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialOutUrl);
|
6699
|
+
});
|
6700
|
+
|
6701
|
+
it('should handle case when no PSTN connection is active', async () => {
|
6702
|
+
meeting.dialInDeviceStatus = 'IDLE';
|
6703
|
+
meeting.dialOutDeviceStatus = 'IDLE';
|
6704
|
+
meeting.pstnCorrelationId = 'test-pstn-correlation-id';
|
6705
|
+
|
6706
|
+
await meeting.disconnectPhoneAudio();
|
6707
|
+
|
6708
|
+
// Verify that pstnCorrelationId is still cleared even when no phone connection is active
|
6709
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
6710
|
+
// And verify no disconnect was attempted
|
6711
|
+
assert.notCalled(MeetingUtil.disconnectPhoneAudio);
|
6580
6712
|
});
|
6581
6713
|
});
|
6582
6714
|
|
@@ -8093,6 +8225,7 @@ describe('plugin-meetings', () => {
|
|
8093
8225
|
|
8094
8226
|
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
8095
8227
|
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
8228
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
8096
8229
|
meeting.mediaProperties.mediaDirection = {
|
8097
8230
|
sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
|
8098
8231
|
sendVideo: 'fake value',
|
@@ -8174,6 +8307,12 @@ describe('plugin-meetings', () => {
|
|
8174
8307
|
payload: {mediaType: 'share', shareInstanceId: meeting.localShareInstanceId},
|
8175
8308
|
options: {meetingId: meeting.id},
|
8176
8309
|
});
|
8310
|
+
|
8311
|
+
// ensure the share start timestamp is saved
|
8312
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
8313
|
+
key: 'internal.client.share.initiated',
|
8314
|
+
});
|
8315
|
+
|
8177
8316
|
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
8178
8317
|
|
8179
8318
|
assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
|
@@ -8192,6 +8331,11 @@ describe('plugin-meetings', () => {
|
|
8192
8331
|
options: {meetingId: meeting.id},
|
8193
8332
|
});
|
8194
8333
|
|
8334
|
+
// ensure the share start timestamp is saved
|
8335
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
8336
|
+
key: 'internal.client.share.initiated',
|
8337
|
+
});
|
8338
|
+
|
8195
8339
|
assert.calledWith(
|
8196
8340
|
meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
|
8197
8341
|
stream
|
@@ -10465,6 +10609,8 @@ describe('plugin-meetings', () => {
|
|
10465
10609
|
meeting.mediaProperties = {mediaDirection: {sendShare: true}};
|
10466
10610
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
10467
10611
|
(meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
|
10612
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
10613
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
|
10468
10614
|
});
|
10469
10615
|
it('should call changeMeetingFloor()', async () => {
|
10470
10616
|
meeting.screenShareFloorState = 'GRANTED';
|
@@ -10482,6 +10628,22 @@ describe('plugin-meetings', () => {
|
|
10482
10628
|
assert.exists(share.then);
|
10483
10629
|
await share;
|
10484
10630
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
10631
|
+
|
10632
|
+
// ensure the share stop timestamp is saved
|
10633
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
10634
|
+
key: 'internal.client.share.stopped',
|
10635
|
+
});
|
10636
|
+
|
10637
|
+
// ensure the CA share stopped metric is submitted with duration
|
10638
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
10639
|
+
name: 'client.share.stopped',
|
10640
|
+
payload: {
|
10641
|
+
mediaType: 'share',
|
10642
|
+
shareInstanceId: meeting.localShareInstanceId,
|
10643
|
+
shareDuration: 1000,
|
10644
|
+
},
|
10645
|
+
options: {meetingId: meeting.id},
|
10646
|
+
});
|
10485
10647
|
});
|
10486
10648
|
it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
|
10487
10649
|
// change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
|
@@ -12054,6 +12216,7 @@ describe('plugin-meetings', () => {
|
|
12054
12216
|
meeting.locusInfo.self = {url: url1};
|
12055
12217
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
12056
12218
|
meeting.deviceUrl = 'deviceUrl.com';
|
12219
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
12057
12220
|
});
|
12058
12221
|
it('should have #startWhiteboardShare', () => {
|
12059
12222
|
assert.exists(meeting.startWhiteboardShare);
|
@@ -12081,6 +12244,11 @@ describe('plugin-meetings', () => {
|
|
12081
12244
|
payload: {mediaType: 'whiteboard'},
|
12082
12245
|
options: {meetingId: meeting.id},
|
12083
12246
|
});
|
12247
|
+
|
12248
|
+
// ensure the share start timestamp is saved
|
12249
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
12250
|
+
key: 'internal.client.share.initiated',
|
12251
|
+
});
|
12084
12252
|
});
|
12085
12253
|
});
|
12086
12254
|
describe('#stopWhiteboardShare', () => {
|
@@ -12092,6 +12260,8 @@ describe('plugin-meetings', () => {
|
|
12092
12260
|
meeting.locusInfo.self = {url: url1};
|
12093
12261
|
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
12094
12262
|
meeting.deviceUrl = 'deviceUrl.com';
|
12263
|
+
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
12264
|
+
webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
|
12095
12265
|
});
|
12096
12266
|
it('should stop the whiteboard share', async () => {
|
12097
12267
|
const whiteboardShare = meeting.stopWhiteboardShare();
|
@@ -12106,6 +12276,21 @@ describe('plugin-meetings', () => {
|
|
12106
12276
|
uri: url1,
|
12107
12277
|
});
|
12108
12278
|
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
12279
|
+
|
12280
|
+
// ensure the share stop timestamp is saved
|
12281
|
+
assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
|
12282
|
+
key: 'internal.client.share.stopped',
|
12283
|
+
});
|
12284
|
+
|
12285
|
+
// ensure the CA share stopped metric is submitted with duration
|
12286
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
12287
|
+
name: 'client.share.stopped',
|
12288
|
+
payload: {
|
12289
|
+
mediaType: 'whiteboard',
|
12290
|
+
shareDuration: 1000,
|
12291
|
+
},
|
12292
|
+
options: {meetingId: meeting.id},
|
12293
|
+
});
|
12109
12294
|
});
|
12110
12295
|
});
|
12111
12296
|
});
|
@@ -12404,7 +12589,7 @@ describe('plugin-meetings', () => {
|
|
12404
12589
|
activeSharingId.whiteboard = beneficiaryId;
|
12405
12590
|
|
12406
12591
|
eventTrigger.share.push(
|
12407
|
-
meeting.webinar.selfIsAttendee
|
12592
|
+
meeting.webinar.selfIsAttendee || meeting.guest
|
12408
12593
|
? {
|
12409
12594
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
12410
12595
|
functionName: 'remoteShare',
|
@@ -12423,7 +12608,8 @@ describe('plugin-meetings', () => {
|
|
12423
12608
|
}
|
12424
12609
|
);
|
12425
12610
|
|
12426
|
-
shareStatus =
|
12611
|
+
shareStatus =
|
12612
|
+
meeting.webinar.selfIsAttendee || meeting.guest
|
12427
12613
|
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
12428
12614
|
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
12429
12615
|
}
|
@@ -12641,6 +12827,36 @@ describe('plugin-meetings', () => {
|
|
12641
12827
|
});
|
12642
12828
|
});
|
12643
12829
|
|
12830
|
+
describe('Whiteboard Share - User is guest', () => {
|
12831
|
+
it('User receives a remote share instead of whiteboard share', () => {
|
12832
|
+
// Set the guest flag
|
12833
|
+
meeting.guest = true;
|
12834
|
+
|
12835
|
+
// Step 1: Start sharing whiteboard A
|
12836
|
+
const data1 = generateData(
|
12837
|
+
blankPayload, // Initial payload
|
12838
|
+
true, // isGranting: Granting share
|
12839
|
+
false, // isContent: Whiteboard (not content)
|
12840
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
12841
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
12842
|
+
);
|
12843
|
+
|
12844
|
+
// Step 2: Stop sharing whiteboard A
|
12845
|
+
const data2 = generateData(
|
12846
|
+
data1.payload, // Updated payload from Step 1
|
12847
|
+
false, // isGranting: Stopping share
|
12848
|
+
false, // isContent: Whiteboard
|
12849
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
12850
|
+
);
|
12851
|
+
|
12852
|
+
// Validate the payload changes and status updates
|
12853
|
+
payloadTestHelper([data1]);
|
12854
|
+
|
12855
|
+
// Specific assertions for guest
|
12856
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
12857
|
+
});
|
12858
|
+
});
|
12859
|
+
|
12644
12860
|
describe('Whiteboard A --> Whiteboard B', () => {
|
12645
12861
|
it('Scenario #1: you share both whiteboards', () => {
|
12646
12862
|
const data1 = generateData(
|
@@ -3,12 +3,14 @@ import sinon from 'sinon';
|
|
3
3
|
import {assert} from '@webex/test-helper-chai';
|
4
4
|
import Meetings from '@webex/plugin-meetings';
|
5
5
|
import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
|
6
|
-
import {LOCAL_SHARE_ERRORS} from '@webex/plugin-meetings/src/constants';
|
6
|
+
import {LOCAL_SHARE_ERRORS, PASSWORD_STATUS} from '@webex/plugin-meetings/src/constants';
|
7
7
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
8
8
|
import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
|
9
9
|
import {SELF_POLICY, IP_VERSION} from '@webex/plugin-meetings/src/constants';
|
10
10
|
import MockWebex from '@webex/test-helper-mock-webex';
|
11
11
|
import * as BrowserDetectionModule from '@webex/plugin-meetings/src/common/browser-detection';
|
12
|
+
import PasswordError from '@webex/plugin-meetings/src/common/errors/password-error';
|
13
|
+
import CaptchaError from '@webex/plugin-meetings/src/common/errors/captcha-error';
|
12
14
|
|
13
15
|
describe('plugin-meetings', () => {
|
14
16
|
let webex;
|
@@ -57,6 +59,10 @@ describe('plugin-meetings', () => {
|
|
57
59
|
meeting.getWebexObject = sinon.stub().returns(webex);
|
58
60
|
meeting.simultaneousInterpretation = {cleanUp: sinon.stub()};
|
59
61
|
meeting.trigger = sinon.stub();
|
62
|
+
meeting.webex = webex;
|
63
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics =
|
64
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics || {};
|
65
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
|
60
66
|
});
|
61
67
|
|
62
68
|
afterEach(() => {
|
@@ -81,6 +87,10 @@ describe('plugin-meetings', () => {
|
|
81
87
|
assert.calledOnce(meeting.breakouts.cleanUp);
|
82
88
|
assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
|
83
89
|
assert.calledOnce(webex.internal.device.meetingEnded);
|
90
|
+
assert.calledOnceWithExactly(
|
91
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId,
|
92
|
+
meeting.correlationId
|
93
|
+
);
|
84
94
|
});
|
85
95
|
|
86
96
|
it('do clean up on meeting object with LLM disabled', async () => {
|
@@ -98,6 +108,10 @@ describe('plugin-meetings', () => {
|
|
98
108
|
assert.calledOnce(meeting.breakouts.cleanUp);
|
99
109
|
assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
|
100
110
|
assert.calledOnce(webex.internal.device.meetingEnded);
|
111
|
+
assert.calledOnceWithExactly(
|
112
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId,
|
113
|
+
meeting.correlationId
|
114
|
+
);
|
101
115
|
});
|
102
116
|
|
103
117
|
it('do clean up on meeting object with no config', async () => {
|
@@ -114,6 +128,10 @@ describe('plugin-meetings', () => {
|
|
114
128
|
assert.calledOnce(meeting.breakouts.cleanUp);
|
115
129
|
assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
|
116
130
|
assert.calledOnce(webex.internal.device.meetingEnded);
|
131
|
+
assert.calledOnceWithExactly(
|
132
|
+
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId,
|
133
|
+
meeting.correlationId
|
134
|
+
);
|
117
135
|
});
|
118
136
|
});
|
119
137
|
|
@@ -622,6 +640,28 @@ describe('plugin-meetings', () => {
|
|
622
640
|
|
623
641
|
assert.equal(parameter.locusClusterUrl, 'locusClusterUrl');
|
624
642
|
});
|
643
|
+
|
644
|
+
it('should post client event with error when join fails', async () => {
|
645
|
+
const joinError = new Error('Join failed');
|
646
|
+
meeting.meetingRequest.joinMeeting.rejects(joinError);
|
647
|
+
meeting.meetingInfo = { meetingLookupUrl: 'test-lookup-url' };
|
648
|
+
|
649
|
+
try {
|
650
|
+
await MeetingUtil.joinMeeting(meeting, {});
|
651
|
+
assert.fail('Expected joinMeeting to throw an error');
|
652
|
+
} catch (error) {
|
653
|
+
assert.equal(error, joinError);
|
654
|
+
|
655
|
+
// Verify error client event was submitted
|
656
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
657
|
+
name: 'client.locus.join.response',
|
658
|
+
payload: {
|
659
|
+
identifiers: { meetingLookupUrl: 'test-lookup-url' },
|
660
|
+
},
|
661
|
+
options: { meetingId: meeting.id, rawError: joinError },
|
662
|
+
});
|
663
|
+
}
|
664
|
+
});
|
625
665
|
});
|
626
666
|
|
627
667
|
describe('joinMeetingOptions', () => {
|
@@ -661,6 +701,82 @@ describe('plugin-meetings', () => {
|
|
661
701
|
joinMeetingSpy.restore();
|
662
702
|
}
|
663
703
|
});
|
704
|
+
|
705
|
+
it('should submit client event and reject with PasswordError when password is required', async () => {
|
706
|
+
const meeting = {
|
707
|
+
id: 'meeting-id',
|
708
|
+
passwordStatus: PASSWORD_STATUS.REQUIRED,
|
709
|
+
resourceId: null,
|
710
|
+
requiredCaptcha: null,
|
711
|
+
getWebexObject: sinon.stub().returns(webex),
|
712
|
+
};
|
713
|
+
|
714
|
+
try {
|
715
|
+
await MeetingUtil.joinMeetingOptions(meeting, {});
|
716
|
+
assert.fail('Expected joinMeetingOptions to throw PasswordError');
|
717
|
+
} catch (error) {
|
718
|
+
assert.instanceOf(error, PasswordError);
|
719
|
+
|
720
|
+
// Verify client event was submitted with error details
|
721
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
722
|
+
name: 'client.meetinginfo.response',
|
723
|
+
options: {
|
724
|
+
meetingId: meeting.id,
|
725
|
+
},
|
726
|
+
payload: {
|
727
|
+
errors: [
|
728
|
+
{
|
729
|
+
fatal: false,
|
730
|
+
category: 'expected',
|
731
|
+
name: 'other',
|
732
|
+
shownToUser: false,
|
733
|
+
errorCode: error.code,
|
734
|
+
errorDescription: error.name,
|
735
|
+
rawErrorMessage: error.sdkMessage,
|
736
|
+
},
|
737
|
+
],
|
738
|
+
},
|
739
|
+
});
|
740
|
+
}
|
741
|
+
});
|
742
|
+
|
743
|
+
it('should submit client event and reject with CaptchaError when captcha is required', async () => {
|
744
|
+
const meeting = {
|
745
|
+
id: 'meeting-id',
|
746
|
+
passwordStatus: null,
|
747
|
+
resourceId: null,
|
748
|
+
requiredCaptcha: {captchaId: 'test-captcha'},
|
749
|
+
getWebexObject: sinon.stub().returns(webex),
|
750
|
+
};
|
751
|
+
|
752
|
+
try {
|
753
|
+
await MeetingUtil.joinMeetingOptions(meeting, {});
|
754
|
+
assert.fail('Expected joinMeetingOptions to throw CaptchaError');
|
755
|
+
} catch (error) {
|
756
|
+
assert.instanceOf(error, CaptchaError);
|
757
|
+
|
758
|
+
// Verify client event was submitted with error details
|
759
|
+
assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
|
760
|
+
name: 'client.meetinginfo.response',
|
761
|
+
options: {
|
762
|
+
meetingId: meeting.id,
|
763
|
+
},
|
764
|
+
payload: {
|
765
|
+
errors: [
|
766
|
+
{
|
767
|
+
fatal: false,
|
768
|
+
category: 'expected',
|
769
|
+
name: 'other',
|
770
|
+
shownToUser: false,
|
771
|
+
errorCode: error.code,
|
772
|
+
errorDescription: error.name,
|
773
|
+
rawErrorMessage: error.sdkMessage,
|
774
|
+
},
|
775
|
+
],
|
776
|
+
},
|
777
|
+
});
|
778
|
+
}
|
779
|
+
});
|
664
780
|
});
|
665
781
|
|
666
782
|
describe('getUserDisplayHintsFromLocusInfo', () => {
|
@@ -850,6 +966,11 @@ describe('plugin-meetings', () => {
|
|
850
966
|
{functionName: 'isClosedCaptionActive', displayHint: 'CAPTION_STATUS_ACTIVE'},
|
851
967
|
{functionName: 'canStartManualCaption', displayHint: 'MANUAL_CAPTION_START'},
|
852
968
|
{functionName: 'canStopManualCaption', displayHint: 'MANUAL_CAPTION_STOP'},
|
969
|
+
|
970
|
+
{functionName: 'isLocalRecordingStarted',displayHint:'LOCAL_RECORDING_STATUS_STARTED'},
|
971
|
+
{functionName: 'isLocalRecordingStopped', displayHint: 'LOCAL_RECORDING_STATUS_STOPPED'},
|
972
|
+
{functionName: 'isLocalRecordingPaused', displayHint: 'LOCAL_RECORDING_STATUS_PAUSED'},
|
973
|
+
|
853
974
|
{functionName: 'isManualCaptionActive', displayHint: 'MANUAL_CAPTION_STATUS_ACTIVE'},
|
854
975
|
{functionName: 'isWebexAssistantActive', displayHint: 'WEBEX_ASSISTANT_STATUS_ACTIVE'},
|
855
976
|
{functionName: 'canViewCaptionPanel', displayHint: 'ENABLE_CAPTION_PANEL'},
|
@@ -189,6 +189,7 @@ describe('plugin-meetings', () => {
|
|
189
189
|
},
|
190
190
|
callDiagnosticMetrics: {
|
191
191
|
clearErrorCache: sinon.stub(),
|
192
|
+
clearEventLimits: sinon.stub(),
|
192
193
|
},
|
193
194
|
},
|
194
195
|
});
|
@@ -1716,6 +1717,7 @@ describe('plugin-meetings', () => {
|
|
1716
1717
|
{file: 'meetings', function: 'fetchMeetingInfo'},
|
1717
1718
|
'meeting:meetingInfoAvailable'
|
1718
1719
|
);
|
1720
|
+
assert.equal(webex.meetings.meetingCollection.get(meeting.id), meeting);
|
1719
1721
|
};
|
1720
1722
|
|
1721
1723
|
it('creates the meeting from a successful meeting info fetch promise testing', async () => {
|