@webex/plugin-meetings 2.14.2 → 2.15.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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/common/errors/parameter.js +1 -1
  3. package/dist/common/errors/parameter.js.map +1 -1
  4. package/dist/constants.js +8 -4
  5. package/dist/constants.js.map +1 -1
  6. package/dist/locus-info/index.js +7 -0
  7. package/dist/locus-info/index.js.map +1 -1
  8. package/dist/locus-info/infoUtils.js +4 -2
  9. package/dist/locus-info/infoUtils.js.map +1 -1
  10. package/dist/locus-info/selfUtils.js +14 -0
  11. package/dist/locus-info/selfUtils.js.map +1 -1
  12. package/dist/meeting/in-meeting-actions.js +3 -1
  13. package/dist/meeting/in-meeting-actions.js.map +1 -1
  14. package/dist/meeting/index.js +164 -118
  15. package/dist/meeting/index.js.map +1 -1
  16. package/dist/meeting/util.js +4 -0
  17. package/dist/meeting/util.js.map +1 -1
  18. package/dist/metrics/constants.js +5 -1
  19. package/dist/metrics/constants.js.map +1 -1
  20. package/package.json +6 -6
  21. package/src/common/errors/parameter.js +1 -1
  22. package/src/constants.js +3 -0
  23. package/src/locus-info/index.js +10 -0
  24. package/src/locus-info/infoUtils.js +11 -3
  25. package/src/locus-info/selfUtils.js +12 -0
  26. package/src/meeting/in-meeting-actions.js +2 -0
  27. package/src/meeting/index.js +106 -52
  28. package/src/meeting/util.js +2 -0
  29. package/src/metrics/constants.js +5 -1
  30. package/test/unit/spec/fixture/locus.js +404 -0
  31. package/test/unit/spec/locus-info/index.js +27 -0
  32. package/test/unit/spec/locus-info/infoUtils.js +17 -10
  33. package/test/unit/spec/locus-info/selfConstant.js +1 -0
  34. package/test/unit/spec/meeting/in-meeting-actions.js +2 -0
  35. package/test/unit/spec/meeting/index.js +230 -0
  36. package/test/unit/spec/meeting/utils.js +7 -0
