@webex/plugin-meetings 3.8.0-next.73 → 3.8.0-next.75

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.
@@ -880,6 +880,17 @@ export default class Meeting extends StatelessWebexPlugin {
880
880
  cancelPhoneInvite(invitee: {
881
881
  phoneNumber: string;
882
882
  }): any;
883
+ /**
884
+ * Cancel an SIP call invitation made during a meeting
885
+ * @param {Object} invitee
886
+ * @param {String} invitee.memberId
887
+ * @returns {Promise} see #members.cancelSIPInvite
888
+ * @public
889
+ * @memberof Meeting
890
+ */
891
+ cancelSIPInvite(invitee: {
892
+ memberId: string;
893
+ }): any;
883
894
  /**
884
895
  * Admit the guest(s) to the call once they are waiting.
885
896
  * If the host/cohost is in a breakout session, the locus url
@@ -251,6 +251,13 @@ export default class Members extends StatelessWebexPlugin {
251
251
  * @memberof Members
252
252
  */
253
253
  cancelPhoneInvite(invitee: any): any;
254
+ /**
255
+ * Cancels an SIP call to the associated meeting
256
+ * @param {String} invitee
257
+ * @returns {Promise}
258
+ * @memberof Members
259
+ */
260
+ cancelSIPInvite(invitee: any): any;
254
261
  /**
255
262
  * Admits waiting members (invited guests to meeting)
256
263
  * @param {Array} memberIds
@@ -130,4 +130,11 @@ export default class MembersRequest extends StatelessWebexPlugin {
130
130
  * @memberof MembersRequest
131
131
  */
132
132
  cancelPhoneInvite(options: any): Promise<any>;
133
+ /**
134
+ * @param {Object} options with format of {invitee: object, locusUrl: string}
135
+ * @returns {Promise}
136
+ * @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
137
+ * @memberof MembersRequest
138
+ */
139
+ cancelSIPInvite(options: any): Promise<any>;
133
140
  }
@@ -228,5 +228,19 @@ declare const MembersUtil: {
228
228
  }[];
229
229
  };
230
230
  };
231
+ cancelSIPInviteOptions: (invitee: any, locusUrl: any) => {
232
+ invitee: any;
233
+ locusUrl: any;
234
+ };
235
+ generateCancelSIPInviteRequestParams: (options: any) => {
236
+ method: string;
237
+ uri: any;
238
+ body: {
239
+ actionType: string;
240
+ invitees: {
241
+ address: any;
242
+ }[];
243
+ };
244
+ };
231
245
  };
232
246
  export default MembersUtil;
@@ -458,7 +458,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
458
458
  }, _callee7);
459
459
  }))();
460
460
  },
461
- version: "3.8.0-next.73"
461
+ version: "3.8.0-next.75"
462
462
  });
463
463
  var _default = exports.default = Webinar;
464
464
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -43,7 +43,7 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.8.0-next.73",
46
+ "@webex/plugin-meetings": "3.8.0-next.75",
47
47
  "@webex/plugin-rooms": "3.8.0-next.25",
48
48
  "@webex/test-helper-chai": "3.8.0-next.20",
49
49
  "@webex/test-helper-mocha": "3.8.0-next.20",
@@ -71,7 +71,7 @@
71
71
  "@webex/internal-plugin-metrics": "3.8.0-next.20",
72
72
  "@webex/internal-plugin-support": "3.8.0-next.25",
73
73
  "@webex/internal-plugin-user": "3.8.0-next.20",
74
- "@webex/internal-plugin-voicea": "3.8.0-next.73",
74
+ "@webex/internal-plugin-voicea": "3.8.0-next.75",
75
75
  "@webex/media-helpers": "3.8.0-next.24",
76
76
  "@webex/plugin-people": "3.8.0-next.22",
77
77
  "@webex/plugin-rooms": "3.8.0-next.25",
@@ -93,5 +93,5 @@
93
93
  "//": [
94
94
  "TODO: upgrade jwt-decode when moving to node 18"
95
95
  ],
96
- "version": "3.8.0-next.73"
96
+ "version": "3.8.0-next.75"
97
97
  }
