@webex/plugin-meetings 3.0.0-beta.254 → 3.0.0-beta.256

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.
@@ -13,6 +13,7 @@ import {Credentials, Token, WebexPlugin} from '@webex/webex-core';
13
13
  import Support from '@webex/internal-plugin-support';
14
14
  import MockWebex from '@webex/test-helper-mock-webex';
15
15
  import StaticConfig from '@webex/plugin-meetings/src/common/config';
16
+ import { Defer } from '@webex/common';
16
17
  import {
17
18
  FLOOR_ACTION,
18
19
  SHARE_STATUS,
@@ -96,6 +97,7 @@ import {
96
97
  } from '../../../../src/meeting-info/meeting-info-v2';
97
98
  import {ANNOTATION_POLICY} from '../../../../src/annotation/constants';
98
99
 
100
+
99
101
  // Non-stubbed function
100
102
  const {getDisplayMedia} = Media;
101
103
 
@@ -818,27 +820,6 @@ describe('plugin-meetings', () => {
818
820
  assert.equal(result, joinMeetingResult);
819
821
  });
820
822
 
821
- it('should call updateLLMConnection upon joining if config value is set', async () => {
822
- meeting.config.enableAutomaticLLM = true;
823
- await meeting.join();
824
-
825
- assert.calledOnce(meeting.updateLLMConnection);
826
- });
827
-
828
- it('should not call updateLLMConnection upon joining if config value is not set', async () => {
829
- await meeting.join();
830
-
831
- assert.notCalled(meeting.updateLLMConnection);
832
- });
833
-
834
- it('should invoke `receiveTranscription()` if receiveTranscription is set to true', async () => {
835
- meeting.isTranscriptionSupported = sinon.stub().returns(true);
836
- meeting.receiveTranscription = sinon.stub().returns(Promise.resolve());
837
-
838
- await meeting.join({receiveTranscription: true});
839
- assert.calledOnce(meeting.receiveTranscription);
840
- });
841
-
842
823
  it('should not create new correlation ID on join immediately after create', async () => {
843
824
  await meeting.join();
844
825
  sinon.assert.notCalled(setCorrelationIdSpy);
@@ -937,6 +918,125 @@ describe('plugin-meetings', () => {
937
918
  });
938
919
  });
939
920
  });