@@ -33,22 +33,29 @@ describe('plugin-meetings', () => {
33
33
  it('only gives includes display hints when user has the correct role', () => {
34
34
  assert.deepEqual(InfoUtils.parse(info, ['MODERATOR']), {
35
35
  policy: {HINT_3: true},
36
- moderator: {HINT_1: true, HINT_2: true},
37
- coHost: {HINT_4: true},
38
- userDisplayHints: ['HINT_3', 'HINT_1', 'HINT_2']
36
+ moderator: {HINT_1: true, HINT_2: true, LOWER_SOMEONE_ELSES_HAND: true},
37
+ coHost: {HINT_4: true, LOWER_SOMEONE_ELSES_HAND: true},
38
+ userDisplayHints: ['HINT_3', 'HINT_1', 'HINT_2', 'LOWER_SOMEONE_ELSES_HAND']
39
+ });
40
+
41
+ assert.deepEqual(InfoUtils.parse(info, ['MODERATOR', 'COHOST']), {
42
+ policy: {HINT_3: true},
43
+ moderator: {HINT_1: true, HINT_2: true, LOWER_SOMEONE_ELSES_HAND: true},
44
+ coHost: {HINT_4: true, LOWER_SOMEONE_ELSES_HAND: true},
45
+ userDisplayHints: ['HINT_3', 'HINT_4', 'LOWER_SOMEONE_ELSES_HAND', 'HINT_1', 'HINT_2']
39
46
  });
40
47
 
41
48
  assert.deepEqual(InfoUtils.parse(info, ['COHOST']), {
42
49
  policy: {HINT_3: true},
43
- moderator: {HINT_1: true, HINT_2: true},
44
- coHost: {HINT_4: true},
45
- userDisplayHints: ['HINT_3', 'HINT_4']
50
+ moderator: {HINT_1: true, HINT_2: true, LOWER_SOMEONE_ELSES_HAND: true},
51
+ coHost: {HINT_4: true, LOWER_SOMEONE_ELSES_HAND: true},
52
+ userDisplayHints: ['HINT_3', 'HINT_4', 'LOWER_SOMEONE_ELSES_HAND']
46
53
  });
47
54
 
48
55
  assert.deepEqual(InfoUtils.parse(info, []), {
49
56
  policy: {HINT_3: true},
50
- moderator: {HINT_1: true, HINT_2: true},
51
- coHost: {HINT_4: true},
57
+ moderator: {HINT_1: true, HINT_2: true, LOWER_SOMEONE_ELSES_HAND: true},
58
+ coHost: {HINT_4: true, LOWER_SOMEONE_ELSES_HAND: true},
52
59
  userDisplayHints: ['HINT_3']
53
60
  });
54
61
  });
@@ -99,7 +106,7 @@ describe('plugin-meetings', () => {
99
106
 
100
107
  assert.calledWith(parseDisplayHintSectionSpy, info, 'moderator');
101
108
 
102
- assert.deepEqual(result, parseDisplayHintSectionSpy.firstCall.returnValue);
109
+ assert.deepEqual(result, {...parseDisplayHintSectionSpy.firstCall.returnValue, LOWER_SOMEONE_ELSES_HAND: true});
103
110
  });
104
111
 
105
112
  it('parsePolicy calls parseDisplayHintSection correctly and returns the result', () => {
@@ -115,7 +122,7 @@ describe('plugin-meetings', () => {
115
122
 
116
123
  assert.calledWith(parseDisplayHintSectionSpy, info, 'coHost');
117
124
 
118
- assert.deepEqual(result, parseDisplayHintSectionSpy.firstCall.returnValue);
125
+ assert.deepEqual(result, {...parseDisplayHintSectionSpy.firstCall.returnValue, LOWER_SOMEONE_ELSES_HAND: true});
119
126
  });
120
127
  });
121
128
  });
@@ -66,6 +66,7 @@ export const self = {
66
66
  ],
67
67
  mediaSessionsExternal: false,
68
68
  state: 'JOINED',
69
+ intent: {type: ''},
69
70
  intents: [
70
71
  null
71
72
  ],
