@webex/plugin-meetings 3.8.1-web-workers-keepalive.1 → 3.9.0-webinar5k.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.
Files changed (87) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +8 -2
  4. package/dist/constants.js.map +1 -1
  5. package/dist/hashTree/constants.js +23 -0
  6. package/dist/hashTree/constants.js.map +1 -0
  7. package/dist/hashTree/hashTree.js +516 -0
  8. package/dist/hashTree/hashTree.js.map +1 -0
  9. package/dist/hashTree/hashTreeParser.js +521 -0
  10. package/dist/hashTree/hashTreeParser.js.map +1 -0
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/locus-info/index.js +301 -59
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/meeting/brbState.js +14 -12
  16. package/dist/meeting/brbState.js.map +1 -1
  17. package/dist/meeting/index.js +110 -12
  18. package/dist/meeting/index.js.map +1 -1
  19. package/dist/meeting/muteState.js +2 -5
  20. package/dist/meeting/muteState.js.map +1 -1
  21. package/dist/meeting/request.js +19 -0
  22. package/dist/meeting/request.js.map +1 -1
  23. package/dist/meeting/request.type.js.map +1 -1
  24. package/dist/meeting/util.js +8 -11
  25. package/dist/meeting/util.js.map +1 -1
  26. package/dist/meetings/index.js +6 -2
  27. package/dist/meetings/index.js.map +1 -1
  28. package/dist/member/index.js.map +1 -1
  29. package/dist/member/types.js.map +1 -1
  30. package/dist/members/collection.js +13 -0
  31. package/dist/members/collection.js.map +1 -1
  32. package/dist/members/index.js +44 -23
  33. package/dist/members/index.js.map +1 -1
  34. package/dist/members/request.js +3 -3
  35. package/dist/members/request.js.map +1 -1
  36. package/dist/members/util.js +18 -6
  37. package/dist/members/util.js.map +1 -1
  38. package/dist/multistream/sendSlotManager.js +32 -2
  39. package/dist/multistream/sendSlotManager.js.map +1 -1
  40. package/dist/types/constants.d.ts +6 -0
  41. package/dist/types/hashTree/constants.d.ts +8 -0
  42. package/dist/types/hashTree/hashTree.d.ts +128 -0
  43. package/dist/types/hashTree/hashTreeParser.d.ts +152 -0
  44. package/dist/types/locus-info/index.d.ts +93 -3
  45. package/dist/types/meeting/brbState.d.ts +0 -1
  46. package/dist/types/meeting/index.d.ts +29 -3
  47. package/dist/types/meeting/request.d.ts +9 -1
  48. package/dist/types/meeting/request.type.d.ts +74 -0
  49. package/dist/types/meeting/util.d.ts +3 -3
  50. package/dist/types/member/types.d.ts +1 -0
  51. package/dist/types/members/collection.d.ts +6 -0
  52. package/dist/types/members/index.d.ts +15 -3
  53. package/dist/types/members/request.d.ts +1 -1
  54. package/dist/types/members/util.d.ts +5 -2
  55. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  56. package/dist/webinar/index.js +1 -1
  57. package/package.json +24 -23
  58. package/src/constants.ts +7 -0
  59. package/src/hashTree/constants.ts +12 -0
  60. package/src/hashTree/hashTree.ts +460 -0
  61. package/src/hashTree/hashTreeParser.ts +556 -0
  62. package/src/locus-info/index.ts +393 -58
  63. package/src/meeting/brbState.ts +9 -7
  64. package/src/meeting/index.ts +104 -6
  65. package/src/meeting/muteState.ts +2 -6
  66. package/src/meeting/request.ts +16 -0
  67. package/src/meeting/request.type.ts +64 -0
  68. package/src/meeting/util.ts +17 -20
  69. package/src/meetings/index.ts +17 -3
  70. package/src/member/index.ts +1 -0
  71. package/src/member/types.ts +1 -0
  72. package/src/members/collection.ts +11 -0
  73. package/src/members/index.ts +33 -7
  74. package/src/members/request.ts +2 -2
  75. package/src/members/util.ts +14 -3
  76. package/src/multistream/sendSlotManager.ts +34 -2
  77. package/test/unit/spec/hashTree/hashTree.ts +394 -0
  78. package/test/unit/spec/hashTree/hashTreeParser.ts +156 -0
  79. package/test/unit/spec/locus-info/index.js +506 -55
  80. package/test/unit/spec/meeting/brbState.ts +9 -9
  81. package/test/unit/spec/meeting/index.js +475 -42
  82. package/test/unit/spec/meeting/request.js +71 -0
  83. package/test/unit/spec/members/index.js +33 -10
  84. package/test/unit/spec/members/request.js +2 -2
  85. package/test/unit/spec/members/utils.js +27 -7
  86. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  87. package/test/unit/spec/reachability/index.ts +3 -1
