@webex/plugin-meetings 2.35.4 → 2.36.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.
@@ -25,6 +25,7 @@ import {
25
25
  SEND_DTMF_ENDPOINT,
26
26
  _SLIDES_
27
27
  } from '../constants';
28
+ import {Reaction} from '../reactions/reactions.type';
28
29
 
29
30
  /**
30
31
  * @class MeetingRequest
@@ -768,4 +769,26 @@ export default class MeetingRequest extends StatelessWebexPlugin {
768
769
  uri: keepAliveUrl
769
770
  });
770
771
  }
772
+
773
+ /**
774
+ * Make a network request to send a reaction.
775
+ * @param {Object} options
776
+ * @param {Url} options.reactionChannelUrl
777
+ * @param {Reaction} options.reaction
778
+ * @param {string} options.senderID
779
+ * @returns {Promise}
780
+ */
781
+ sendReaction({ reactionChannelUrl, reaction, participantId }: { reactionChannelUrl: string, reaction: Reaction, participantId: string }) {
782
+ const uri = reactionChannelUrl;
783
+
784
+ // @ts-ignore
785
+ return this.request({
786
+ method: HTTP_VERBS.POST,
787
+ uri,
788
+ body: {
789
+ sender: {participantId},
790
+ reaction,
791
+ }
792
+ });
793
+ }
771
794
  }
@@ -0,0 +1,104 @@
1
+ import {Reaction, ReactionType, SkinTone, SkinToneType} from './reactions.type';
2
+
3
+ const Reactions: Record<ReactionType, Reaction> = {
4
+ smile: {
5
+ type: 'smile',
6
+ codepoints: '1F642',
7
+ shortcodes: ':slightly_smiling_face:'
8
+ },
9
+ sad: {
10
+ type: 'sad',
11
+ codepoints: '1F625',
12
+ shortcodes: ':sad_but_relieved_face:',
13
+ },
14
+ wow: {
15
+ type: 'wow',
16
+ codepoints: '1F62E',
17
+ shortcodes: ':open_mouth:',
18
+ },
19
+ haha: {
20
+ type: 'haha',
21
+ codepoints: '1F603',
22
+ shortcodes: ':smiley:'
23
+ },
24
+ celebrate: {
25
+ type: 'celebrate',
26
+ codepoints: '1F389',
27
+ shortcodes: ':party_popper:',
28
+ },
29
+ clap: {
30
+ type: 'clap',
31
+ codepoints: '1F44F',
32
+ shortcodes: ':clap:',
33
+ },
34
+ thumbs_up: {
35
+ type: 'thumb_up',
36
+ codepoints: '1F44D',
37
+ shortcodes: ':thumbsup:',
38
+ },
39
+ thumbs_down: {
40
+ type: 'thumb_down',
41
+ codepoints: '1F44E',
42
+ shortcodes: ':thumbsdown:',
43
+ },
44
+ heart: {
45
+ type: 'heart',
46
+ codepoints: '2764',
47
+ shortcodes: ':heart:',
48
+ },
49
+ fire: {
50
+ type: 'fire',
51
+ codepoints: '1F525',
52
+ shortcodes: ':fire:',
53
+ },
54
+ prayer: {
55
+ type: 'prayer',
56
+ codepoints: '1F64F',
57
+ shortcodes: ':pray:',
58
+ },
59
+ speed_up: {
60
+ type: 'speed_up',
61
+ codepoints: '1F407',
62
+ shortcodes: ':rabbit:',
63
+ },
64
+ slow_down: {
65
+ type: 'slow_down',
66
+ codepoints: '1F422',
67
+ shortcodes: ':turtle:',
68
+ }
69
+ };
70
+
71
+ const SkinTones: Record<SkinToneType, SkinTone> = {
72
+ normal: {
73
+ type: 'normal_skin_tone',
74
+ codepoints: '',
75
+ shortcodes: '',
76
+ },
77
+ light: {
78
+ type: 'light_skin_tone',
79
+ codepoints: '1F3FB',
80
+ shortcodes: ':skin-tone-2:',
81
+ },
82
+ medium_light: {
83
+ type: 'medium_light_skin_tone',
84
+ codepoints: '1F3FC',
85
+ shortcodes: ':skin-tone-3:',
86
+ },
87
+ medium: {
88
+ type: 'medium_skin_tone',
89
+ codepoints: '1F3FD',
90
+ shortcodes: ':skin-tone-4:',
91
+ },
92
+ medium_dark: {
93
+ type: 'medium_dark_skin_tone',
94
+ codepoints: '1F3FE',
95
+ shortcodes: ':skin-tone-5:',
96
+ },
97
+ dark: {
98
+ type: 'dark_skin_tone',
99
+ codepoints: '1F3FF',
100
+ shortcodes: ':skin-tone-6:',
101
+ }
102
+ };
103
+
104
+ export {Reactions, SkinTones};
@@ -0,0 +1,36 @@
1
+
2
+ export type EmoticonData = {
3
+ type: string;
4
+ codepoints?: string;
5
+ shortcodes?: string;
6
+ }
7
+
8
+ export type SkinTone = EmoticonData;
9
+ export type Reaction = ReactionData & {
10
+ tone?: SkinTone;
11
+ }
12
+
13
+ export enum ReactionType {
14
+ smile = 'smile',
15
+ sad = 'sad',
16
+ wow = 'wow',
17
+ haha = 'haha',
18
+ celebrate = 'celebrate',
19
+ clap = 'clap',
20
+ thumbs_up = 'thumbs_up',
21
+ thumbs_down = 'thumbs_down',
22
+ heart = 'heart',
23
+ fire = 'fire',
24
+ prayer = 'prayer',
25
+ speed_up = 'speed_up',
26
+ slow_down = 'slow_down',
27
+ }
28
+
29
+ export enum SkinToneType {
30
+ normal = 'normal',
31
+ light = 'light',
32
+ medium_light = 'medium_light',
33
+ medium = 'medium',
34
+ medium_dark = 'medium_dark',
35
+ dark = 'dark',
36
+ }
@@ -715,6 +715,13 @@ describe('plugin-meetings', () => {
715
715
  height: {
716
716
  max: 200,
717
717
  ideal: 200
718
+ },
719
+ frameRate: {
720
+ ideal: 15,
721
+ max: 30
722
+ },
723
+ facingMode: {
724
+ ideal: 'user'
718
725
  }
719
726
  }
720
727
  };
