@webex/plugin-meetings 3.9.0-next.13 → 3.9.0-next.15

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.
@@ -74,7 +74,11 @@ import {Invitee} from '../meeting/type';
74
74
  * @memberof Members
75
75
  */
76
76
 
77
- type UpdatedMembers = {added: Array<Member>; updated: Array<Member>};
77
+ type UpdatedMembers = {
78
+ added: Array<Member>;
79
+ updated: Array<Member>;
80
+ removedIds?: Array<string>; // removed member ids
81
+ };
78
82
  /**
79
83
  * @class Members
80
84
  */
@@ -384,12 +388,18 @@ export default class Members extends StatelessWebexPlugin {
384
388
  * when new participant updates come in, both delta and full participants, update them in members collection
385
389
  * delta object in the event will have {updated, added} and full will be the full membersCollection
386
390
  * @param {Object} payload
387
- * @param {Object} payload.participants
391
+ * @param {Object} payload.participants new/updated participants
392
+ * @param {Boolean} payload.isReplace whether to replace the whole members collection
393
+ * @param {Object} payload.removedParticipantIds ids of the removed participants
388
394
  * @returns {undefined}
389
395
  * @private
390
396
  * @memberof Members
391
397
  */
392
- locusParticipantsUpdate(payload: {participants: object; isReplace?: boolean}) {
398
+ locusParticipantsUpdate(payload: {
399
+ participants: object;
400
+ isReplace?: boolean;
401
+ removedParticipantIds?: Array<string>;
402
+ }) {
393
403
  if (payload) {
394
404
  if (payload.isReplace) {
395
405
  this.clearMembers();
@@ -547,10 +557,22 @@ export default class Members extends StatelessWebexPlugin {
547
557
  private handleMembersUpdate(membersUpdate: UpdatedMembers) {
548
558
  this.constructMembers(membersUpdate.updated, true);
549
559
  this.constructMembers(membersUpdate.added, false);
560
+ this.removeMembers(membersUpdate.removedIds);
550
561
 
551
562
  return this.membersCollection.getAll();
552
563
  }
553
564
 
565
+ /**
566
+ * removes members from the collection
567
+ * @param {Array<string>} removedMembers removed members ids
568
+ * @returns {void}
569
+ */
570
+ private removeMembers(removedMembers: Array<string>) {
571
+ removedMembers.forEach((memberId) => {
572
+ this.membersCollection.remove(memberId);
573
+ });
574
+ }
575
+
554
576
  /**
555
577
  * set members to the member collection from each updated/added lists as passed in
556
578
  * @param {Array} list
@@ -600,6 +622,10 @@ export default class Members extends StatelessWebexPlugin {
600
622
  }
601
623
  const memberUpdate = this.update(payload.participants);
602
624
 
625
+ // this code depends on memberIds being the same as participantIds
626
+ // if MemberUtil.extractId() ever changes, this will need to be updated
627
+ memberUpdate.removedIds = payload.removedParticipantIds || [];
628
+
603
629
  return memberUpdate;
604
630
  }
605
631
 
@@ -772,7 +772,7 @@ describe('plugin-meetings', () => {
772
772
  },
773
773
  };
774
774
  locusInfo.emitScoped = sinon.stub();
775
- locusInfo.updateParticipants({});
775
+ locusInfo.updateParticipants({}, []);
776
776
 
777
777
  // if this assertion fails, double-check the attributes used in
778
778
  // the updateParticipants function in locus-info/index.js
@@ -790,6 +790,7 @@ describe('plugin-meetings', () => {
790
790
  selfId: '2',
791
791
  hostId: '3',
792
792
  isReplace: undefined,
793
+ removedParticipantIds: [],
793
794
  }
794
795
  );
795
796
  // note: in a real use case, recordingId, selfId, and hostId would all be the same
@@ -814,7 +815,7 @@ describe('plugin-meetings', () => {
814
815
  };
815
816
 
816
817
  locusInfo.emitScoped = sinon.stub();
817
- locusInfo.updateParticipants({}, true);
818
+ locusInfo.updateParticipants({}, [], true);
818
819
 
819
820
  assert.calledWith(
820
821
  locusInfo.emitScoped,
@@ -830,6 +831,7 @@ describe('plugin-meetings', () => {
830
831
  selfId: '2',
831
832
  hostId: '3',
832
833
  isReplace: true,
834
+ removedParticipantIds: [],
833
835
  }
834
836
  );
835
837
  });
@@ -847,7 +849,7 @@ describe('plugin-meetings', () => {
847
849
  ];
848
850
 
849
851
  locusInfo.emitScoped = sinon.stub();
850
- locusInfo.updateParticipants(failureParticipant);
852
+ locusInfo.updateParticipants(failureParticipant, []);
851
853
  assert.calledWith(
852
854
  locusInfo.emitScoped,
853
855
  {
@@ -2050,7 +2052,7 @@ describe('plugin-meetings', () => {
2050
2052
 
2051
2053
  fakeLocus = {
2052
2054
  meeting: true,
2053
- participants: true,
2055
+ participants: [],
2054
2056
  url: 'newLocusUrl',
2055
2057
  syncUrl: 'newSyncUrl',
2056
2058
  };
@@ -2525,7 +2527,7 @@ describe('plugin-meetings', () => {
2525
2527
  });
2526
2528
 
2527
2529
  it('onDeltaLocus handle delta data', () => {
2528
- fakeLocus.participants = {};
2530
+ fakeLocus.participants = [];
2529
2531
  const fakeBreakout = {
2530
2532
  sessionId: 'sessionId',
2531
2533
  groupId: 'groupId',
@@ -2542,11 +2544,11 @@ describe('plugin-meetings', () => {
2542
2544
  };
2543
2545
  locusInfo.updateParticipants = sinon.stub();
2544
2546
  locusInfo.onDeltaLocus(fakeLocus);
2545
- assert.calledWith(locusInfo.updateParticipants, {}, false);
2547
+ assert.calledWith(locusInfo.updateParticipants, [], undefined, false);
2546
2548
 
2547
2549
  fakeLocus.controls.breakout.sessionId = 'sessionId2';
2548
2550
  locusInfo.onDeltaLocus(fakeLocus);
2549
- assert.calledWith(locusInfo.updateParticipants, {}, true);
2551
+ assert.calledWith(locusInfo.updateParticipants, [], undefined, true);
2550
2552
  });
2551
2553
 
2552
2554
  it('onDeltaLocus merges delta participants with existing participants', () => {
@@ -2563,7 +2565,7 @@ describe('plugin-meetings', () => {
2563
2565
  existingParticipants,
2564
2566
  FAKE_DELTA_PARTICIPANTS
2565
2567
  );
2566
- assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, false);
2568
+ assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, undefined, false);
2567
2569
  });
2568
2570
 
2569
2571
  [true, false].forEach((isDelta) =>
@@ -3074,6 +3076,7 @@ describe('plugin-meetings', () => {
3074
3076
  id: 'test person id',
3075
3077
  },
3076
3078
  },
3079
+ participants: [],
3077
3080
  });
3078
3081
 
3079
3082
  updateLocusInfoStub.resetHistory();
@@ -14819,4 +14819,30 @@ describe('plugin-meetings', () => {
14819
14819
  );
14820
14820
  });
14821
14821
  });
14822
+
14823
+ describe('#notifyHost', () => {
14824
+ beforeEach(() => {
14825
+ meeting.meetingRequest.notifyHost = sinon.stub().returns(Promise.resolve());
14826
+ });
14827
+
14828
+ it('sends the expected request', async () => {
14829
+ meeting.meetingInfo.siteFullUrl = `convergedats.webex.com`;
14830
+ const meetingUuid = 'meeting-uuid';
14831
+ const displayName = ['Test', 'User'];
14832
+ meeting.locusId = 'locusId';
14833
+
14834
+ const notifyHostPromise = meeting.notifyHost(meetingUuid, displayName);
14835
+
14836
+ assert.exists(notifyHostPromise.then);
14837
+ await notifyHostPromise;
14838
+
14839
+ assert.calledOnceWithExactly(
14840
+ meeting.meetingRequest.notifyHost,
14841
+ meeting.meetingInfo.siteFullUrl,
14842
+ meeting.locusId,
14843
+ meetingUuid,
14844
+ displayName,
14845
+ );
14846
+ });
14847
+ });
14822
14848
  });
@@ -897,4 +897,25 @@ describe('plugin-meetings', () => {
897
897
  });
898
898
  });
899
899
  });
900
+
901
+ describe('#notifyHost', () => {
902
+ it('check locus status', async () => {
903
+ const siteFullUrl = 'ats062222cvg.webex.com';
904
+ const locusId = 'locusId';
905
+ const meetingUuid = 'meetingUuid';
906
+ const displayName = ['user1', 'user2'];
907
+ await meetingsRequest.notifyHost(siteFullUrl, locusId, meetingUuid, displayName);
908
+ assert.deepEqual(meetingsRequest.request.getCall(0).args[0], {
909
+ method: 'POST',
910
+ uri: `https://${siteFullUrl}/wbxappapi/v1/meetings/${meetingUuid}/notifyhost`,
911
+ body: {
912
+ displayName,
913
+ size: displayName?.length,
914
+ },
915
+ headers: {
916
+ locusId,
917
+ }
918
+ });
919
+ });
920
+ });
900
921
  });
@@ -0,0 +1,120 @@
1
+ const { default: MembersCollection } = require("../../../../src/members/collection");
2
+ const { default: Member } = require("../../../../src/member");
3
+ const { assert } = require('@webex/test-helper-chai');
4
+
5
+
6
+ describe('plugin-meetings', () => {
7
+ describe('MembersCollection', () => {
8
+ let collection;
9
+ let member1;
10
+ let member2;
11
+ const participant1 = { controls: {}, status: {} };
12
+ const participant2 = { controls: {}, status: {} };
13
+
14
+ beforeEach(() => {
15
+ collection = new MembersCollection();
16
+ member1 = new Member(participant1);
17
+ member2 = new Member(participant2);
18
+ });
19
+
20
+ it('starts with no members', () => {
21
+ assert.equal(Object.keys(collection.getAll()).length, 0);
22
+ });
23
+
24
+ it('can add members', () => {
25
+ collection.set('member1', member1);
26
+ assert.equal(Object.keys(collection.getAll()).length, 1);
27
+ assert.equal(collection.get('member1'), member1);
28
+
29
+ collection.set('member2', member2);
30
+ assert.equal(Object.keys(collection.getAll()).length, 2);
31
+ assert.equal(collection.get('member2'), member2);
32
+ });
33
+
34
+ it('can remove members', () => {
35
+ // Add some members first
36
+ collection.set('member1', member1);
37
+ collection.set('member2', member2);
38
+ assert.equal(Object.keys(collection.getAll()).length, 2);
39
+
40
+ // Remove one member
41
+ collection.remove('member1');
42
+ assert.equal(Object.keys(collection.getAll()).length, 1);
43
+ assert.equal(collection.get('member1'), undefined);
44
+ assert.equal(collection.get('member2'), member2);
45
+
46
+ // Remove another member
47
+ collection.remove('member2');
48
+ assert.equal(Object.keys(collection.getAll()).length, 0);
49
+ assert.equal(collection.get('member2'), undefined);
50
+
51
+ // Removing non-existent member should not cause errors
52
+ collection.remove('nonExistent');
53
+ assert.equal(Object.keys(collection.getAll()).length, 0);
54
+ });
55
+
56
+ describe('reset', () => {
57
+ it('removes all members', () => {
58
+ // Add some members first
59
+ collection.set('member1', member1);
60
+ collection.set('member2', member2);
61
+ assert.equal(Object.keys(collection.getAll()).length, 2);
62
+
63
+ // Reset should clear all members
64
+ collection.reset();
65
+ assert.equal(Object.keys(collection.getAll()).length, 0);
66
+ assert.equal(collection.get('member1'), undefined);
67
+ assert.equal(collection.get('member2'), undefined);
68
+ });
69
+ });
70
+
71
+ describe('setAll', () => {
72
+ it('replaces all members with new collection', () => {
73
+ // Add initial member
74
+ collection.set('member1', member1);
75
+ assert.equal(Object.keys(collection.getAll()).length, 1);
76
+
77
+ // Set all with new collection
78
+ const newMembers = {
79
+ 'member2': member2,
80
+ 'member3': new Member(participant1)
81
+ };
82
+ collection.setAll(newMembers);
83
+
84
+ assert.equal(Object.keys(collection.getAll()).length, 2);
85
+ assert.equal(collection.get('member1'), undefined);
86
+ assert.equal(collection.get('member2'), member2);
87
+ assert.exists(collection.get('member3'));
88
+ });
89
+ });
90
+
91
+ describe('get', () => {
92
+ it('returns undefined for non-existent members', () => {
93
+ assert.equal(collection.get('nonExistent'), undefined);
94
+ });
95
+
96
+ it('returns correct member for existing id', () => {
97
+ collection.set('member1', member1);
98
+ assert.equal(collection.get('member1'), member1);
99
+ });
100
+ });
101
+
102
+ describe('getAll', () => {
103
+ it('returns empty object when no members', () => {
104
+ const allMembers = collection.getAll();
105
+ assert.isObject(allMembers);
106
+ assert.equal(Object.keys(allMembers).length, 0);
107
+ });
108
+
109
+ it('returns all members', () => {
110
+ collection.set('member1', member1);
111
+ collection.set('member2', member2);
112
+
113
+ const allMembers = collection.getAll();
114
+ assert.equal(Object.keys(allMembers).length, 2);
115
+ assert.equal(allMembers.member1, member1);
116
+ assert.equal(allMembers.member2, member2);
117
+ });
118
+ });
119
+ });
120
+ });
@@ -320,9 +320,18 @@ describe('plugin-meetings', () => {
320
320
  EVENT_TRIGGERS.MEMBERS_CLEAR,
321
321
  {}
322
322
  );
323
+ sinon.restore();
323
324
  });
324
325
  });
325
326
  describe('#locusParticipantsUpdate', () => {
327
+ beforeEach(() => {
328
+ sinon.stub(Trigger, 'trigger');
329
+ });
330
+
331
+ afterEach(() => {
332
+ sinon.restore();
333
+ });
334
+
326
335
  it('should send member update event with session info', () => {
327
336
  const members = createMembers({url: url1});
328
337
  const fakePayload = {
@@ -343,13 +352,45 @@ describe('plugin-meetings', () => {
343
352
  },
344
353
  EVENT_TRIGGERS.MEMBERS_UPDATE,
345
354
  {
346
- delta: {added: [], updated: []},
355
+ delta: {added: [], updated: [], removedIds: []},
347
356
  full: {},
348
357
  isReplace: true,
349
358
  }
350
359
  );
351
360
  });
352
361
 
362
+ it('should handle participants being removed', () => {
363
+ const members = createMembers({url: url1});
364
+
365
+ // setup the collection with a fake member
366
+ members.membersCollection.setAll(fakeMembersCollection);
367
+ assert.equal(Object.keys(members.membersCollection.getAll()).length, 1);
368
+
369
+ // remove the member
370
+ members.locusParticipantsUpdate({
371
+ participants: [],
372
+ removedParticipantIds: ['test1'],
373
+ });
374
+
375
+ assert.equal(Object.keys(members.membersCollection.getAll()).length, 0);
376
+
377
+ // check that the event was emitted
378
+ assert.calledWith(
379
+ Trigger.trigger,
380
+ members,
381
+ {
382
+ file: 'members',
383
+ function: 'locusParticipantsUpdate',
384
+ },
385
+ EVENT_TRIGGERS.MEMBERS_UPDATE,
386
+ {
387
+ delta: {added: [], updated: [], removedIds: ['test1']},
388
+ full: {},
389
+ isReplace: false,
390
+ }
391
+ );
392
+ });
393
+
353
394
  describe('handles members with paired devices correctly', () => {
354
395
  const runCheck = (propsForUpdate, expectedPropsOnPairedMember) => {
355
396
  const members = createMembers({url: url1});