@@ -66,7 +66,7 @@ import Media, {type BundlePolicy} from '../media';
66
66
  import MediaProperties from '../media/properties';
67
67
  import MeetingStateMachine from './state';
68
68
  import {createMuteState} from './muteState';
69
- import LocusInfo from '../locus-info';
69
+ import LocusInfo, {LocusDTO, LocusLLMEvent} from '../locus-info';
70
70
  import Metrics from '../metrics';
71
71
  import ReconnectionManager from '../reconnection-manager';
72
72
  import ReconnectionNotStartedError from '../common/errors/reconnection-not-started';
@@ -121,6 +121,7 @@ import {
121
121
  WEBINAR_ERROR_REGISTRATION_ID,
122
122
  JOIN_BEFORE_HOST,
123
123
  REGISTRATION_ID_STATUS,
124
+ STAGE_MANAGER_TYPE,
124
125
  } from '../constants';
125
126
  import BEHAVIORAL_METRICS from '../metrics/constants';
126
127
  import ParameterError from '../common/errors/parameter';
@@ -164,6 +165,8 @@ import {BrbState, createBrbState} from './brbState';
164
165
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
165
166
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
166
167
  import {ReachabilityMetrics} from '../reachability/reachability.types';
168
+ import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
169
+ import {DataSet} from '../hashTree/hashTreeParser';
167
170
 
168
171
  // default callback so we don't call an undefined function, but in practice it should never be used
169
172
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -3861,15 +3864,16 @@ export default class Meeting extends StatelessWebexPlugin {
3861
3864
  }
3862
3865
 
3863
3866
  /**
3864
- * Cancel an SIP call invitation made during a meeting
3867
+ * Cancel an SIP/phone call invitation made during a meeting
3865
3868
  * @param {Object} invitee
3866
3869
  * @param {String} invitee.memberId
3867
- * @returns {Promise} see #members.cancelSIPInvite
3870
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3871
+ * @returns {Promise} see #members.cancelInviteByMemberId
3868
3872
  * @public
3869
3873
  * @memberof Meeting
3870
3874
  */
3871
- public cancelSIPInvite(invitee: {memberId: string}) {
3872
- return this.members.cancelSIPInvite(invitee);
3875
+ public cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
3876
+ return this.members.cancelInviteByMemberId(invitee);
3873
3877
  }
3874
3878
 