package/src/constants.ts CHANGED
@@ -381,6 +381,7 @@ export const EVENT_TRIGGERS = {
381
381
  MEETING_STOPPED_RECEIVING_TRANSCRIPTION: 'meeting:receiveTranscription:stopped',
382
382
  MEETING_MANUAL_CAPTION_UPDATED: 'meeting:manualCaptionControl:updated',
383
383
  MEETING_CAPTION_RECEIVED: 'meeting:caption-received',
384
+ MEETING_PARTICIPANT_REASON_CHANGED: 'meeting:participant-reason-changed',
384
385
  };
385
386
 
386
387
  export const EVENT_TYPES = {
@@ -743,6 +744,7 @@ export const LOCUSINFO = {
743
744
  MEDIA_INACTIVITY: 'MEDIA_INACTIVITY',
744
745
  LINKS_SERVICES: 'LINKS_SERVICES',
745
746
  LINKS_RESOURCES: 'LINKS_RESOURCES',
747
+ PARTICIPANT_REASON_CHANGED: 'PARTICIPANT_REASON_CHANGED',
746
748
  },
747
749
  };
748
750
 
@@ -1,4 +1,4 @@
1
- import {isEqual, assignWith, cloneDeep, isEmpty} from 'lodash';
1
+ import {isEqual, assignWith, cloneDeep, isEmpty, forEach} from 'lodash';
2
2
 
3
3
  import LoggerProxy from '../common/logs/logger-proxy';
4
4
  import EventsScope from '../common/events/events-scope';
@@ -784,6 +784,23 @@ export default class LocusInfo extends EventsScope {
784
784
  isReplace,
785
785
  }
786
786
  );
787
+
788
+ if (participants && Array.isArray(participants) && participants.length > 0) {
789
+ for (const participant of participants) {
790
+ if (participant && participant?.reason === 'FAILURE') {
791
+ this.emitScoped(
792
+ {
793
+ file: 'locus-info',
794
+ function: 'updateParticipants',
795
+ },
796
+ LOCUSINFO.EVENTS.PARTICIPANT_REASON_CHANGED,
797
+ {
798
+ displayName: participant?.person?.primaryDisplayString,
799
+ }
800
+ );
801
+ }
802
+ }
803
+ }
787
804
  }
788
805
 
789
806
  /**
@@ -2637,6 +2637,19 @@ export default class Meeting extends StatelessWebexPlugin {
2637
2637
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS, (payload) => {
2638
2638
  this.members.locusParticipantsUpdate(payload);
2639
2639
  });
2640
+ this.locusInfo.on(LOCUSINFO.EVENTS.PARTICIPANT_REASON_CHANGED, (payload) => {
2641
+ Trigger.trigger(
2642
+ this,
2643
+ {
2644
+ file: 'meeting/index',
2645
+ function: 'setUpLocusParticipantsListener',
2646
+ },
2647
+ EVENT_TRIGGERS.MEETING_PARTICIPANT_REASON_CHANGED,
2648
+ {
2649
+ payload,
2650
+ }
2651
+ );
2652
+ });
2640
2653
  }
2641
2654
 
2642
2655
  /**
@@ -3803,6 +3816,18 @@ export default class Meeting extends StatelessWebexPlugin {
3803
3816
  return this.members.cancelPhoneInvite(invitee);
3804
3817
  }
3805
3818
 
3819
+ /**
3820
+ * Cancel an SIP call invitation made during a meeting
3821
+ * @param {Object} invitee
3822
+ * @param {String} invitee.memberId
3823
+ * @returns {Promise} see #members.cancelSIPInvite
3824
+ * @public
3825
+ * @memberof Meeting
3826
+ */
3827
+ public cancelSIPInvite(invitee: {memberId: string}) {
3828
+ return this.members.cancelSIPInvite(invitee);
3829
+ }
3830
+
3806
3831
  /**
3807
3832
  * Admit the guest(s) to the call once they are waiting.
3808
3833
  * If the host/cohost is in a breakout session, the locus url
@@ -773,6 +773,28 @@ export default class Members extends StatelessWebexPlugin {
773
773
  return this.membersRequest.cancelPhoneInvite(options);
774
774
  }
775
775
 
776
+ /**
777
+ * Cancels an SIP call to the associated meeting
778
+ * @param {String} invitee
779
+ * @returns {Promise}
780
+ * @memberof Members
781
+ */
782
+ cancelSIPInvite(invitee: any) {
783
+ if (!this.locusUrl) {
784
+ return Promise.reject(
785
+ new ParameterError('The associated locus url for this meeting object must be defined.')
786
+ );
787
+ }
788
+ if (!invitee?.memberId) {
789
+ return Promise.reject(
790
+ new ParameterError('The invitee must be defined with a memberId property.')
791
+ );
792
+ }
793
+ const options = MembersUtil.cancelSIPInviteOptions(invitee, this.locusUrl);
794
+
795
+ return this.membersRequest.cancelSIPInvite(options);
796
+ }
797
+
776
798
  /**
777
799
  * Admits waiting members (invited guests to meeting)
778
800
  * @param {Array} memberIds
@@ -278,4 +278,22 @@ export default class MembersRequest extends StatelessWebexPlugin {
278
278
 
279
279
  return this.locusDeltaRequest(requestParams);
280
280
  }
281
+
282
+ /**
283
+ * @param {Object} options with format of {invitee: object, locusUrl: string}
284
+ * @returns {Promise}
285
+ * @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
286
+ * @memberof MembersRequest
287
+ */
288
+ cancelSIPInvite(options: any) {
289
+ if (!options?.invitee?.memberId || !options?.locusUrl) {
290
+ throw new ParameterError(
291
+ 'invitee must be passed and the associated locus url for this meeting object must be defined.'
292
+ );
293
+ }
294
+
295
+ const requestParams = MembersUtil.generateCancelSIPInviteRequestParams(options);
296
+
297
+ return this.locusDeltaRequest(requestParams);
298
+ }
281
299
  }