921
+ describe('lmm and transcription decoupling', () => {
922
+ beforeEach(() => {
923
+ sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
924
+ });
925
+
926
+ describe('llm', () => {
927
+ it('makes sure that join does not wait for update llm connection promise', async () => {
928
+ const defer = new Defer();
929
+
930
+ meeting.config.enableAutomaticLLM = true;
931
+ meeting.updateLLMConnection = sinon.stub().returns(defer.promise);
932
+
933
+ const result = await meeting.join();
934
+
935
+ assert.equal(result, joinMeetingResult);
936
+
937
+ defer.resolve();
938
+ });
939
+
940
+ it('should call updateLLMConnection as part of joining if config value is set', async () => {
941
+ meeting.config.enableAutomaticLLM = true;
942
+ meeting.updateLLMConnection = sinon.stub().resolves();
943
+
944
+ await meeting.join();
945
+
946
+ assert.calledOnce(meeting.updateLLMConnection);
947
+ });
948
+
949
+ it('should not call updateLLMConnection as part of joining if config value is not set', async () => {
950
+ meeting.updateLLMConnection = sinon.stub().resolves();
951
+ await meeting.join();
952
+
953
+ assert.notCalled(meeting.updateLLMConnection);
954
+ });
955
+
956
+ it('handles catching error of llm connection later, and join still resolves', async () => {
957
+ const defer = new Defer();
958
+
959
+ meeting.config.enableAutomaticLLM = true;
960
+ meeting.updateLLMConnection = sinon.stub().returns(defer.promise);
961
+
962
+ const result = await meeting.join();
963
+
964
+ assert.equal(result, joinMeetingResult);
965
+
966
+ defer.reject(new Error("bad day", {cause: 'bad weather'}));
967
+
968
+ try {
969
+ await defer.promise;
970
+ } catch (err) {
971
+
972
+ assert.deepEqual(Metrics.sendBehavioralMetric.getCalls()[0].args, [
973
+ BEHAVIORAL_METRICS.JOIN_SUCCESS, {correlation_id: meeting.correlationId}
974
+ ])
975
+
976
+ assert.deepEqual(Metrics.sendBehavioralMetric.getCalls()[1].args, [
977
+ BEHAVIORAL_METRICS.LLM_CONNECTION_AFTER_JOIN_FAILURE, {
978
+ correlation_id: meeting.correlationId,
979
+ reason: err.message,
980
+ stack: err.stack,
981
+ }
982
+ ]);
983
+ }
984
+ });
985
+ });
986
+
987
+ describe('receive transcription', () => {
988
+ it('should invoke `receiveTranscription()` if receiveTranscription is set to true', async () => {
989
+ meeting.isTranscriptionSupported = sinon.stub().returns(true);
990
+ meeting.receiveTranscription = sinon.stub().returns(Promise.resolve());
991
+
992
+ await meeting.join({receiveTranscription: true});
993
+ assert.calledOnce(meeting.receiveTranscription);
994
+ });
995
+
996
+ it('make sure that join does not wait for setting up receive transcriptions', async () => {
997
+ const defer = new Defer();
998
+
999
+ meeting.isTranscriptionSupported = sinon.stub().returns(true);
1000
+ meeting.receiveTranscription = sinon.stub().returns(defer.promise);
1001
+
1002
+ const result = await meeting.join({receiveTranscription: true});
1003
+
1004
+ assert.equal(result, joinMeetingResult);
1005
+
1006
+ defer.resolve();
1007
+ });
1008
+
1009
+ it('handles catching error of receiveTranscription(), and join still resolves', async () => {
1010
+ const defer = new Defer();
1011
+
1012
+ meeting.isTranscriptionSupported = sinon.stub().returns(true);
1013
+ meeting.receiveTranscription = sinon.stub().returns(defer.promise);
1014
+
1015
+ const result = await meeting.join({receiveTranscription: true});
1016
+
1017
+ assert.equal(result, joinMeetingResult);
1018
+
1019
+ defer.reject(new Error("bad day", {cause: 'bad weather'}));
1020
+
1021
+ try {
1022
+ await defer.promise;
1023
+ } catch (err) {
1024
+ console.log(Metrics.sendBehavioralMetric.getCalls())
1025
+ assert.deepEqual(Metrics.sendBehavioralMetric.getCalls()[0].args, [
1026
+ BEHAVIORAL_METRICS.JOIN_SUCCESS, {correlation_id: meeting.correlationId}
1027
+ ])
1028
+
1029
+ assert.deepEqual(Metrics.sendBehavioralMetric.getCalls()[1].args, [
1030
+ BEHAVIORAL_METRICS.RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE, {
1031
+ correlation_id: meeting.correlationId,
1032
+ reason: err.message,
1033
+ stack: err.stack,
1034
+ }
1035
+ ]);
1036
+ }
1037
+ })
1038
+ })
1039
+ })
940
1040
  });
941
1041
 
942
1042
  describe('#addMedia', () => {
@@ -3484,6 +3584,219 @@ describe('plugin-meetings', () => {
3484
3584
  });
3485
3585
  });
3486
3586
 