3875
3879
  /**
@@ -4475,11 +4479,13 @@ export default class Meeting extends StatelessWebexPlugin {
4475
4479
  setLocus(
4476
4480
  locus:
4477
4481
  | {
4482
+ locus: LocusDTO;
4478
4483
  mediaConnections: Array<any>;
4479
4484
  locusUrl: string;
4480
4485
  locusId: string;
4481
4486
  mediaId: string;
4482
4487
  host: object;
4488
+ dataSets: DataSet[];
4483
4489
  }
4484
4490
  | any
4485
4491
  ) {
@@ -4493,7 +4499,7 @@ export default class Meeting extends StatelessWebexPlugin {
4493
4499
  this.selfId = locus.selfId;
4494
4500
  this.mediaId = locus.mediaId;
4495
4501
  this.hostId = mtgLocus.host ? mtgLocus.host.id : this.hostId;
4496
- this.locusInfo.initialSetup(mtgLocus);
4502
+ this.locusInfo.initialSetup(mtgLocus, locus.dataSets);
4497
4503
  }
4498
4504
 
4499
4505
  /**
@@ -5606,6 +5612,21 @@ export default class Meeting extends StatelessWebexPlugin {
5606
5612
  }
5607
5613
  }
5608
5614
 
5615
+ /** Handles Locus LLM events
5616
+ *
5617
+ * @param {LocusLLMEvent} event - The Locus LLM event to process
5618
+ * @returns {void}
5619
+ */
5620
+ private processLocusLLMEvent = (event: LocusLLMEvent): void => {
5621
+ if (event.data.eventType === 'locus.state_message') {
5622
+ this.locusInfo.parse(this, event.data);
5623
+ } else {
5624
+ LoggerProxy.logger.warn(
5625
+ `Meeting:index#processLocusLLMEvent --> Unknown event type: ${event.data.eventType}`
5626
+ );
5627
+ }
5628
+ };
5629
+
5609
5630
  /**
5610
5631
  * Callback called when a relay event is received from meeting LLM Connection
5611
5632
  * @param {RelayEvent} e Event object coming from LLM Connection
@@ -6026,6 +6047,8 @@ export default class Meeting extends StatelessWebexPlugin {
6026
6047
  );
6027
6048
  // @ts-ignore - Fix type
6028
6049
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6050
+ // @ts-ignore - Fix type
6051
+ this.webex.internal.llm.off('event:locus.state_message', this.processLocusLLMEvent);
6029
6052
  }
6030
6053
 
6031
6054
  if (!isJoined) {
@@ -6040,6 +6063,10 @@ export default class Meeting extends StatelessWebexPlugin {
6040
6063
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6041
6064
  // @ts-ignore - Fix type
6042
6065
  this.webex.internal.llm.on('event:relay.event', this.processRelayEvent);
6066
+ // @ts-ignore - Fix type
6067
+ this.webex.internal.llm.off('event:locus.state_message', this.processLocusLLMEvent);
6068
+ // @ts-ignore - Fix type
6069
+ this.webex.internal.llm.on('event:locus.state_message', this.processLocusLLMEvent);
6043
6070
  LoggerProxy.logger.info(
6044
6071
  'Meeting:index#updateLLMConnection --> enabled to receive relay events!'
6045
6072
  );
@@ -9211,6 +9238,8 @@ export default class Meeting extends StatelessWebexPlugin {
9211
9238
 
9212
9239
  // @ts-ignore - fix types
9213
9240
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
9241
+ // @ts-ignore - Fix type
9242
+ this.webex.internal.llm.off('event:locus.state_message', this.processLocusLLMEvent);
9214
9243
  };
9215
9244
 
9216
9245
  /**
@@ -9763,4 +9792,73 @@ export default class Meeting extends StatelessWebexPlugin {
9763
9792
  selected_subnet: selectedSubnetFirstOctet ? `${selectedSubnetFirstOctet}.X.X.X` : null,
9764
9793
  };
9765
9794
  }
9795
+
9796
+ /**
9797
+ * Set the stage for the meeting
9798
+ *
9799
+ * @param {SetStageOptions} options Options to use when setting the stage
9800
+ * @returns {Promise} The locus request
9801
+ */
9802
+ setStage({
9803
+ activeSpeakerProportion = 0.5,
9804
+ customBackground,
9805
+ customLogo,
9806
+ customNameLabel,
9807
+ importantParticipants,
9808
+ lockAttendeeViewOnStage = false,
9809
+ showActiveSpeaker = false,
9810
+ }: SetStageOptions = {}) {
9811
+ const videoLayout: SetStageVideoLayout = {
9812
+ overrideDefault: true,
9813
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
9814
+ stageParameters: {
9815
+ activeSpeakerProportion,
9816
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
9817
+ stageManagerType: 0,
9818
+ },
9819
+ };
9820
+
9821
+ if (importantParticipants?.length) {
9822
+ videoLayout.stageParameters.importantParticipants = importantParticipants.map(
9823
+ (importantParticipant, index) => ({...importantParticipant, order: index + 1})
9824
+ );
9825
+ }
9826
+
9827
+ if (customLogo) {
9828
+ if (!videoLayout.customLayouts) {
9829
+ videoLayout.customLayouts = {};
9830
+ }
9831
+ videoLayout.customLayouts.logo = customLogo;
9832
+ // eslint-disable-next-line no-bitwise
9833
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.LOGO;
9834
+ }
9835
+
9836
+ if (customBackground) {
9837
+ if (!videoLayout.customLayouts) {
9838
+ videoLayout.customLayouts = {};
9839
+ }
9840
+ videoLayout.customLayouts.background = customBackground;
9841
+ // eslint-disable-next-line no-bitwise
9842
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.BACKGROUND;
9843
+ }
9844
+
9845
+ if (customNameLabel) {
9846
+ videoLayout.nameLabelStyle = customNameLabel;
9847
+ // eslint-disable-next-line no-bitwise
9848
+ videoLayout.stageParameters.stageManagerType |= STAGE_MANAGER_TYPE.NAME_LABEL;
9849
+ }
9850
+
9851
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9852
+ }
9853
+
9854
+ /**
9855
+ * Unset the stage for the meeting
9856
+ *
9857
+ * @returns {Promise} The locus request
9858
+ */
9859
+ unsetStage() {
9860
+ const videoLayout: UnsetStageVideoLayout = {overrideDefault: false};
9861
+
9862
+ return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9863
+ }
9766
9864
  }