@@ -16,6 +16,7 @@ describe('plugin-meetings', () => {
16
16
  canStopRecording: null,
17
17
  canRaiseHand: null,
18
18
  canLowerAllHands: null,
19
+ canLowerSomeoneElsesHand: null,
19
20
  ...expected
20
21
  };
21
22
 
@@ -38,6 +39,7 @@ describe('plugin-meetings', () => {
38
39
  'canStopRecording',
39
40
  'canRaiseHand',
40
41
  'canLowerAllHands',
42
+ 'canLowerSomeoneElsesHand',
41
43
  ].forEach((key) => {
42
44
  it(`get and set for ${key} work as expected`, () => {
43
45
  const inMeetingActions = new InMeetingActions();
@@ -42,6 +42,7 @@ import {
42
42
  } from '@webex/plugin-meetings/src/constants';
43
43
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
44
44
 
45
+ import locus from '../fixture/locus';
45
46
  import {
46
47
  UserNotJoinedError,
47
48
  MeetingNotActiveError,
@@ -785,6 +786,7 @@ describe('plugin-meetings', () => {
785
786
  beforeEach(() => {
786
787
  meeting.setCorrelationId = sinon.stub().returns(true);
787
788
  meeting.setLocus = sinon.stub().returns(true);
789
+ webex.meetings.registered = true;
788
790
  });
789
791
  describe('successful', () => {
790
792
  beforeEach(() => {
@@ -853,6 +855,14 @@ describe('plugin-meetings', () => {
853
855
  assert.calledWith(MeetingUtil.joinMeeting, meeting, {moderator: false, pin: '1234'});
854
856
  });
855
857
  });
858
+
859
+ it('should throw error if device is not registered', async () => {
860
+ webex.meetings.registered = false;
861
+ await meeting.join().catch((error) => {
862
+ assert.equal(error.message, 'Meeting:index#join --> Device not registered');
863
+ });
864
+ });
865
+
856
866
  it('should post error event if failed', async () => {
857
867
  await meeting.join().catch(() => {
858
868
  assert.calledWithMatch(Metrics.postEvent, {event: eventType.LOCUS_JOIN_RESPONSE});
@@ -2561,6 +2571,7 @@ describe('plugin-meetings', () => {
2561
2571
  assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
2562
2572
  });
2563
2573
  });
2574
+
2564
2575
  describe('#mediaNegotiatedEvent', () => {
2565
2576
  it('should have #mediaNegotiatedEvent', () => {
2566
2577
  assert.exists(meeting.mediaNegotiatedEvent);
@@ -2638,6 +2649,222 @@ describe('plugin-meetings', () => {
2638
2649
  assert.calledOnce(meeting?.roap?.stop);
2639
2650
  });
2640
2651
  });
2652
+
2653
+ describe('#moveTo', () => {
2654
+ let sandbox;
2655
+
2656
+ beforeEach(() => {
2657
+ sandbox = sinon.createSandbox();
2658
+ sandbox.stub(meeting, 'closeLocalStream');
2659
+ sandbox.stub(meeting, 'closeLocalShare');
2660
+
2661
+ sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
2662
+ sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
2663
+
2664
+ sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
2665
+ sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}})));
2666
+ });
2667
+
2668
+ afterEach(() => {
2669
+ sandbox.restore();
2670
+ sandbox = null;
2671
+ });
2672
+
2673
+ it('should throw an error if resourceId not passed', async () => {
2674
+ try {
2675
+ await meeting.moveTo();
2676
+ }
2677
+ catch (err) {
2678
+ assert.instanceOf(err, ParameterError);
2679
+ assert.equal(err.sdkMessage, 'Cannot move call without a resourceId.');
2680
+ }
2681
+ });
2682
+
2683
+ it('should postEvent on moveTo ', async () => {
2684
+ await meeting.moveTo('resourceId');
2685
+ assert.calledWithMatch(Metrics.postEvent, {
2686
+ event: eventType.MEDIA_CAPABILITIES,
2687
+ data: {
2688
+ mediaCapabilities: {
2689
+ rx: {
2690
+ audio: false,
2691
+ share: true,
2692
+ share_audio: false,
2693
+ video: false,
2694
+ whiteboard: false
2695
+ },
2696
+ tx: {
2697
+ audio: false,
2698
+ share: false,
2699
+ share_audio: false,
2700
+ video: false,
2701
+ whiteboard: false
2702
+ }
2703
+ }
2704
+ }
2705
+ });
2706
+ assert.calledWithMatch(Metrics.postEvent, {event: eventType.MOVE_MEDIA});
2707
+ });
2708
+
2709
+ it('should call `MeetingUtil.joinMeetingOptions` with resourceId', async () => {
2710
+ sinon.spy(MeetingUtil, 'joinMeetingOptions');
2711
+ await meeting.moveTo('resourceId');
2712
+
2713
+ assert.calledWith(MeetingUtil.joinMeetingOptions, meeting, {resourceId: 'resourceId', moveToResource: true});
2714
+ });
2715
+
2716
+ it('should reconnectMedia after DX joins after moveTo', async () => {
2717
+ await meeting.moveTo('resourceId');
2718
+
2719
+
2720
+ await meeting.locusInfo.emitScoped(
2721
+ {
2722
+ file: 'locus-info',
2723
+ function: 'updateSelf'
2724
+ },
2725
+ 'SELF_OBSERVING'
2726
+ );
2727
+
2728
+ // beacuse we are calling callback so we need to wait
2729
+
2730
+ assert.called(meeting.closeLocalStream);
2731
+ assert.called(meeting.closeLocalShare);
2732
+
2733
+ // give queued Promise callbacks a chance to run
2734
+ await Promise.resolve();
2735
+
2736
+ assert.called(meeting.mediaProperties.setMediaDirection);
2737
+ assert.called(meeting.mediaProperties.unsetMediaTracks);
2738
+
2739
+ assert.calledWith(meeting.reconnectionManager.reconnectMedia,
2740
+ {
2741
+ mediaDirection: {
2742
+ sendVideo: false,
2743
+ receiveVideo: false,
2744
+ sendAudio: false,
2745
+ receiveAudio: false,
2746
+ sendShare: false,
2747
+ receiveShare: true
2748
+ }
2749
+ });
2750
+ });
2751
+
2752
+ it('should throw an error if moveTo call fails', async () => {
2753
+ MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
2754
+ try {
2755
+ await meeting.moveTo('resourceId');
2756
+ }
2757
+ catch {
2758
+ assert.calledOnce(Metrics.sendBehavioralMetric);
2759
+ assert.calledWith(
2760
+ Metrics.sendBehavioralMetric,
2761
+ BEHAVIORAL_METRICS.MOVE_TO_FAILURE,
2762
+ {
2763
+ correlation_id: meeting.correlationId,
2764
+ locus_id: meeting.locusUrl.split('/').pop(),
2765
+ reason: sinon.match.any,
2766
+ stack: sinon.match.any
2767
+ }
2768
+ );
2769
+ }
2770
+ Metrics.sendBehavioralMetric.reset();
2771
+ meeting.reconnectionManager.reconnectMedia = sinon.stub().returns(Promise.reject());
2772
+ try {
2773
+ await meeting.moveTo('resourceId');
2774
+
2775
+ await meeting.locusInfo.emitScoped(
2776
+ {
2777
+ file: 'locus-info',
2778
+ function: 'updateSelf'
2779
+ },
2780
+ 'SELF_OBSERVING'
2781
+ );
2782
+ }
2783
+ catch {
2784
+ assert.calledOnce(Metrics.sendBehavioralMetric);
2785
+ assert.calledWith(
2786
+ Metrics.sendBehavioralMetric,
2787
+ BEHAVIORAL_METRICS.MOVE_TO_FAILURE,
2788
+ {
2789
+ correlation_id: meeting.correlationId,
2790
+ locus_id: meeting.locusUrl.split('/').pop(),
2791
+ reason: sinon.match.any,
2792
+ stack: sinon.match.any
2793
+ }
2794
+ );
2795
+ }
2796
+ });
2797
+ });
2798
+
2799
+ describe('#moveFrom', () => {
2800
+ let sandbox;
2801
+
2802
+ beforeEach(() => {
2803
+ sandbox = sinon.createSandbox();
2804
+ sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}})));
2805
+ sandbox.stub(MeetingUtil, 'leaveMeeting').returns(Promise.resolve());
2806
+ });
2807
+
2808
+ afterEach(() => {
2809
+ sandbox.restore();
2810
+ sandbox = null;
2811
+ });
2812
+
2813
+ it('should throw an error if resourceId not passed', async () => {
2814
+ try {
2815
+ await meeting.moveFrom();
2816
+ }
2817
+ catch (err) {
2818
+ assert.instanceOf(err, ParameterError);
2819
+
2820
+ assert.equal(err.sdkMessage, 'Cannot move call without a resourceId.');
2821
+ }
2822
+ });
2823
+
2824
+ it('should postEvent on moveFrom ', async () => {
2825
+ await meeting.moveFrom('resourceId');
2826
+
2827
+ assert.calledWithMatch(Metrics.postEvent, {event: eventType.MOVE_MEDIA});
2828
+ });
2829
+
2830
+ it('should call `MeetingUtil.joinMeetingOptions` with resourceId', async () => {
2831
+ sinon.spy(MeetingUtil, 'joinMeetingOptions');
2832
+ await meeting.moveFrom('resourceId');
2833
+
2834
+ assert.calledWith(MeetingUtil.joinMeetingOptions, meeting);
2835
+ assert.calledWith(MeetingUtil.leaveMeeting, meeting, {
2836
+ resourceId: 'resourceId',
2837
+ correlationId: meeting.correlationId,
2838
+ moveMeeting: true
2839
+ });
2840
+
2841
+ assert.calledOnce(Metrics.sendBehavioralMetric);
2842
+ assert.calledWith(
2843
+ Metrics.sendBehavioralMetric,
2844
+ BEHAVIORAL_METRICS.MOVE_FROM_SUCCESS,
2845
+ );
2846
+ });
2847
+
2848
+ it('should throw an error if moveFrom call fails', async () => {
2849
+ MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
2850
+ try {
2851
+ await meeting.moveFrom('resourceId');
2852
+ }
2853
+ catch {
2854
+ assert.calledOnce(Metrics.sendBehavioralMetric);
2855
+ assert.calledWith(
2856
+ Metrics.sendBehavioralMetric,
2857
+ BEHAVIORAL_METRICS.MOVE_FROM_FAILURE,
2858
+ {
2859
+ correlation_id: meeting.correlationId,
2860
+ locus_id: meeting.locusUrl.split('/').pop(),
2861
+ reason: sinon.match.any,
2862
+ stack: sinon.match.any
2863
+ }
2864
+ );
2865
+ }
2866
+ });
2867
+ });
2641
2868
  });
2642
2869
 
2643
2870
  describe('Public Event Triggers', () => {
@@ -3316,6 +3543,7 @@ describe('plugin-meetings', () => {
3316
3543
  let canUserResumeSpy;
3317
3544
  let canUserRaiseHandSpy;
3318
3545
  let canUserLowerAllHandsSpy;
3546
+ let canUserLowerSomeoneElsesHandSpy;
3319
3547
 
3320
3548
 
3321
3549
  beforeEach(() => {
@@ -3329,6 +3557,7 @@ describe('plugin-meetings', () => {
3329
3557
  inMeetingActionsSetSpy = sinon.spy(meeting.inMeetingActions, 'set');
3330
3558
  canUserRaiseHandSpy = sinon.spy(MeetingUtil, 'canUserRaiseHand');
3331
3559
  canUserLowerAllHandsSpy = sinon.spy(MeetingUtil, 'canUserLowerAllHands');
3560
+ canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
3332
3561
  });
3333
3562
 
3334
3563
  afterEach(() => {
@@ -3363,6 +3592,7 @@ describe('plugin-meetings', () => {
3363
3592
  assert.calledWith(canUserResumeSpy, payload.info.userDisplayHints);
3364
3593
  assert.calledWith(canUserRaiseHandSpy, payload.info.userDisplayHints);
3365
3594
  assert.calledWith(canUserLowerAllHandsSpy, payload.info.userDisplayHints);
3595
+ assert.calledWith(canUserLowerSomeoneElsesHandSpy, payload.info.userDisplayHints);
3366
3596
 
3367
3597
  assert.calledWith(
3368
3598
  TriggerProxy.trigger,
@@ -228,6 +228,13 @@ describe('plugin-meetings', () => {
228
228
  });
229
229
  });
230
230
 
231
+ describe('canUserLowerSomeoneElsesHand', () => {
232
+ it('works as expected', () => {
233
+ assert.deepEqual(MeetingUtil.canUserLowerSomeoneElsesHand(['LOWER_SOMEONE_ELSES_HAND']), true);
234
+ assert.deepEqual(MeetingUtil.canUserLowerSomeoneElsesHand([]), false);
235
+ });
236
+ });
237
+
231
238
  describe('canUserLock', () => {
232
239
  it('works as expected', () => {
233
240
  assert.deepEqual(MeetingUtil.canUserLock(['LOCK_CONTROL_LOCK', 'LOCK_STATUS_UNLOCKED']), true);