@webex/plugin-meetings 3.8.1-next.42 → 3.8.1-next.44
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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +140 -122
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +57 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/types/meeting/index.d.ts +12 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/meeting/index.ts +40 -14
- package/src/meeting/util.ts +60 -2
- package/test/unit/spec/meeting/index.js +137 -33
- package/test/unit/spec/meeting/utils.js +101 -1
package/src/meeting/util.ts
CHANGED
@@ -197,6 +197,17 @@ const MeetingUtil = {
|
|
197
197
|
});
|
198
198
|
|
199
199
|
return parsed;
|
200
|
+
})
|
201
|
+
.catch((err) => {
|
202
|
+
webex.internal.newMetrics.submitClientEvent({
|
203
|
+
name: 'client.locus.join.response',
|
204
|
+
payload: {
|
205
|
+
identifiers: {meetingLookupUrl: meeting.meetingInfo?.meetingLookupUrl},
|
206
|
+
},
|
207
|
+
options: {meetingId: meeting.id, rawError: err},
|
208
|
+
});
|
209
|
+
|
210
|
+
throw err;
|
200
211
|
});
|
201
212
|
},
|
202
213
|
|
@@ -332,10 +343,57 @@ const MeetingUtil = {
|
|
332
343
|
meeting.resourceId = meeting.resourceId || options.resourceId;
|
333
344
|
|
334
345
|
if (meeting.requiredCaptcha) {
|
335
|
-
|
346
|
+
const errorToThrow = new CaptchaError();
|
347
|
+
|
348
|
+
// @ts-ignore
|
349
|
+
webex.internal.newMetrics.submitClientEvent({
|
350
|
+
name: 'client.meetinginfo.response',
|
351
|
+
options: {
|
352
|
+
meetingId: meeting.id,
|
353
|
+
},
|
354
|
+
payload: {
|
355
|
+
errors: [
|
356
|
+
{
|
357
|
+
fatal: false,
|
358
|
+
category: 'expected',
|
359
|
+
name: 'other',
|
360
|
+
shownToUser: false,
|
361
|
+
errorCode: errorToThrow.code,
|
362
|
+
errorDescription: errorToThrow.name,
|
363
|
+
rawErrorMessage: errorToThrow.sdkMessage,
|
364
|
+
},
|
365
|
+
],
|
366
|
+
},
|
367
|
+
});
|
368
|
+
|
369
|
+
return Promise.reject(errorToThrow);
|
336
370
|
}
|
371
|
+
|
337
372
|
if (meeting.passwordStatus === PASSWORD_STATUS.REQUIRED) {
|
338
|
-
|
373
|
+
const errorToThrow = new PasswordError();
|
374
|
+
|
375
|
+
// @ts-ignore
|
376
|
+
webex.internal.newMetrics.submitClientEvent({
|
377
|
+
name: 'client.meetinginfo.response',
|
378
|
+
options: {
|
379
|
+
meetingId: meeting.id,
|
380
|
+
},
|
381
|
+
payload: {
|
382
|
+
errors: [
|
383
|
+
{
|
384
|
+
fatal: false,
|
385
|
+
category: 'expected',
|
386
|
+
name: 'other',
|
387
|
+
shownToUser: false,
|
388
|
+
errorCode: errorToThrow.code,
|
389
|
+
errorDescription: errorToThrow.name,
|
390
|
+
rawErrorMessage: errorToThrow.sdkMessage,
|
391
|
+
},
|
392
|
+
],
|
393
|
+
},
|
394
|
+
});
|
395
|
+
|
396
|
+
return Promise.reject(errorToThrow);
|
339
397
|
}
|
340
398
|
|
341
399
|
if (options.pin) {
|
@@ -485,6 +485,18 @@ describe('plugin-meetings', () => {
|
|
485
485
|
});
|
486
486
|
});
|
487
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
|
+
|
488
500
|
describe('creates ReceiveSlot manager instance', () => {
|
489
501
|
let mockReceiveSlotManagerCtor;
|
490
502
|
let providedCreateSlotCallback;
|
@@ -1977,21 +1989,25 @@ describe('plugin-meetings', () => {
|
|
1977
1989
|
});
|
1978
1990
|
});
|
1979
1991
|
|
1980
|
-
it('should
|
1992
|
+
it('should handle join failure', async () => {
|
1981
1993
|
MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
|
1994
|
+
webex.internal.newMetrics.submitClientEvent = sinon.stub();
|
1995
|
+
|
1982
1996
|
await meeting.join().catch(() => {
|
1983
|
-
assert.
|
1984
|
-
|
1985
|
-
|
1986
|
-
);
|
1987
|
-
assert.
|
1988
|
-
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,
|
1989
2003
|
{
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
2004
|
+
name: 'client.call.initiated',
|
2005
|
+
payload: {
|
2006
|
+
trigger: 'user-interaction',
|
2007
|
+
isRoapCallEnabled: true,
|
2008
|
+
pstnAudioType: undefined
|
2009
|
+
},
|
2010
|
+
options: {meetingId: meeting.id},
|
1995
2011
|
}
|
1996
2012
|
);
|
1997
2013
|
});
|
@@ -6526,12 +6542,17 @@ describe('plugin-meetings', () => {
|
|
6526
6542
|
const DIAL_IN_URL = meeting.dialInUrl;
|
6527
6543
|
|
6528
6544
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
6529
|
-
correlationId: meeting.
|
6545
|
+
correlationId: meeting.pstnCorrelationId,
|
6530
6546
|
dialInUrl: DIAL_IN_URL,
|
6531
6547
|
locusUrl: meeting.locusUrl,
|
6532
6548
|
clientUrl: meeting.deviceUrl,
|
6533
6549
|
});
|
6534
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
|
6535
6556
|
|
6536
6557
|
meeting.meetingRequest.dialIn.resetHistory();
|
6537
6558
|
|
@@ -6539,12 +6560,18 @@ describe('plugin-meetings', () => {
|
|
6539
6560
|
await meeting.usePhoneAudio();
|
6540
6561
|
|
6541
6562
|
assert.calledWith(meeting.meetingRequest.dialIn, {
|
6542
|
-
correlationId: meeting.
|
6563
|
+
correlationId: meeting.pstnCorrelationId,
|
6543
6564
|
dialInUrl: DIAL_IN_URL,
|
6544
6565
|
locusUrl: meeting.locusUrl,
|
6545
6566
|
clientUrl: meeting.deviceUrl,
|
6546
6567
|
});
|
6547
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
|
+
);
|
6548
6575
|
});
|
6549
6576
|
|
6550
6577
|
it('given a phone number, triggers dial-out, delegating request to meetingRequest correctly', async () => {
|
@@ -6554,7 +6581,7 @@ describe('plugin-meetings', () => {
|
|
6554
6581
|
const DIAL_OUT_URL = meeting.dialOutUrl;
|
6555
6582
|
|
6556
6583
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
6557
|
-
correlationId: meeting.
|
6584
|
+
correlationId: meeting.pstnCorrelationId,
|
6558
6585
|
dialOutUrl: DIAL_OUT_URL,
|
6559
6586
|
locusUrl: meeting.locusUrl,
|
6560
6587
|
clientUrl: meeting.deviceUrl,
|
@@ -6562,49 +6589,126 @@ describe('plugin-meetings', () => {
|
|
6562
6589
|
});
|
6563
6590
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
6564
6591
|
|
6592
|
+
// Verify pstnCorrelationId was set
|
6593
|
+
assert.exists(meeting.pstnCorrelationId);
|
6594
|
+
assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
|
6595
|
+
const firstPstnCorrelationId = meeting.pstnCorrelationId;
|
6596
|
+
|
6565
6597
|
meeting.meetingRequest.dialOut.resetHistory();
|
6566
6598
|
|
6567
6599
|
// try again. the dial out urls should match
|
6568
6600
|
await meeting.usePhoneAudio(phoneNumber);
|
6569
6601
|
|
6570
6602
|
assert.calledWith(meeting.meetingRequest.dialOut, {
|
6571
|
-
correlationId: meeting.
|
6603
|
+
correlationId: meeting.pstnCorrelationId,
|
6572
6604
|
dialOutUrl: DIAL_OUT_URL,
|
6573
6605
|
locusUrl: meeting.locusUrl,
|
6574
6606
|
clientUrl: meeting.deviceUrl,
|
6575
6607
|
phoneNumber,
|
6576
6608
|
});
|
6577
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
|
+
);
|
6578
6616
|
});
|
6579
6617
|
|
6580
|
-
it('rejects if the request failed (dial in)', () => {
|
6581
|
-
const error = '
|
6618
|
+
it('rejects if the request failed (dial in)', async () => {
|
6619
|
+
const error = {error: {message: 'dial in failed'}, stack: 'error stack'};
|
6582
6620
|
|
6583
6621
|
meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
|
6584
6622
|
|
6585
|
-
|
6586
|
-
.usePhoneAudio()
|
6587
|
-
|
6588
|
-
|
6589
|
-
|
6590
|
-
|
6591
|
-
|
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,
|
6592
6638
|
});
|
6639
|
+
|
6640
|
+
// Verify pstnCorrelationId was cleared after error
|
6641
|
+
assert.equal(meeting.pstnCorrelationId, undefined);
|
6642
|
+
}
|
6593
6643
|
});
|
6594
6644
|
|
6595
6645
|
it('rejects if the request failed (dial out)', async () => {
|
6596
|
-
const error = '
|
6646
|
+
const error = {error: {message: 'dial out failed'}, stack: 'error stack'};
|
6597
6647
|
|
6598
6648
|
meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
|
6599
6649
|
|
6600
|
-
|
6601
|
-
.usePhoneAudio('+441234567890')
|
6602
|
-
|
6603
|
-
|
6604
|
-
|
6605
|
-
|
6606
|
-
|
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,
|
6607
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);
|
6608
6712
|
});
|
6609
6713
|
});
|
6610
6714
|
|
@@ -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;
|
@@ -638,6 +640,28 @@ describe('plugin-meetings', () => {
|
|
638
640
|
|
639
641
|
assert.equal(parameter.locusClusterUrl, 'locusClusterUrl');
|
640
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
|
+
});
|
641
665
|
});
|
642
666
|
|
643
667
|
describe('joinMeetingOptions', () => {
|
@@ -677,6 +701,82 @@ describe('plugin-meetings', () => {
|
|
677
701
|
joinMeetingSpy.restore();
|
678
702
|
}
|
679
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
|
+
});
|
680
780
|
});
|
681
781
|
|
682
782
|
describe('getUserDisplayHintsFromLocusInfo', () => {
|