@@ -367,6 +367,29 @@ const MembersUtil = {
367
367
 
368
368
  return requestParams;
369
369
  },
370
+
371
+ cancelSIPInviteOptions: (invitee, locusUrl) => ({
372
+ invitee,
373
+ locusUrl,
374
+ }),
375
+
376
+ generateCancelSIPInviteRequestParams: (options) => {
377
+ const body = {
378
+ actionType: _REMOVE_,
379
+ invitees: [
380
+ {
381
+ address: options.invitee.memberId,
382
+ },
383
+ ],
384
+ };
385
+ const requestParams = {
386
+ method: HTTP_VERBS.PUT,
387
+ uri: options.locusUrl,
388
+ body,
389
+ };
390
+
391
+ return requestParams;
392
+ },
370
393
  };
371
394
 
372
395
  export default MembersUtil;
@@ -825,6 +825,32 @@ describe('plugin-meetings', () => {
825
825
 
826
826
  assert.isTrue(locusInfo.deltaParticipants.length === 0);
827
827
  });
828
+
829
+ it('should call with participant display name', () => {
830
+ const failureParticipant = [
831
+ {
832
+ person: {
833
+ id: 5678,
834
+ primaryDisplayString: 'Test User',
835
+ },
836
+ reason: 'FAILURE',
837
+ },
838
+ ];
839
+
840
+ locusInfo.emitScoped = sinon.stub();
841
+ locusInfo.updateParticipants(failureParticipant);
842
+ assert.calledWith(
843
+ locusInfo.emitScoped,
844
+ {
845
+ file: 'locus-info',
846
+ function: 'updateParticipants',
847
+ },
848
+ LOCUSINFO.EVENTS.PARTICIPANT_REASON_CHANGED,
849
+ {
850
+ displayName: 'Test User',
851
+ }
852
+ );
853
+ })
828
854
  });
829
855
 
830
856
  describe('#updateSelf', () => {
@@ -614,6 +614,22 @@ describe('plugin-meetings', () => {
614
614
  assert.calledWith(meeting.members.cancelPhoneInvite, uuid1);
615
615
  });
616
616
  });