@@ -291,18 +291,14 @@ export class MuteState {
291
291
  );
292
292
 
293
293
  return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
294
- .then((locus) => {
294
+ .then((response) => {
295
295
  LoggerProxy.logger.info(
296
296
  `Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
297
297
  );
298
298
 
299
299
  this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;
300
300
 
301
- if (locus) {
302
- meeting.locusInfo.handleLocusDelta(locus, meeting);
303
- }
304
-
305
- return locus;
301
+ return MeetingUtil.updateLocusFromApiResponse(meeting, response);
306
302
  })
307
303
  .catch((remoteUpdateError) => {
308
304
  LoggerProxy.logger.warn(
@@ -32,6 +32,7 @@ import {
32
32
  BrbOptions,
33
33
  ToggleReactionsOptions,
34
34
  PostMeetingDataConsentOptions,
35
+ SynchronizeVideoLayout,
35
36
  } from './request.type';
36
37
  import MeetingUtil from './util';
37
38
  import {AnnotationInfo} from '../annotation/annotation.types';
@@ -969,4 +970,19 @@ export default class MeetingRequest extends StatelessWebexPlugin {
969
970
  },
970
971
  });
971
972
  }
973
+
974
+ /**
975
+ * Synchronize the stage for a meeting
976
+ *
977
+ * @param {LocusUrl} locusUrl The locus URL
978
+ * @param {SetStageVideoLayout} videoLayout The video layout to synchronize
979
+ * @returns {Promise} The locus request
980
+ */
981
+ synchronizeStage(locusUrl: string, videoLayout: SynchronizeVideoLayout) {
982
+ return this.locusDeltaRequest({
983
+ method: HTTP_VERBS.PATCH,
984
+ uri: `${locusUrl}/${CONTROLS}`,
985
+ body: {videoLayout},
986
+ });
987
+ }
972
988
  }
@@ -25,3 +25,67 @@ export type PostMeetingDataConsentOptions = {
25
25
  deviceUrl: string;
26
26
  selfId: string;
27
27
  };
28
+
29
+ export type StageCustomLogoPositions =
30
+ | 'LowerLeft'
31
+ | 'LowerMiddle'
32
+ | 'LowerRight'
33
+ | 'UpperLeft'
34
+ | 'UpperMiddle'
35
+ | 'UpperRight';
36
+
37
+ export type StageNameLabelType = 'Primary' | 'PrimaryInverted' | 'Secondary' | 'SecondaryInverted';
38
+
39
+ export type StageCustomBackground = {
40
+ url: string;
41
+ [others: string]: unknown;
42
+ };
43
+
44
+ export type StageCustomLogo = {
45
+ url: string;
46
+ position: StageCustomLogoPositions;
47
+ [others: string]: unknown;
48
+ };
49
+
50
+ export type StageCustomNameLabel = {
51
+ accentColor: string;
52
+ background: {color: string};
53
+ border: {color: string};
54
+ content: {displayName: {color: string}; subtitle: {color: string}};
55
+ decoration: {color: string};
56
+ fadeOut?: {delay: number};
57
+ type: StageNameLabelType;
58
+ [others: string]: unknown;
59
+ };
60
+
61
+ export type SetStageOptions = {
62
+ activeSpeakerProportion?: number;
63
+ customBackground?: StageCustomBackground;
64
+ customLogo?: StageCustomLogo;
65
+ customNameLabel?: StageCustomNameLabel;
66
+ importantParticipants?: {mainCsi: number; participantId: string}[];
67
+ lockAttendeeViewOnStage?: boolean;
68
+ showActiveSpeaker?: boolean;
69
+ };
70
+
71
+ export type SetStageVideoLayout = {
72
+ overrideDefault: true;
73
+ lockAttendeeViewOnStageOnly: boolean;
74
+ stageParameters: {
75
+ importantParticipants?: {participantId: string; mainCsi: number; order: number}[];
76
+ showActiveSpeaker: {show: boolean; order: number};
77
+ activeSpeakerProportion: number;
78
+ stageManagerType: number;
79
+ };
80
+ customLayouts?: {
81
+ background?: StageCustomBackground;
82
+ logo?: StageCustomLogo;
83
+ };
84
+ nameLabelStyle?: StageCustomNameLabel;
85
+ };
86
+
87
+ export type UnsetStageVideoLayout = {
88
+ overrideDefault: false;
89
+ };
90
+
91
+ export type SynchronizeVideoLayout = SetStageVideoLayout | UnsetStageVideoLayout;
@@ -31,6 +31,7 @@ const MeetingUtil = {
31
31
 
32
32
  // First todo: add check for existance
33
33
  parsed.locus = response.body.locus;
34
+ parsed.dataSets = response.body.dataSets;
34
35
  parsed.mediaConnections = response.body.mediaConnections;
35
36
  parsed.locusUrl = parsed.locus.url;
36
37
  parsed.locusId = parsed.locus.url.split('/').pop();
@@ -59,18 +60,16 @@ const MeetingUtil = {
59
60
  );
60
61
  }
61
62
 
62
- return meeting.locusMediaRequest
63
- .send({
64
- type: 'LocalMute',
65
- selfUrl: meeting.selfUrl,
66
- mediaId: meeting.mediaId,
67
- sequence: meeting.locusInfo.sequence,
68
- muteOptions: {
69
- audioMuted,
70
- videoMuted,
71
- },
72
- })
73
- .then((response) => response?.body?.locus);
63
+ return meeting.locusMediaRequest.send({
64
+ type: 'LocalMute',
65
+ selfUrl: meeting.selfUrl,
66
+ mediaId: meeting.mediaId,
67
+ sequence: meeting.locusInfo.sequence,
68
+ muteOptions: {
69
+ audioMuted,
70
+ videoMuted,
71
+ },
72
+ });
74
73
  },
75
74
 
76
75
  hasOwner: (info) => info && info.owner,
@@ -602,22 +601,20 @@ const MeetingUtil = {
602
601
  },
603
602
 
604
603
  /**
605
- * Updates the locus info for the meeting with the delta locus
606
- * returned from requests that include the sequence information
604
+ * Updates the locus info for the meeting with the locus
605
+ * information returned from API requests made to Locus
607
606
  * Returns the original response object
608
607
  * @param {Object} meeting The meeting object
609
608
  * @param {Object} response The response of the http request
610
609
  * @returns {Object}
611
610
  */
612
- updateLocusWithDelta: (meeting, response) => {
611
+ updateLocusFromApiResponse: (meeting, response) => {
613
612
  if (!meeting) {
614
613
  return response;
615
614
  }
616
615
 
617
- const locus = response?.body?.locus;
618
-
619
- if (locus) {
620
- meeting.locusInfo.handleLocusDelta(locus, meeting);
616
+ if (response?.body?.locus) {
617
+ meeting.locusInfo.handleLocusAPIResponse(meeting, response.body);
621
618
  }
622
619
 
623
620
  return response;
@@ -664,7 +661,7 @@ const MeetingUtil = {
664
661
 
665
662
  return meeting
666
663
  .request(options)
667
- .then((response) => MeetingUtil.updateLocusWithDelta(meeting, response));
664
+ .then((response) => MeetingUtil.updateLocusFromApiResponse(meeting, response));
668
665
  };
669
666
 
670
667
  return locusDeltaRequest;
@@ -66,6 +66,7 @@ import JoinWebinarError from '../common/errors/join-webinar-error';
66
66
  import {SpaceIDDeprecatedError} from '../common/errors/webex-errors';
67
67
  import NoMeetingInfoError from '../common/errors/no-meeting-info';
68
68
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
69
+ import {DataSet} from '../hashTree/hashTreeParser';
69
70
 
70
71
  let mediaLogger;
71
72
 
@@ -407,6 +408,13 @@ export default class Meetings extends WebexPlugin {
407
408
  * @memberof Meetings
408
409
  */
409
410
  getCorrespondingMeetingByLocus(data) {
411
+ if (data.eventType === 'locus.state_message' && data.stateElementsMessage?.locusUrl) {
412
+ return this.meetingCollection.getByKey(
413
+ MEETING_KEY.LOCUS_URL,
414
+ data.stateElementsMessage.locusUrl
415
+ );
416
+ }
417
+
410
418
  // getting meeting by correlationId. This will happen for the new event
411
419
  // Either the locus
412
420
  // TODO : Add check for the callBack Address
@@ -445,7 +453,10 @@ export default class Meetings extends WebexPlugin {
445
453
  * @private
446
454
  * @memberof Meetings
447
455
  */
448
- private handleLocusEvent(data: {locusUrl: string; locus: any}, useRandomDelayForInfo = false) {
456
+ private handleLocusEvent(
457
+ data: {locusUrl: string; locus: any; dataSets?: DataSet[]},
458
+ useRandomDelayForInfo = false
459
+ ) {
449
460
  let meeting = this.getCorrespondingMeetingByLocus(data);
450
461
 
451
462
  // Special case when locus has got replaced, This only happend once if a replace locus exists
@@ -524,7 +535,7 @@ export default class Meetings extends WebexPlugin {
524
535
  meeting = newMeeting;
525
536
 
526
537
  // It's a new meeting so initialize the locus data
527
- meeting.locusInfo.initialSetup(data.locus);
538
+ meeting.locusInfo.initialSetup(data.locus, data.dataSets);
528
539
  this.checkHandleBreakoutLocus(data.locus);
529
540
  })
530
541
  .catch((e) => {
@@ -1824,7 +1835,10 @@ export default class Meetings extends WebexPlugin {
1824
1835
  }
1825
1836
 
1826
1837
  const associateBreakoutLocus = this.breakoutLocusForHandleLater[existIndex];
1827
- this.handleLocusEvent({locus: associateBreakoutLocus, locusUrl: associateBreakoutLocus.url});
1838
+ this.handleLocusEvent({
1839
+ locus: associateBreakoutLocus,
1840
+ locusUrl: associateBreakoutLocus.url,
1841
+ });
1828
1842
  this.breakoutLocusForHandleLater.splice(existIndex, 1);
1829
1843
  }
1830
1844
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import {MEETINGS, _IN_LOBBY_, _NOT_IN_MEETING_, _IN_MEETING_, _OBSERVE_} from '../constants';
5
5
  import {IExternalRoles, IMediaStatus, Participant, ParticipantUrl} from './types';
6
+
6
7
  import MemberUtil from './util';
7
8
 
8
9
  export type MemberId = string;
@@ -109,4 +109,5 @@ export interface Participant {
109
109
  status: ParticipantMediaStatus;
110
110
  type: string;
111
111
  url: ParticipantUrl;
112
+ isRemoved: boolean; // JS-SDK internal field to indicate in updates when the participant is removed
112
113
  }
@@ -39,6 +39,17 @@ export default class MembersCollection {
39
39
  return this.members;
40
40
  }
41
41
 
42
+ /**
43
+ * Removes a member from the collection
44
+ * @param {String} id
45
+ * @returns {void}
46
+ */
47
+ remove(id: string) {
48
+ if (this.members[id]) {
49
+ delete this.members[id];
50
+ }
51
+ }
52
+
42
53
  /**
43
54
  * @returns {void}
44
55
  * reset members
@@ -73,7 +73,11 @@ import {ServerRoleShape} from './types';
73
73
  * @memberof Members
74
74
  */
75
75
 
76
- type UpdatedMembers = {added: Array<Member>; updated: Array<Member>};
76
+ type UpdatedMembers = {
77
+ added: Array<Member>;
78
+ updated: Array<Member>;
79
+ removedIds?: Array<string>; // removed member ids
80
+ };
77
81
  /**
78
82
  * @class Members
79
83
  */
@@ -388,7 +392,11 @@ export default class Members extends StatelessWebexPlugin {
388
392
  * @private
389
393
  * @memberof Members
390
394
  */
391
- locusParticipantsUpdate(payload: {participants: object; isReplace?: boolean}) {
395
+ locusParticipantsUpdate(payload: {
396
+ participants: object;
397
+ isReplace?: boolean;
398
+ removedParticipantIds?: Array<string>;
399
+ }) {
392
400
  if (payload) {
393
401
  if (payload.isReplace) {
394
402
  this.clearMembers();
@@ -546,10 +554,22 @@ export default class Members extends StatelessWebexPlugin {
546
554
  private handleMembersUpdate(membersUpdate: UpdatedMembers) {
547
555
  this.constructMembers(membersUpdate.updated, true);
548
556
  this.constructMembers(membersUpdate.added, false);
557
+ this.removeMembers(membersUpdate.removedIds);
549
558
 
550
559
  return this.membersCollection.getAll();
551
560
  }
552
561
 
562
+ /**
563
+ * removes members from the collection
564
+ * @param {Array<string>} removedMembers removed members ids
565
+ * @returns {void}
566
+ */
567
+ private removeMembers(removedMembers: Array<string>) {
568
+ removedMembers.forEach((memberId) => {
569
+ this.membersCollection.remove(memberId);
570
+ });
571
+ }
572
+
553
573
  /**
554
574
  * set members to the member collection from each updated/added lists as passed in
555
575
  * @param {Array} list
@@ -599,6 +619,10 @@ export default class Members extends StatelessWebexPlugin {
599
619
  }
600
620
  const memberUpdate = this.update(payload.participants);
601
621
 
622
+ // this code depends on memberIds being the same as participantIds
623
+ // if MemberUtil.extractId() ever changes, this will need to be updated
624
+ memberUpdate.removedIds = payload.removedParticipantIds || [];
625
+
602
626
  return memberUpdate;
603
627
  }
604
628
 
@@ -846,12 +870,14 @@ export default class Members extends StatelessWebexPlugin {
846
870
  }
847
871
 
848
872
  /**
849
- * Cancels an SIP call to the associated meeting
850
- * @param {String} invitee
873
+ * Cancels an SIP/phone call to the associated meeting
874
+ * @param {Object} invitee
875
+ * @param {String} invitee.memberId - The memberId of the invitee
876
+ * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
851
877
  * @returns {Promise}
852
878
  * @memberof Members
853
879
  */
854
- cancelSIPInvite(invitee: any) {
880
+ cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
855
881
  if (!this.locusUrl) {
856
882
  return Promise.reject(
857
883
  new ParameterError('The associated locus url for this meeting object must be defined.')
@@ -862,9 +888,9 @@ export default class Members extends StatelessWebexPlugin {
862
888
  new ParameterError('The invitee must be defined with a memberId property.')
863
889
  );
864
890
  }
865
- const options = MembersUtil.cancelSIPInviteOptions(invitee, this.locusUrl);
891
+ const options = MembersUtil.cancelInviteByMemberIdOptions(invitee, this.locusUrl);
866
892
 
867
- return this.membersRequest.cancelSIPInvite(options);
893
+ return this.membersRequest.cancelInviteByMemberId(options);
868
894
  }
869
895
 
870
896
  /**
@@ -285,14 +285,14 @@ export default class MembersRequest extends StatelessWebexPlugin {
285
285
  * @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
286
286
  * @memberof MembersRequest
287
287
  */
288
- cancelSIPInvite(options: any) {
288
+ cancelInviteByMemberId(options: any) {
289
289
  if (!options?.invitee?.memberId || !options?.locusUrl) {
290
290
  throw new ParameterError(
291
291
  'invitee must be passed and the associated locus url for this meeting object must be defined.'
292
292
  );
293
293
  }
294
294
 
295
- const requestParams = MembersUtil.generateCancelSIPInviteRequestParams(options);
295
+ const requestParams = MembersUtil.generateCancelInviteByMemberIdRequestParams(options);
296
296
 
297
297
  return this.locusDeltaRequest(requestParams);
298
298
  }
@@ -1,4 +1,5 @@
1
1
  import uuid from 'uuid';
2
+ import {has} from 'lodash';
2
3
  import {
3
4
  HTTP_VERBS,
4
5
  CONTROLS,
@@ -47,6 +48,9 @@ const MembersUtil = {
47
48
  address:
48
49
  options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,
49
50
  ...(options.invitee.roles ? {roles: options.invitee.roles} : {}),
51
+ ...(has(options.invitee, 'isInternalNumber')
52
+ ? {isInternalNumber: options.invitee.isInternalNumber}
53
+ : {}),
50
54
  },
51
55
  ],
52
56
  alertIfActive: options.alertIfActive,
@@ -107,6 +111,10 @@ const MembersUtil = {
107
111
  }
108
112
 
109
113
  if (invitee.phoneNumber) {
114
+ if (invitee.isInternalNumber) {
115
+ return !DIALER_REGEX.INTERNAL_NUMBER.test(invitee.phoneNumber);
116
+ }
117
+
110
118
  return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);
111
119
  }
112
120
 
@@ -371,17 +379,20 @@ const MembersUtil = {
371
379
  return requestParams;
372
380
  },
373
381
 
374
- cancelSIPInviteOptions: (invitee, locusUrl) => ({
382
+ cancelInviteByMemberIdOptions: (invitee, locusUrl) => ({
375
383
  invitee,
376
384
  locusUrl,
377
385
  }),
378
386
 
379
- generateCancelSIPInviteRequestParams: (options) => {
387
+ generateCancelInviteByMemberIdRequestParams: (options) => {
388
+ const {memberId, isInternalNumber} = options.invitee;
389
+ const hasIsInternalNumberProp = has(options.invitee, 'isInternalNumber');
380
390
  const body = {
381
391
  actionType: _REMOVE_,
382
392
  invitees: [
383
393
  {
384
- address: options.invitee.memberId,
394
+ address: memberId,
395
+ ...(hasIsInternalNumberProp ? {isInternalNumber} : {}),
385
396
  },
386
397
  ],
387
398
  };