@@ -1420,9 +1427,9 @@ describe('plugin-meetings', () => {
1420
1427
  });
1421
1428
  });
1422
1429
  });
1423
- describe('#share', () => {
1424
- it('should have #share', () => {
1425
- assert.exists(meeting.share);
1430
+ describe('#requestScreenShareFloor', () => {
1431
+ it('should have #requestScreenShareFloor', () => {
1432
+ assert.exists(meeting.requestScreenShareFloor);
1426
1433
  });
1427
1434
  beforeEach(() => {
1428
1435
  meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
@@ -1430,7 +1437,7 @@ describe('plugin-meetings', () => {
1430
1437
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
1431
1438
  });
1432
1439
  it('should send the share', async () => {
1433
- const share = meeting.share();
1440
+ const share = meeting.requestScreenShareFloor();
1434
1441
 
1435
1442
  assert.exists(share.then);
1436
1443
  await share;
@@ -3457,22 +3464,34 @@ describe('plugin-meetings', () => {
3457
3464
  sandbox = null;
3458
3465
  });
3459
3466
 
3460
- describe('#stopFloorRequest', () => {
3461
- it('should have #stopFloorRequest', () => {
3462
- assert.exists(meeting.stopFloorRequest);
3467
+ describe('#releaseScreenShareFloor', () => {
3468
+ it('should have #releaseScreenShareFloor', () => {
3469
+ assert.exists(meeting.releaseScreenShareFloor);
3463
3470
  });
3464
3471
  beforeEach(() => {
3465
- meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
3472
+ meeting.selfId = 'some self id';
3473
+ meeting.locusInfo.mediaShares = [{name: 'content', url: url1, floor: {beneficiary: {id: meeting.selfId}}}];
3466
3474
  meeting.locusInfo.self = {url: url2};
3475
+ meeting.mediaProperties = {mediaDirection: {sendShare: true}};
3467
3476
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
3468
3477
  });
3469
- it('should call change meeting floor', async () => {
3470
- const share = meeting.share();
3478
+ it('should call changeMeetingFloor()', async () => {
3479
+ const share = meeting.releaseScreenShareFloor();
3471
3480
 
3472
3481
  assert.exists(share.then);
3473
3482
  await share;
3474
3483
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
3475
3484
  });
3485
+ it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
3486
+ // change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
3487
+ meeting.selfId = 'new self id';
3488
+
3489
+ const share = meeting.releaseScreenShareFloor();
3490
+
3491
+ assert.exists(share.then);
3492
+ await share;
3493
+ assert.notCalled(meeting.meetingRequest.changeMeetingFloor);
3494
+ });
3476
3495
  });
3477
3496
 
3478
3497
  describe('#setSipUri', () => {
@@ -4003,7 +4022,7 @@ describe('plugin-meetings', () => {
4003
4022
  if (newPayload.previous.content.beneficiaryId === USER_IDS.ME) {
4004
4023
  eventTrigger.share.push({
4005
4024
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4006
- functionName: 'stopFloorRequest'
4025
+ functionName: 'localShare'
4007
4026
  });
4008
4027
  }
4009
4028
  else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
@@ -4054,7 +4073,7 @@ describe('plugin-meetings', () => {
4054
4073
  if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
4055
4074
  eventTrigger.share.push({
4056
4075
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4057
- functionName: 'stopFloorRequest'
4076
+ functionName: 'localShare'
4058
4077
  });
4059
4078
  }
4060
4079
  else {
@@ -4073,7 +4092,7 @@ describe('plugin-meetings', () => {
4073
4092
  if (newPayload.previous.content.beneficiaryId === USER_IDS.ME) {
4074
4093
  eventTrigger.share.push({
4075
4094
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4076
- functionName: 'stopFloorRequest'
4095
+ functionName: 'localShare'
4077
4096
  });
4078
4097
  }
4079
4098
  else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
@@ -4116,7 +4135,7 @@ describe('plugin-meetings', () => {
4116
4135
  if (beneficiaryId === USER_IDS.ME) {
4117
4136
  eventTrigger.share.push({
4118
4137
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4119
- functionName: 'stopFloorRequest'
4138
+ functionName: 'localShare'
4120
4139
  });
4121
4140
  }
4122
4141
  else {
@@ -4582,6 +4601,93 @@ describe('plugin-meetings', () => {
4582
4601
  meeting.stopKeepAlive();
4583
4602
  });
4584
4603
  });
4604
+
4605
+ describe('#sendReaction', () => {
4606
+ it('should have #sendReaction', () => {
4607
+ assert.exists(meeting.sendReaction);
4608
+ });
4609
+
4610
+ beforeEach(() => {
4611
+ meeting.meetingRequest.sendReaction = sinon.stub().returns(Promise.resolve());
4612
+ });
4613
+
4614
+ it('should send reaction with the right data and return a promise', async () => {
4615
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
4616
+
4617
+ const reactionPromise = meeting.sendReaction('thumbs_down', 'light');
4618
+
4619
+ assert.exists(reactionPromise.then);
4620
+ await reactionPromise;
4621
+ assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
4622
+ reactionChannelUrl: 'Fake URL',
4623
+ reaction: {
4624
+ type: 'thumb_down',
4625
+ codepoints: '1F44E',
4626
+ shortcodes: ':thumbsdown:',
4627
+ tone: {
4628
+ type: 'light_skin_tone',
4629
+ codepoints: '1F3FB',
4630
+ shortcodes: ':skin-tone-2:'
4631
+ }
4632
+ },
4633
+ participantId: meeting.members.selfId,
4634
+ });
4635
+ });
4636
+
4637
+ it('should fail sending a reaction if data channel is undefined', async () => {
4638
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: undefined}};
4639
+
4640
+ await assert.isRejected(meeting.sendReaction('thumbs_down', 'light'), Error, 'Error sending reaction, service url not found.');
4641
+
4642
+ assert.notCalled(meeting.meetingRequest.sendReaction);
4643
+ });
4644
+
4645
+ it('should fail sending a reaction if reactionType is invalid ', async () => {
4646
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
4647
+
4648
+ await assert.isRejected(meeting.sendReaction('invalid_reaction', 'light'), Error, 'invalid_reaction is not a valid reaction.');
4649
+
4650
+ assert.notCalled(meeting.meetingRequest.sendReaction);
4651
+ });
4652
+
4653
+ it('should send a reaction with default skin tone if provided skinToneType is invalid ', async () => {
4654
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
4655
+
4656
+ const reactionPromise = meeting.sendReaction('thumbs_down', 'invalid_skin_tone');
4657
+
4658
+ assert.exists(reactionPromise.then);
4659
+ await reactionPromise;
4660
+ assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
4661
+ reactionChannelUrl: 'Fake URL',
4662
+ reaction: {
4663
+ type: 'thumb_down',
4664
+ codepoints: '1F44E',
4665
+ shortcodes: ':thumbsdown:',
4666
+ tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''}
4667
+ },
4668
+ participantId: meeting.members.selfId,
4669
+ });
4670
+ });
4671
+
4672
+ it('should send a reaction with default skin tone if none provided', async () => {
4673
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
4674
+
4675
+ const reactionPromise = meeting.sendReaction('thumbs_down');
4676
+
4677
+ assert.exists(reactionPromise.then);
4678
+ await reactionPromise;
4679
+ assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
4680
+ reactionChannelUrl: 'Fake URL',
4681
+ reaction: {
4682
+ type: 'thumb_down',
4683
+ codepoints: '1F44E',
4684
+ shortcodes: ':thumbsdown:',
4685
+ tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''}
4686
+ },
4687
+ participantId: meeting.members.selfId,
4688
+ });
4689
+ });
4690
+ });
4585
4691
  });
4586
4692
  });
4587
4693
  });
@@ -1,7 +1,6 @@
1
1
  import sinon from 'sinon';
2
2
  import {assert} from '@webex/test-helper-chai';
3
3
  import MockWebex from '@webex/test-helper-mock-webex';
4
-
5
4
  import Meetings from '@webex/plugin-meetings';
6
5
  import MeetingRequest from '@webex/plugin-meetings/src/meeting/request';
7
6
 
@@ -278,5 +277,30 @@ describe('plugin-meetings', () => {
278
277
  assert.equal(requestParams.uri, keepAliveUrl);
279
278
  });
280
279
  });
280
+
281
+ describe('#sendReaction', () => {
282
+ it('sends request to sendReaction', async () => {
283
+ const reactionChannelUrl = 'reactionChannelUrl';
284
+ const participantId = 'participantId';
285
+ const reaction = {
286
+ type: 'thumb_down',
287
+ codepoints: '1F44E',
288
+ shortcodes: ':thumbsdown:',
289
+ tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''}
290
+ };
291
+
292
+ await meetingsRequest.sendReaction({
293
+ reactionChannelUrl,
294
+ reaction,
295
+ participantId
296
+ });
297
+ const requestParams = meetingsRequest.request.getCall(0).args[0];
298
+
299
+ assert.equal(requestParams.method, 'POST');
300
+ assert.equal(requestParams.uri, reactionChannelUrl);
301
+ assert.equal(requestParams.body.sender.participantId, participantId);
302
+ assert.equal(requestParams.body.reaction, reaction);
303
+ });
304
+ });
281
305
  });
282
306
  });