617
+ describe('#cancelSIPInvite', () => {
618
+ it('should have #cancelSIPInvite', () => {
619
+ assert.exists(meeting.cancelSIPInvite);
620
+ });
621
+ beforeEach(() => {
622
+ meeting.members.cancelSIPInvite = sinon.stub().returns(Promise.resolve(test1));
623
+ });
624
+ it('should proxy members #cancelSIPInvite and return a promise', async () => {
625
+ const cancel = meeting.cancelSIPInvite({memberId: uuid1});
626
+
627
+ assert.exists(cancel.then);
628
+ await cancel;
629
+ assert.calledOnce(meeting.members.cancelSIPInvite);
630
+ assert.calledWith(meeting.members.cancelSIPInvite, {memberId: uuid1});
631
+ });
632
+ });
617
633
  describe('#admit', () => {
618
634
  it('should have #admit', () => {
619
635
  assert.exists(meeting.admit);
@@ -342,6 +342,32 @@ describe('plugin-meetings', () => {
342
342
  });
343
343
  });
344
344
 
345
+ describe('#cancelSIPInvite', () => {
346
+ const memberId = uuid.v4();
347
+ it('should invoke cancelSIPInviteOptions from MembersUtil when cancelSIPInvite is called with valid params', async () => {
348
+ sandbox.spy(MembersUtil, 'cancelSIPInviteOptions');
349
+
350
+ const members = createMembers({url: url1});
351
+
352
+ await members.cancelSIPInvite({memberId});
353
+ assert.calledOnce(MembersUtil.cancelSIPInviteOptions);
354
+ });
355
+
356
+ it('should throw a rejection if there is no locus url', async () => {
357
+ const members = createMembers({url: false});
358
+
359
+ assert.isRejected(members.cancelSIPInvite({memberId}));
360
+ });
361
+
362
+ it('should throw a rejection if memberId is not provided', async () => {
363
+ const members = createMembers({url: url1});
364
+
365
+ assert.isRejected(members.cancelSIPInvite({}));
366
+ assert.isRejected(members.cancelSIPInvite({memberId: null}));
367
+ assert.isRejected(members.cancelSIPInvite({memberId: undefined}));
368
+ });
369
+ });
370
+
345
371
  describe('#assignRoles', () => {
346
372
  const fakeRoles = [
347
373
  {type: 'PRESENTER', hasRole: true},
@@ -221,6 +221,29 @@ describe('plugin-meetings', () => {
221
221
  });
222
222
  });
223
223
 
224
+ describe('#cancelSIPInvite', () => {
225
+ const memberId = uuid.v4();
226
+ it('sends a PUT to the locus endpoint', async () => {
227
+ const options = {
228
+ invitee: {
229
+ memberId,
230
+ },
231
+ locusUrl: url1,
232
+ };
233
+
234
+ await membersRequest.cancelSIPInvite(options);
235
+
236
+ checkRequest({
237
+ method: 'PUT',
238
+ uri: url1,
239
+ body: {
240
+ actionType: 'REMOVE',
241
+ invitees: [{address: memberId}],
242
+ },
243
+ });
244
+ });
245
+ });
246
+
224
247
  describe('#assignRolesMember', () => {
225
248
  it('sends a assignRolesMember PATCH to the locus endpoint', async () => {
226
249
  const locusUrl = url1;
@@ -390,5 +390,47 @@ describe('plugin-meetings', () => {
390
390
  });
391
391
  });
392
392
  });
393
+
394
+ describe('#cancelSIPInviteOptions', () => {
395
+ it('returns the correct options', () => {
396
+ const locusUrl = 'TestLocusUrl';
397
+ const memberId = 'test';
398
+ const invitee = {memberId};
399
+
400
+ assert.deepEqual(
401
+ MembersUtil.cancelSIPInviteOptions(
402
+ invitee,
403
+ locusUrl
404
+ ),
405
+ {
406
+ invitee,
407
+ locusUrl,
408
+ }
409
+ );
410
+ });
411
+ });
412
+
413
+ describe('#generateCancelSIPInviteRequestParams', () => {
414
+ it('returns the correct params', () => {
415
+ const locusUrl = 'TestLocusUrl';
416
+ const memberId = 'test';
417
+ const options = {
418
+ locusUrl,
419
+ invitee: {memberId}
420
+ };
421
+ const body = {
422
+ actionType: 'REMOVE',
423
+ invitees: [{address: options.invitee.memberId}],
424
+ };
425
+
426
+ const uri = options.locusUrl;
427
+
428
+ assert.deepEqual(MembersUtil.generateCancelSIPInviteRequestParams(options), {
429
+ method: HTTP_VERBS.PUT,
430
+ uri,
431
+ body,
432
+ });
433
+ });
434
+ });
393
435
  });
394
436
  });