3587
+ describe('#refreshPermissionToken', () => {
3588
+ const FAKE_MEETING_INFO = {
3589
+ conversationUrl: 'some_convo_url',
3590
+ locusUrl: 'some_locus_url',
3591
+ sipUrl: 'some_sip_url',
3592
+ meetingNumber: '123456',
3593
+ hostId: 'some_host_id',
3594
+ };
3595
+ const FAKE_MEETING_INFO_LOOKUP_URL = 'meetingLookupUrl';
3596
+ const FAKE_PERMISSION_TOKEN = {someField: 'some value'};
3597
+ const FAKE_TTL = 13;
3598
+
3599
+ beforeEach(() => {
3600
+ meeting.locusId = 'locus-id';
3601
+ meeting.id = 'meeting-id';
3602
+ meeting.config.installedOrgID = 'fake-installed-org-id';
3603
+ meeting.meetingInfo.permissionToken = FAKE_PERMISSION_TOKEN;
3604
+ meeting.destination = 'meeting-destination';
3605
+ meeting.destinationType = 'meeting-destination-type';
3606
+ meeting.updateMeetingActions = sinon.stub().returns(undefined);
3607
+ meeting.getPermissionTokenTimeLeftInSec = sinon.stub().returns(FAKE_TTL);
3608
+ meeting.meetingInfoExtraParams = {
3609
+ extraParam1: 'value1'
3610
+ };
3611
+ meeting.attrs.meetingInfoProvider = {
3612
+ fetchMeetingInfo: sinon
3613
+ .stub()
3614
+ .resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
3615
+ };
3616
+ });
3617
+
3618
+ it('resolves without doing anything if there is no permission token', async () => {
3619
+ meeting.meetingInfo.permissionToken = undefined;
3620
+
3621
+ await meeting.refreshPermissionToken();
3622
+
3623
+ assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
3624
+ assert.notCalled(Metrics.sendBehavioralMetric);
3625
+ });
3626
+
3627
+ it('calls meetingInfoProvider.fetchMeetingInfo() with the right params', async () => {
3628
+ await meeting.refreshPermissionToken('fake reason');
3629
+
3630
+ assert.calledOnceWithExactly(
3631
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3632
+ 'meeting-destination',
3633
+ 'meeting-destination-type',
3634
+ null,
3635
+ null,
3636
+ 'fake-installed-org-id',
3637
+ 'locus-id',
3638
+ {extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
3639
+ {meetingId: meeting.id, sendCAevents: true}
3640
+ );
3641
+ assert.deepEqual(meeting.meetingInfo, {
3642
+ ...FAKE_MEETING_INFO,
3643
+ meetingLookupUrl: FAKE_MEETING_INFO_LOOKUP_URL
3644
+ });
3645
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
3646
+ assert.equal(meeting.requiredCaptcha, null);
3647
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
3648
+
3649
+ assert.calledWith(
3650
+ TriggerProxy.trigger,
3651
+ meeting,
3652
+ {file: 'meetings', function: 'fetchMeetingInfo'},
3653
+ 'meeting:meetingInfoAvailable'
3654
+ );
3655
+ assert.calledWith(meeting.updateMeetingActions);
3656
+
3657
+ assert.calledWith(
3658
+ Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH, {
3659
+ correlationId: meeting.correlationId,
3660
+ timeLeft: FAKE_TTL,
3661
+ reason: 'fake reason',
3662
+ destinationType: 'meeting-destination-type',
3663
+ }
3664
+ );
3665
+ });
3666
+
3667
+ it('calls meetingInfoProvider.fetchMeetingInfo() with the right params when we are starting an instant space meeting', async () => {
3668
+ meeting.destination = 'some-convo-url';
3669
+ meeting.destinationType = 'CONVERSATION_URL';
3670
+ meeting.config.experimental = {enableAdhocMeetings: true};
3671
+ meeting.meetingInfo.meetingJoinUrl = 'meeting-join-url';
3672
+ meeting.webex.meetings.preferredWebexSite = 'preferredWebexSite';
3673
+
3674
+ await meeting.refreshPermissionToken('some reason');
3675
+
3676
+ assert.calledOnceWithExactly(
3677
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3678
+ 'meeting-join-url',
3679
+ 'MEETING_LINK',
3680
+ null,
3681
+ null,
3682
+ 'fake-installed-org-id',
3683
+ 'locus-id',
3684
+ {
3685
+ extraParam1: 'value1',
3686
+ permissionToken: FAKE_PERMISSION_TOKEN
3687
+ },
3688
+ {meetingId: meeting.id, sendCAevents: true}
3689
+ );
3690
+ assert.deepEqual(meeting.meetingInfo, {
3691
+ ...FAKE_MEETING_INFO,
3692
+ meetingLookupUrl: FAKE_MEETING_INFO_LOOKUP_URL
3693
+ });
3694
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
3695
+ assert.equal(meeting.requiredCaptcha, null);
3696
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
3697
+
3698
+ assert.calledWith(
3699
+ TriggerProxy.trigger,
3700
+ meeting,
3701
+ {file: 'meetings', function: 'fetchMeetingInfo'},
3702
+ 'meeting:meetingInfoAvailable'
3703
+ );
3704
+ assert.calledWith(meeting.updateMeetingActions);
3705
+
3706
+ assert.calledWith(
3707
+ Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH, {
3708
+ correlationId: meeting.correlationId,
3709
+ timeLeft: FAKE_TTL,
3710
+ reason: 'some reason',
3711
+ destinationType: 'MEETING_LINK',
3712
+ }
3713
+ );
3714
+ });
3715
+
3716
+ it('throws PermissionError if policy error is encountered', async () => {
3717
+ meeting.attrs.meetingInfoProvider = {
3718
+ fetchMeetingInfo: sinon
3719
+ .stub()
3720
+ .throws(new MeetingInfoV2PolicyError(123456, FAKE_MEETING_INFO, 'a message')),
3721
+ };
3722
+
3723
+ await assert.isRejected(meeting.refreshPermissionToken());
3724
+
3725
+ assert.calledOnce(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
3726
+ assert.deepEqual(meeting.meetingInfo, {
3727
+ ...FAKE_MEETING_INFO,
3728
+ });
3729
+ assert.equal(meeting.meetingInfoFailureCode, 123456);
3730
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.POLICY);
3731
+ assert.calledWith(meeting.updateMeetingActions);
3732
+
3733
+ assert.calledWith(
3734
+ Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH_ERROR, {
3735
+ correlationId: meeting.correlationId,
3736
+ reason: 'Not allowed to execute the function, some properties on server, or local client state do not allow you to complete this action.',
3737
+ stack: sinon.match.any,
3738
+ }
3739
+ );
3740
+ });
3741
+
3742
+ it('throws PasswordError if password is required', async () => {
3743
+ meeting.attrs.meetingInfoProvider = {
3744
+ fetchMeetingInfo: sinon
3745
+ .stub()
3746
+ .throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO)),
3747
+ };
3748
+
3749
+ await assert.isRejected(meeting.refreshPermissionToken());
3750
+
3751
+ assert.calledOnce(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
3752
+ assert.deepEqual(meeting.meetingInfo, {
3753
+ ...FAKE_MEETING_INFO,
3754
+ });
3755
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
3756
+ assert.equal(meeting.requiredCaptcha, null);
3757
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
3758
+ assert.calledWith(meeting.updateMeetingActions);
3759
+
3760
+ assert.calledWith(
3761
+ Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH_ERROR, {
3762
+ correlationId: meeting.correlationId,
3763
+ reason: 'Password is required, please use verifyPassword()',
3764
+ stack: sinon.match.any,
3765
+ }
3766
+ );
3767
+ });
3768
+
3769
+ it('throws CaptchaError if captcha is required', async () => {
3770
+ const FAKE_SDK_CAPTCHA_INFO = {
3771
+ captchaId: 'FAKE_CAPTCHA_ID',
3772
+ verificationImageURL: 'FAKE_CAPTCHA_IMAGE_URL',
3773
+ verificationAudioURL: 'FAKE_CAPTCHA_AUDIO_URL',
3774
+ refreshURL: 'FAKE_CAPTCHA_REFRESH_URL',
3775
+ };
3776
+ meeting.attrs.meetingInfoProvider = {
3777
+ fetchMeetingInfo: sinon
3778
+ .stub()
3779
+ .throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO)),
3780
+ };
3781
+
3782
+ await assert.isRejected(meeting.refreshPermissionToken());
3783
+
3784
+ assert.calledOnce(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
3785
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
3786
+ assert.equal(meeting.requiredCaptcha, FAKE_SDK_CAPTCHA_INFO);
3787
+ assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
3788
+ assert.calledWith(meeting.updateMeetingActions);
3789
+
3790
+ assert.calledWith(
3791
+ Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH_ERROR, {
3792
+ correlationId: meeting.correlationId,
3793
+ reason: 'Captcha is required.',
3794
+ stack: sinon.match.any,
3795
+ }
3796
+ );
3797
+ });
3798
+ });
3799
+
3487
3800
  describe('#refreshCaptcha', () => {
3488
3801
  it('fails if no captcha required', async () => {
3489
3802
  assert.isRejected(meeting.refreshCaptcha(), Error);
@@ -5506,9 +5819,9 @@ describe('plugin-meetings', () => {
5506
5819
  assert.notOk(meeting.permissionTokenPayload);
5507
5820
 
5508
5821
  const permissionTokenPayloadData = {permission: {userPolicies: {a: true}}, exp: '1234'};
5509
-
5822
+
5510
5823
  const jwtDecodeStub = sinon.stub(jwt, 'decode').returns(permissionTokenPayloadData);
5511
-
5824
+
5512
5825
  meeting.setPermissionTokenPayload();
5513
5826
 
5514
5827
  assert.calledOnce(jwtDecodeStub);
@@ -5605,7 +5918,7 @@ describe('plugin-meetings', () => {
5605
5918
  assert.equal(meeting.owner, expectedInfoToParse.owner);
5606
5919
  assert.equal(meeting.permissionToken, expectedInfoToParse.permissionToken);
5607
5920
  assert.deepEqual(meeting.selfUserPolicies, expectedInfoToParse.selfUserPolicies);
5608
-
5921
+
5609
5922
  if(expectedInfoToParse.permissionTokenPayload) {
5610
5923
  assert.deepEqual(meeting.permissionTokenPayload, expectedInfoToParse.permissionTokenPayload);
5611
5924
  }
@@ -5624,9 +5937,9 @@ describe('plugin-meetings', () => {
5624
5937
  }
5625
5938
  };
5626
5939
 
5627
- // generated permissionToken with secret `secret` and
5940
+ // generated permissionToken with secret `secret` and
5628
5941
  // value `JSON.stringify(expectedPermissionTokenPayload)`
5629
- const permissionToken =
5942
+ const permissionToken =
5630
5943
  'eyJhbGciOiJIUzI1NiJ9.eyJleHAiOiIxMjM0NTYiLCJwZXJtaXNzaW9uIjp7InVzZXJQb2xpY2llcyI6eyJhIjp0cnVlfX19.wkTk0Hp8sUlq2wi2nP4-Ym4Xb7aEUHzyXA1kzk6f0V0';
5631
5944
 
5632
5945
  const FAKE_MEETING_INFO = {
@@ -7976,20 +8289,20 @@ describe('plugin-meetings', () => {
7976
8289
  });
7977
8290
 
7978
8291
  afterEach(() => {
7979
- clock.restore();
8292
+ clock.restore();
7980
8293
  })
7981
8294
 
7982
- it('should return undefined if exp is undefined', () => {
8295
+ it('should return undefined if exp is undefined', () => {
7983
8296
  assert.equal(meeting.getPermissionTokenTimeLeftInSec(), undefined)
7984
8297
  });
7985
8298
 
7986
- it('should return the expected positive exp', () => {
8299
+ it('should return the expected positive exp', () => {
7987
8300
  // set permission token as now + 1 sec
7988
8301
  meeting.permissionTokenPayload = {exp: (now + 1000).toString()};
7989
8302
  assert.equal(meeting.getPermissionTokenTimeLeftInSec(), 1);
7990
8303
  });
7991
8304
 
7992
- it('should return the expected negative exp', () => {
8305
+ it('should return the expected negative exp', () => {
7993
8306
  // set permission token as now - 1 sec
7994
8307
  meeting.permissionTokenPayload = {exp: (now - 1000).toString()};
7995
8308
  assert.equal(meeting.getPermissionTokenTimeLeftInSec(), -1);