@webex/plugin-meetings 3.12.0-next.9 → 3.12.0-next.91

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 (213) hide show
  1. package/AGENTS.md +9 -0
  2. package/dist/aiEnableRequest/index.js +15 -2
  3. package/dist/aiEnableRequest/index.js.map +1 -1
  4. package/dist/breakouts/breakout.js +8 -3
  5. package/dist/breakouts/breakout.js.map +1 -1
  6. package/dist/breakouts/index.js +26 -2
  7. package/dist/breakouts/index.js.map +1 -1
  8. package/dist/config.js +2 -2
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js +35 -9
  11. package/dist/constants.js.map +1 -1
  12. package/dist/controls-options-manager/constants.js +11 -1
  13. package/dist/controls-options-manager/constants.js.map +1 -1
  14. package/dist/controls-options-manager/index.js +67 -29
  15. package/dist/controls-options-manager/index.js.map +1 -1
  16. package/dist/controls-options-manager/util.js +91 -0
  17. package/dist/controls-options-manager/util.js.map +1 -1
  18. package/dist/hashTree/constants.js +13 -1
  19. package/dist/hashTree/constants.js.map +1 -1
  20. package/dist/hashTree/hashTreeParser.js +880 -382
  21. package/dist/hashTree/hashTreeParser.js.map +1 -1
  22. package/dist/hashTree/utils.js +42 -0
  23. package/dist/hashTree/utils.js.map +1 -1
  24. package/dist/index.js +7 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/interceptors/dataChannelAuthToken.js +75 -15
  27. package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
  28. package/dist/interceptors/locusRetry.js +23 -8
  29. package/dist/interceptors/locusRetry.js.map +1 -1
  30. package/dist/interpretation/index.js +10 -1
  31. package/dist/interpretation/index.js.map +1 -1
  32. package/dist/interpretation/interpretation.types.js +7 -0
  33. package/dist/interpretation/interpretation.types.js.map +1 -0
  34. package/dist/interpretation/siLanguage.js +1 -1
  35. package/dist/locus-info/controlsUtils.js +4 -1
  36. package/dist/locus-info/controlsUtils.js.map +1 -1
  37. package/dist/locus-info/index.js +303 -88
  38. package/dist/locus-info/index.js.map +1 -1
  39. package/dist/locus-info/infoUtils.js +36 -5
  40. package/dist/locus-info/infoUtils.js.map +1 -1
  41. package/dist/locus-info/types.js +19 -0
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/index.js +3 -1
  44. package/dist/media/index.js.map +1 -1
  45. package/dist/media/properties.js +1 -0
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +5 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1233 -793
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/locusMediaRequest.js +229 -82
  52. package/dist/meeting/locusMediaRequest.js.map +1 -1
  53. package/dist/meeting/muteState.js +10 -1
  54. package/dist/meeting/muteState.js.map +1 -1
  55. package/dist/meeting/request.js +5 -2
  56. package/dist/meeting/request.js.map +1 -1
  57. package/dist/meeting/util.js +35 -2
  58. package/dist/meeting/util.js.map +1 -1
  59. package/dist/meeting-info/meeting-info-v2.js +2 -2
  60. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  61. package/dist/meetings/index.js +231 -118
  62. package/dist/meetings/index.js.map +1 -1
  63. package/dist/meetings/meetings.types.js +6 -1
  64. package/dist/meetings/meetings.types.js.map +1 -1
  65. package/dist/meetings/request.js +39 -0
  66. package/dist/meetings/request.js.map +1 -1
  67. package/dist/meetings/util.js +79 -5
  68. package/dist/meetings/util.js.map +1 -1
  69. package/dist/member/index.js +10 -0
  70. package/dist/member/index.js.map +1 -1
  71. package/dist/member/types.js.map +1 -1
  72. package/dist/member/util.js +30 -0
  73. package/dist/member/util.js.map +1 -1
  74. package/dist/members/index.js +101 -43
  75. package/dist/members/index.js.map +1 -1
  76. package/dist/members/util.js +8 -1
  77. package/dist/members/util.js.map +1 -1
  78. package/dist/metrics/constants.js +5 -1
  79. package/dist/metrics/constants.js.map +1 -1
  80. package/dist/multistream/codec/constants.js +63 -0
  81. package/dist/multistream/codec/constants.js.map +1 -0
  82. package/dist/multistream/mediaRequestManager.js +62 -15
  83. package/dist/multistream/mediaRequestManager.js.map +1 -1
  84. package/dist/multistream/receiveSlot.js +9 -0
  85. package/dist/multistream/receiveSlot.js.map +1 -1
  86. package/dist/reachability/index.js +10 -13
  87. package/dist/reachability/index.js.map +1 -1
  88. package/dist/reachability/reachability.types.js +2 -0
  89. package/dist/reachability/reachability.types.js.map +1 -1
  90. package/dist/reachability/util.js +45 -0
  91. package/dist/reachability/util.js.map +1 -1
  92. package/dist/reactions/reactions.type.js.map +1 -1
  93. package/dist/recording-controller/index.js +1 -3
  94. package/dist/recording-controller/index.js.map +1 -1
  95. package/dist/types/config.d.ts +2 -2
  96. package/dist/types/constants.d.ts +12 -1
  97. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  98. package/dist/types/controls-options-manager/index.d.ts +16 -0
  99. package/dist/types/hashTree/constants.d.ts +2 -0
  100. package/dist/types/hashTree/hashTreeParser.d.ts +146 -17
  101. package/dist/types/hashTree/utils.d.ts +18 -0
  102. package/dist/types/index.d.ts +3 -0
  103. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  104. package/dist/types/interpretation/interpretation.types.d.ts +10 -0
  105. package/dist/types/locus-info/index.d.ts +50 -6
  106. package/dist/types/locus-info/infoUtils.d.ts +8 -0
  107. package/dist/types/locus-info/types.d.ts +21 -1
  108. package/dist/types/media/properties.d.ts +1 -0
  109. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  110. package/dist/types/meeting/index.d.ts +94 -7
  111. package/dist/types/meeting/locusMediaRequest.d.ts +12 -0
  112. package/dist/types/meeting/request.d.ts +1 -0
  113. package/dist/types/meeting/util.d.ts +9 -0
  114. package/dist/types/meetings/index.d.ts +30 -18
  115. package/dist/types/meetings/meetings.types.d.ts +15 -0
  116. package/dist/types/meetings/request.d.ts +14 -0
  117. package/dist/types/member/index.d.ts +1 -0
  118. package/dist/types/member/types.d.ts +1 -0
  119. package/dist/types/member/util.d.ts +9 -0
  120. package/dist/types/members/index.d.ts +15 -2
  121. package/dist/types/members/util.d.ts +1 -5
  122. package/dist/types/metrics/constants.d.ts +4 -0
  123. package/dist/types/multistream/codec/constants.d.ts +7 -0
  124. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  125. package/dist/types/reachability/index.d.ts +1 -1
  126. package/dist/types/reachability/reachability.types.d.ts +7 -0
  127. package/dist/types/reachability/util.d.ts +17 -0
  128. package/dist/types/reactions/reactions.type.d.ts +3 -0
  129. package/dist/webinar/index.js +305 -159
  130. package/dist/webinar/index.js.map +1 -1
  131. package/package.json +22 -22
  132. package/src/aiEnableRequest/index.ts +16 -0
  133. package/src/breakouts/breakout.ts +3 -1
  134. package/src/breakouts/index.ts +31 -0
  135. package/src/config.ts +2 -2
  136. package/src/constants.ts +20 -3
  137. package/src/controls-options-manager/constants.ts +14 -1
  138. package/src/controls-options-manager/index.ts +87 -28
  139. package/src/controls-options-manager/util.ts +81 -1
  140. package/src/hashTree/constants.ts +16 -0
  141. package/src/hashTree/hashTreeParser.ts +580 -196
  142. package/src/hashTree/utils.ts +36 -0
  143. package/src/index.ts +6 -0
  144. package/src/interceptors/dataChannelAuthToken.ts +88 -12
  145. package/src/interceptors/locusRetry.ts +25 -4
  146. package/src/interpretation/index.ts +27 -9
  147. package/src/interpretation/interpretation.types.ts +11 -0
  148. package/src/locus-info/controlsUtils.ts +3 -1
  149. package/src/locus-info/index.ts +304 -100
  150. package/src/locus-info/infoUtils.ts +41 -6
  151. package/src/locus-info/types.ts +25 -1
  152. package/src/media/index.ts +3 -0
  153. package/src/media/properties.ts +1 -0
  154. package/src/meeting/in-meeting-actions.ts +8 -0
  155. package/src/meeting/index.ts +494 -67
  156. package/src/meeting/locusMediaRequest.ts +106 -6
  157. package/src/meeting/muteState.ts +10 -1
  158. package/src/meeting/request.ts +11 -0
  159. package/src/meeting/util.ts +47 -5
  160. package/src/meeting-info/meeting-info-v2.ts +4 -2
  161. package/src/meetings/index.ts +133 -78
  162. package/src/meetings/meetings.types.ts +19 -0
  163. package/src/meetings/request.ts +43 -0
  164. package/src/meetings/util.ts +97 -1
  165. package/src/member/index.ts +10 -0
  166. package/src/member/types.ts +1 -0
  167. package/src/member/util.ts +29 -0
  168. package/src/members/index.ts +70 -7
  169. package/src/members/util.ts +12 -4
  170. package/src/metrics/constants.ts +4 -0
  171. package/src/multistream/codec/constants.ts +58 -0
  172. package/src/multistream/mediaRequestManager.ts +119 -28
  173. package/src/multistream/receiveSlot.ts +18 -0
  174. package/src/reachability/index.ts +19 -15
  175. package/src/reachability/reachability.types.ts +11 -0
  176. package/src/reachability/util.ts +36 -0
  177. package/src/reactions/reactions.type.ts +3 -0
  178. package/src/recording-controller/index.ts +1 -2
  179. package/src/webinar/index.ts +214 -36
  180. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  181. package/test/unit/spec/breakouts/breakout.ts +9 -3
  182. package/test/unit/spec/breakouts/index.ts +49 -0
  183. package/test/unit/spec/controls-options-manager/index.js +244 -29
  184. package/test/unit/spec/controls-options-manager/util.js +165 -0
  185. package/test/unit/spec/hashTree/hashTreeParser.ts +1838 -180
  186. package/test/unit/spec/hashTree/utils.ts +125 -1
  187. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
  188. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  189. package/test/unit/spec/interpretation/index.ts +26 -4
  190. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  191. package/test/unit/spec/locus-info/index.js +499 -81
  192. package/test/unit/spec/locus-info/infoUtils.js +184 -1
  193. package/test/unit/spec/media/index.ts +31 -0
  194. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  195. package/test/unit/spec/meeting/index.js +1615 -42
  196. package/test/unit/spec/meeting/locusMediaRequest.ts +302 -4
  197. package/test/unit/spec/meeting/muteState.js +81 -0
  198. package/test/unit/spec/meeting/request.js +12 -0
  199. package/test/unit/spec/meeting/utils.js +157 -1
  200. package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
  201. package/test/unit/spec/meetings/index.js +363 -36
  202. package/test/unit/spec/meetings/request.js +141 -0
  203. package/test/unit/spec/meetings/utils.js +189 -0
  204. package/test/unit/spec/member/index.js +7 -0
  205. package/test/unit/spec/member/util.js +89 -0
  206. package/test/unit/spec/members/index.js +216 -0
  207. package/test/unit/spec/members/request.js +37 -1
  208. package/test/unit/spec/members/utils.js +63 -0
  209. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
  210. package/test/unit/spec/reachability/index.ts +59 -46
  211. package/test/unit/spec/reachability/util.ts +31 -0
  212. package/test/unit/spec/recording-controller/index.js +9 -8
  213. package/test/unit/spec/webinar/index.ts +329 -28
@@ -55,10 +55,12 @@ import PasswordError from '../common/errors/password-error';
55
55
  import CaptchaError from '../common/errors/captcha-error';
56
56
  import MeetingCollection from './collection';
57
57
  import {
58
+ FetchSitePreferencesMeViaSiteOptions,
58
59
  MEETING_KEY,
59
60
  INoiseReductionEffect,
60
61
  IVirtualBackgroundEffect,
61
62
  MeetingRegistrationStatus,
63
+ SitePreferencesResponse,
62
64
  } from './meetings.types';
63
65
  import MeetingsUtil from './util';
64
66
  import PermissionError from '../common/errors/permission';
@@ -69,7 +71,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
69
71
  import {HashTreeMessage} from '../hashTree/hashTreeParser';
70
72
  import {HashTreeObject} from '../hashTree/types';
71
73
  import {isSelf} from '../hashTree/utils';
74
+
72
75
  import {createLocusFromHashTreeMessage, findMeetingForHashTreeMessage} from '../locus-info';
76
+ import {LocusDTO} from '../locus-info/types';
73
77
 
74
78
  let mediaLogger;
75
79
 
@@ -313,7 +317,7 @@ export default class Meetings extends WebexPlugin {
313
317
  const breakoutLocus = this.meetingCollection.getActiveBreakoutLocus(breakoutUrl);
314
318
 
315
319
  const isSelfJoined = newLocus?.self?.state === _JOINED_;
316
- const isSelfMoved = newLocus?.self?.state === _LEFT_ && newLocus?.self?.reason === _MOVED_;
320
+ const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
317
321
  // @ts-ignore
318
322
  const deviceFromNewLocus = MeetingsUtil.getThisDevice(newLocus, this.webex.internal.device.url);
319
323
  const isResourceMovedOnThisDevice =
@@ -390,7 +394,7 @@ export default class Meetings extends WebexPlugin {
390
394
  private isNeedHandleLocusDTO(meeting: any, newLocus: any) {
391
395
  if (newLocus) {
392
396
  const isNewLocusAsBreakout = MeetingsUtil.isBreakoutLocusDTO(newLocus);
393
- const isSelfMoved = newLocus?.self?.state === _LEFT_ && newLocus?.self?.reason === _MOVED_;
397
+ const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
394
398
  const isSelfMovedToLobby =
395
399
  newLocus?.self?.devices[0]?.intent?.reason === _ON_HOLD_LOBBY_ &&
396
400
  newLocus?.self?.devices[0]?.intent?.type === _WAIT_;
@@ -435,14 +439,11 @@ export default class Meetings extends WebexPlugin {
435
439
  if (existingMeeting) {
436
440
  return existingMeeting;
437
441
  }
438
-
439
442
  if (data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
440
443
  // need to check if maybe this event indicates a move to/from breakout
441
444
  const meetingForHashTreeMessage = findMeetingForHashTreeMessage(
442
- data.stateElementsMessage,
443
- this.meetingCollection,
444
- // @ts-ignore
445
- this.webex.internal.device.url
445
+ data?.stateElementsMessage,
446
+ this.meetingCollection
446
447
  );
447
448
 
448
449
  if (meetingForHashTreeMessage) {
@@ -492,7 +493,6 @@ export default class Meetings extends WebexPlugin {
492
493
  */
493
494
  private handleLocusEvent(data: LocusEvent, useRandomDelayForInfo = false) {
494
495
  let meeting = this.getCorrespondingMeetingByLocus(data);
495
-
496
496
  // @ts-ignore
497
497
  if (this.config.experimental.storeLocusHashTreeEventsForDebugging) {
498
498
  storeEventForDebugging('mercury', data);
@@ -513,6 +513,7 @@ export default class Meetings extends WebexPlugin {
513
513
  if (meeting && !MeetingsUtil.isBreakoutLocusDTO(data.locus)) {
514
514
  meeting.locusInfo.updateMainSessionLocusCache(data.locus); // here data.locus will never be a complete locus
515
515
  }
516
+
516
517
  if (!this.isNeedHandleLocusDTO(meeting, data.locus)) {
517
518
  LoggerProxy.logger.log(
518
519
  `Meetings:index#handleLocusEvent --> doesn't need to process locus event`
@@ -586,17 +587,21 @@ export default class Meetings extends WebexPlugin {
586
587
  this.create(data.locus, DESTINATION_TYPE.LOCUS_ID, useRandomDelayForInfo)
587
588
  .then(async (newMeeting) => {
588
589
  meeting = newMeeting;
589
-
590
590
  try {
591
591
  // It's a new meeting so initialize the locus data
592
- await meeting.locusInfo.initialSetup({
593
- trigger:
594
- data.eventType === LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS
595
- ? 'get-loci-response'
596
- : 'locus-message',
597
- locus: data.locus,
598
- hashTreeMessage: data.stateElementsMessage,
599
- });
592
+ await meeting.locusInfo.initialSetup(
593
+ {
594
+ trigger:
595
+ data.eventType === LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS
596
+ ? 'get-loci-response'
597
+ : 'locus-message',
598
+ locus: data.locus,
599
+ hashTreeMessage: data.stateElementsMessage,
600
+ },
601
+ (locus: LocusDTO) => {
602
+ meeting.finalizeMeetingAfterInitialLocusSetup(locus);
603
+ }
604
+ );
600
605
  } catch (error) {
601
606
  LoggerProxy.logger.warn(
602
607
  `Meetings:index#handleLocusEvent --> Error initializing locus data: ${error.message}`
@@ -837,97 +842,82 @@ export default class Meetings extends WebexPlugin {
837
842
  }
838
843
 
839
844
  /**
840
- * API to toggle TCP reachability, needs to be called before webex.meetings.register()
841
- * @param {Boolean} newValue
842
- * @private
843
- * @memberof Meetings
844
- * @returns {undefined}
845
- */
846
- private _toggleTcpReachability(newValue: boolean) {
847
- if (typeof newValue !== 'boolean') {
848
- return;
849
- }
850
- // @ts-ignore
851
- if (this.config.experimental.enableTcpReachability !== newValue) {
852
- // @ts-ignore
853
- this.config.experimental.enableTcpReachability = newValue;
854
- }
855
- }
856
-
857
- /**
858
- * API to toggle TLS reachability, needs to be called before webex.meetings.register()
845
+ * API to toggle backend ipv6 native support config, needs to be called before webex.meetings.register()
846
+ *
859
847
  * @param {Boolean} newValue
860
848
  * @private
861
849
  * @memberof Meetings
862
850
  * @returns {undefined}
863
851
  */
864
- private _toggleTlsReachability(newValue: boolean) {
852
+ private _toggleIpv6BackendNativeSupport(newValue: boolean) {
865
853
  if (typeof newValue !== 'boolean') {
866
854
  return;
867
855
  }
868
856
  // @ts-ignore
869
- if (this.config.experimental.enableTlsReachability !== newValue) {
857
+ if (this.config.backendIpv6NativeSupport !== newValue) {
870
858
  // @ts-ignore
871
- this.config.experimental.enableTlsReachability = newValue;
859
+ this.config.backendIpv6NativeSupport = newValue;
872
860
  }
873
861
  }
874
862
 
875
863
  /**
876
- * API to toggle backend ipv6 native support config, needs to be called before webex.meetings.register()
864
+ * API to toggle usage of audio main DTX, needs to be called before webex.meetings.register()
877
865
  *
878
866
  * @param {Boolean} newValue
879
867
  * @private
880
868
  * @memberof Meetings
881
869
  * @returns {undefined}
882
870
  */
883
- private _toggleIpv6BackendNativeSupport(newValue: boolean) {
871
+ private _toggleDisableAudioMainDtx(newValue: boolean) {
884
872
  if (typeof newValue !== 'boolean') {
885
873
  return;
886
874
  }
875
+
887
876
  // @ts-ignore
888
- if (this.config.backendIpv6NativeSupport !== newValue) {
877
+ if (this.config.experimental.disableAudioMainDtx !== newValue) {
889
878
  // @ts-ignore
890
- this.config.backendIpv6NativeSupport = newValue;
879
+ this.config.experimental.disableAudioMainDtx = newValue;
891
880
  }
892
881
  }
893
882
 
894
883
  /**
895
- * API to toggle usage of audio main DTX, needs to be called before webex.meetings.register()
884
+ * API to toggle usage of audio twcc support
896
885
  *
897
886
  * @param {Boolean} newValue
898
887
  * @private
899
888
  * @memberof Meetings
900
889
  * @returns {undefined}
901
890
  */
902
- private _toggleDisableAudioMainDtx(newValue: boolean) {
891
+ private _toggleEnableAudioTwccForMultistream(newValue: boolean) {
903
892
  if (typeof newValue !== 'boolean') {
904
893
  return;
905
894
  }
906
895
 
907
896
  // @ts-ignore
908
- if (this.config.experimental.disableAudioMainDtx !== newValue) {
897
+ if (this.config.enableAudioTwccForMultistream !== newValue) {
909
898
  // @ts-ignore
910
- this.config.experimental.disableAudioMainDtx = newValue;
899
+ this.config.enableAudioTwccForMultistream = newValue;
911
900
  }
912
901
  }
913
902
 
914
903
  /**
915
- * API to toggle usage of audio twcc support
904
+ * API to toggle AV1 codec support for video slides in multistream,
905
+ * needs to be called before webex.meetings.joinWithMedia()
916
906
  *
917
907
  * @param {Boolean} newValue
918
908
  * @private
919
909
  * @memberof Meetings
920
910
  * @returns {undefined}
921
911
  */
922
- private _toggleEnableAudioTwccForMultistream(newValue: boolean) {
912
+ private _toggleEnableAv1SlidesSupport(newValue: boolean) {
923
913
  if (typeof newValue !== 'boolean') {
924
914
  return;
925
915
  }
926
916
 
927
917
  // @ts-ignore
928
- if (this.config.enableAudioTwccForMultistream !== newValue) {
918
+ if (this.config.enableAv1SlidesSupport !== newValue) {
929
919
  // @ts-ignore
930
- this.config.enableAudioTwccForMultistream = newValue;
920
+ this.config.enableAv1SlidesSupport = newValue;
931
921
  }
932
922
  }
933
923
 
@@ -1403,6 +1393,31 @@ export default class Meetings extends WebexPlugin {
1403
1393
  return this.personalMeetingRoom;
1404
1394
  }
1405
1395
 
1396
+ /**
1397
+ * Fetches site preferences for the provided Webex site, or the preferred Webex site.
1398
+ * This is used to determine capabilities of the site, such as whether scheduling a webinar is supported.
1399
+ *
1400
+ * @param {object} [options]
1401
+ * @param {string} [options.siteUrl] - Webex site URL. Defaults to preferredWebexSite, for example "cisco.webex.com".
1402
+ * @param {string} [options.siteName] - Site name query override. Defaults to the site name derived from siteUrl, for example "cisco" for "cisco.webex.com".
1403
+ * @param {SitePreferenceSelectOption[]} [options.selectOptions] - Preference sections to fetch. Defaults to 'scheduling'.
1404
+ * @returns {Promise<SitePreferencesResponse>} site preferences response body
1405
+ * @throws {ParameterError}
1406
+ * @public
1407
+ * @memberof Meetings
1408
+ * @example
1409
+ * const preferences = await webex.meetings.fetchSitePreferencesMeViaSite();
1410
+ * const supportScheduleWebinar = preferences?.scheduling?.supportScheduleWebinar;
1411
+ */
1412
+ public fetchSitePreferencesMeViaSite(
1413
+ options: FetchSitePreferencesMeViaSiteOptions = {}
1414
+ ): Promise<SitePreferencesResponse> {
1415
+ return this.request.fetchSitePreferencesMeViaSite({
1416
+ ...options,
1417
+ siteUrl: options.siteUrl || this.preferredWebexSite,
1418
+ });
1419
+ }
1420
+
1406
1421
  /**
1407
1422
  * Returns basic information about a meeting that exists or
1408
1423
  * used to exist in the MeetingCollection
@@ -1765,10 +1780,14 @@ export default class Meetings extends WebexPlugin {
1765
1780
  extraParams: infoExtraParams,
1766
1781
  sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
1767
1782
  };
1783
+ const shouldDeferMeetingInfoFetch = type === DESTINATION_TYPE.LOCUS_ID && !destination?.info;
1784
+
1785
+ const isOneOnOneCallLocus =
1786
+ type === DESTINATION_TYPE.LOCUS_ID && MeetingsUtil.isOneOnOneCall(destination);
1768
1787
 
1769
1788
  if (meetingInfo) {
1770
1789
  meeting.injectMeetingInfo(meetingInfo, meetingInfoOptions, meetingLookupUrl);
1771
- } else if (type !== DESTINATION_TYPE.ONE_ON_ONE_CALL) {
1790
+ } else if (type !== DESTINATION_TYPE.ONE_ON_ONE_CALL && !isOneOnOneCallLocus) {
1772
1791
  // ignore fetchMeetingInfo for 1:1 meetings
1773
1792
  if (enableUnifiedMeetings && !isMeetingActive && useRandomDelayForInfo && waitingTime > 0) {
1774
1793
  meeting.fetchMeetingInfoTimeoutId = setTimeout(
@@ -1776,8 +1795,12 @@ export default class Meetings extends WebexPlugin {
1776
1795
  waitingTime
1777
1796
  );
1778
1797
  meeting.parseMeetingInfo(undefined, destination);
1779
- } else {
1798
+ } else if (!shouldDeferMeetingInfoFetch) {
1780
1799
  await meeting.fetchMeetingInfo(meetingInfoOptions);
1800
+ } else {
1801
+ LoggerProxy.logger.info(
1802
+ 'Meetings:index#createMeeting --> defer fetchMeetingInfo for incomplete locus, will do it after locus initialSetup'
1803
+ );
1781
1804
  }
1782
1805
  }
1783
1806
  } catch (err) {
@@ -1811,7 +1834,11 @@ export default class Meetings extends WebexPlugin {
1811
1834
  // For type LOCUS_ID we need to parse the locus object to get the information
1812
1835
  // about the caller and callee
1813
1836
  // Meeting Added event will be created in `handleLocusEvent`
1814
- if (type !== DESTINATION_TYPE.LOCUS_ID) {
1837
+ // Only emit MEETING_ADDED if the meeting still exists in the collection.
1838
+ // If fetchMeetingInfo failed and the meeting was destroyed in the catch block,
1839
+ // skip emitting to prevent orphaned meeting references on the consumer side.
1840
+ // @ts-ignore - getMeetingByType types value as object but accepts strings (same as handleLocusEvent)
1841
+ if (type !== DESTINATION_TYPE.LOCUS_ID && this.getMeetingByType(_ID_, meeting.id)) {
1815
1842
  if (!meeting.sipUri) {
1816
1843
  meeting.setSipUri(destination);
1817
1844
  }
@@ -1886,23 +1913,23 @@ export default class Meetings extends WebexPlugin {
1886
1913
  * @public
1887
1914
  * @memberof Meetings
1888
1915
  */
1889
- public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
1916
+ public async syncMeetings({
1917
+ keepOnlyLocusMeetings = true,
1918
+ skipHashTreeSync = false,
1919
+ } = {}): Promise<void> {
1890
1920
  // @ts-ignore
1891
1921
  if (this.webex.credentials.isUnverifiedGuest) {
1892
1922
  LoggerProxy.logger.info(
1893
- 'Meetings:index#syncMeetings --> skipping meeting sync as unverified guest'
1923
+ 'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
1894
1924
  );
1895
-
1896
- return Promise.resolve();
1897
- }
1898
-
1899
- return this.request
1900
- .getActiveMeetings()
1901
- .then((locusArray) => {
1902
- const activeLocusUrl = [];
1925
+ } else {
1926
+ try {
1927
+ const locusArray = await this.request.getActiveMeetings();
1928
+ const activeLocusUrl: string[] = [];
1903
1929
 
1904
1930
  if (locusArray?.loci && locusArray.loci.length > 0) {
1905
1931
  const lociToUpdate = this.sortLocusArrayToUpdate(locusArray.loci);
1932
+
1906
1933
  lociToUpdate.forEach((locus) => {
1907
1934
  activeLocusUrl.push(locus.url);
1908
1935
  this.handleLocusEvent({
@@ -1920,21 +1947,50 @@ export default class Meetings extends WebexPlugin {
1920
1947
  // (they had a locusUrl previously but are no longer active) in the sync
1921
1948
  for (const meeting of Object.values(meetingsCollection)) {
1922
1949
  // @ts-ignore
1923
- const {locusUrl} = meeting;
1950
+ const {locusUrl, locusInfo} = meeting;
1924
1951
  if ((keepOnlyLocusMeetings || locusUrl) && !activeLocusUrl.includes(locusUrl)) {
1925
- // destroy function also uploads logs
1926
- // @ts-ignore
1927
- this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
1952
+ const globalMeetingId = locusInfo?.info?.globalMeetingId;
1953
+
1954
+ if (
1955
+ globalMeetingId &&
1956
+ locusArray?.loci?.some(
1957
+ (locus: LocusDTO) => locus.info?.globalMeetingId === globalMeetingId
1958
+ )
1959
+ ) {
1960
+ // don't destroy the meeting as Locus API still returned some Locus that shares
1961
+ // the same globalMeetingId - that happens for example if a webinar user (who hasn't scheduled it)
1962
+ // is in a breakout and gets moved to a different breakout while we were offline
1963
+ } else {
1964
+ // destroy function also uploads logs
1965
+ // @ts-ignore
1966
+ this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
1967
+ }
1928
1968
  }
1929
1969
  }
1930
1970
  }
1931
- })
1932
- .catch((error) => {
1971
+ } catch (error) {
1933
1972
  LoggerProxy.logger.error(
1934
1973
  `Meetings:index#syncMeetings --> failed to sync meetings, ${error}`
1935
1974
  );
1936
- throw new Error(error);
1937
- });
1975
+ throw error;
1976
+ }
1977
+ }
1978
+
1979
+ if (!skipHashTreeSync) {
1980
+ // Trigger hash tree syncs for all remaining meetings
1981
+ const remainingMeetings = this.meetingCollection.getAll();
1982
+ const syncPromises = [];
1983
+
1984
+ for (const meeting of Object.values(remainingMeetings) as any[]) {
1985
+ if (meeting.locusInfo) {
1986
+ syncPromises.push(meeting.locusInfo.syncAllHashTreeDatasets());
1987
+ }
1988
+ }
1989
+
1990
+ if (syncPromises.length > 0) {
1991
+ await Promise.all(syncPromises);
1992
+ }
1993
+ }
1938
1994
  }
1939
1995
 
1940
1996
  /**
@@ -1950,8 +2006,8 @@ export default class Meetings extends WebexPlugin {
1950
2006
  this.breakoutLocusForHandleLater = [];
1951
2007
  const lociToUpdate = [...mainLoci];
1952
2008
  breakoutLoci.forEach((breakoutLocus) => {
1953
- const associateMainLocus = mainLoci.find(
1954
- (mainLocus) => mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
2009
+ const associateMainLocus = mainLoci.find((mainLocus) =>
2010
+ MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus)
1955
2011
  );
1956
2012
  const existCorrespondingMeeting = this.getCorrespondingMeetingByLocus({
1957
2013
  eventType: LOCUSEVENT.SDK_NO_EVENT,
@@ -1979,7 +2035,7 @@ export default class Meetings extends WebexPlugin {
1979
2035
  * @public
1980
2036
  * @memberof Meetings
1981
2037
  */
1982
- checkHandleBreakoutLocus(newCreatedLocus) {
2038
+ checkHandleBreakoutLocus(newCreatedLocus: any) {
1983
2039
  if (
1984
2040
  !newCreatedLocus ||
1985
2041
  !this.breakoutLocusForHandleLater ||
@@ -1990,9 +2046,8 @@ export default class Meetings extends WebexPlugin {
1990
2046
  if (MeetingsUtil.isBreakoutLocusDTO(newCreatedLocus)) {
1991
2047
  return;
1992
2048
  }
1993
- const existIndex = this.breakoutLocusForHandleLater.findIndex(
1994
- (breakoutLocus) =>
1995
- breakoutLocus.controls?.breakout?.url === newCreatedLocus.controls?.breakout?.url
2049
+ const existIndex = this.breakoutLocusForHandleLater.findIndex((breakoutLocus: any) =>
2050
+ MeetingsUtil.isMainAssociatedWithBreakout(newCreatedLocus, breakoutLocus)
1996
2051
  );
1997
2052
 
1998
2053
  if (existIndex < 0) {
@@ -31,3 +31,22 @@ export type MeetingRegistrationStatus = {
31
31
  mercuryConnect: boolean;
32
32
  checkH264Support: boolean;
33
33
  };
34
+
35
+ export enum SitePreferenceSelectOption {
36
+ SCHEDULING = 'scheduling',
37
+ }
38
+
39
+ export type FetchSitePreferencesMeViaSiteOptions = {
40
+ siteUrl?: string;
41
+ siteName?: string;
42
+ selectOptions?: SitePreferenceSelectOption[];
43
+ };
44
+
45
+ export const DEFAULT_SITE_PREFERENCE_SELECT_OPTIONS = [SitePreferenceSelectOption.SCHEDULING];
46
+
47
+ export type SitePreferencesResponse = {
48
+ scheduling?: {
49
+ supportScheduleWebinar?: boolean;
50
+ webinarWebLink?: string;
51
+ };
52
+ };
@@ -2,7 +2,14 @@
2
2
  import {StatelessWebexPlugin} from '@webex/webex-core';
3
3
 
4
4
  import LoggerProxy from '../common/logs/logger-proxy';
5
+ import ParameterError from '../common/errors/parameter';
5
6
  import {HTTP_VERBS, API, RESOURCE} from '../constants';
7
+ import {
8
+ DEFAULT_SITE_PREFERENCE_SELECT_OPTIONS,
9
+ type FetchSitePreferencesMeViaSiteOptions,
10
+ type SitePreferencesResponse,
11
+ } from './meetings.types';
12
+ import MeetingsUtil from './util';
6
13
 
7
14
  /**
8
15
  * @class MeetingRequest
@@ -45,6 +52,42 @@ export default class MeetingRequest extends StatelessWebexPlugin {
45
52
  return this.webex.internal.services.getMeetingPreferences();
46
53
  }
47
54
 
55
+ /**
56
+ * Fetches site preferences from a given site given a select option and a siteUrl with an optional siteName. If siteName is not provided, it will be derived from the siteUrl. If siteUrl is not provided, it will throw an error. If selectOptions is not provided, it will default to scheduling.
57
+ *
58
+ * @param {object} [options]
59
+ * @param {string} [options.siteUrl] - Webex site URL, for example "cisco.webex.com".
60
+ * @param {string} [options.siteName] - Site name query override. Defaults to the site name derived from options.siteUrl, e.g., "cisco".
61
+ * @param {SitePreferenceSelectOption[]} [options.selectOptions] - Preference sections to fetch. Defaults to 'scheduling'.
62
+ * @returns {Promise<SitePreferencesResponse>} site preferences response body
63
+ * @throws {ParameterError}
64
+ * @public
65
+ * @memberof MeetingRequest
66
+ */
67
+ fetchSitePreferencesMeViaSite(
68
+ options: FetchSitePreferencesMeViaSiteOptions = {}
69
+ ): Promise<SitePreferencesResponse> {
70
+ const {siteUrl, selectOptions = DEFAULT_SITE_PREFERENCE_SELECT_OPTIONS} = options;
71
+
72
+ if (!siteUrl) {
73
+ throw new ParameterError(
74
+ 'No siteUrl available. Call register() before fetching site preferences or provide options.siteUrl.'
75
+ );
76
+ }
77
+
78
+ // @ts-ignore - config comes from registerPlugin
79
+ const multipartSitePrefixList = this.config.meetings.multipartSitePrefixList || [];
80
+ const siteName = options.siteName || MeetingsUtil.getSiteName(siteUrl, multipartSitePrefixList);
81
+
82
+ // @ts-ignore
83
+ return this.request({
84
+ method: HTTP_VERBS.GET,
85
+ uri: `https://${siteUrl}/wbxappapi/v1/users/me/preference?select=${encodeURIComponent(
86
+ selectOptions.join(',')
87
+ )}&siteurl=${encodeURIComponent(siteName)}`,
88
+ }).then((res: any) => res.body);
89
+ }
90
+
48
91
  // locus federation, determines and populate locus if the responseBody has remote URLs to fetch locus details
49
92
 
50
93
  /**
@@ -1,12 +1,15 @@
1
1
  /* globals window */
2
2
 
3
3
  import {
4
+ _CALL_,
4
5
  _CREATED_,
5
6
  _INCOMING_,
6
7
  _JOINED_,
7
8
  _LEFT_,
8
9
  DESTINATION_TYPE,
9
10
  _MOVED_,
11
+ _SIP_BRIDGE_,
12
+ _SPACE_SHARE_,
10
13
  BREAKOUTS,
11
14
  EVENT_TRIGGERS,
12
15
  LOCUS,
@@ -18,6 +21,7 @@ import Trigger from '../common/events/trigger-proxy';
18
21
  import BEHAVIORAL_METRICS from '../metrics/constants';
19
22
  import Metrics from '../metrics';
20
23
  import {MEETING_KEY} from './meetings.types';
24
+ import {EndMeetingReason, LocusFullState} from '../locus-info/types';
21
25
 
22
26
  /**
23
27
  * Meetings Media Codec Missing Event
@@ -152,6 +156,30 @@ MeetingsUtil.parseDefaultSiteFromMeetingPreferences = (userPreferences) => {
152
156
  return result;
153
157
  };
154
158
 
159
+ MeetingsUtil.getSiteName = (site: string, multipartSitePrefixList: string[] = []) => {
160
+ if (!site) {
161
+ return null;
162
+ }
163
+
164
+ let siteName: string | undefined;
165
+
166
+ multipartSitePrefixList.forEach((multipartSitePrefix) => {
167
+ if (!siteName && site.includes(multipartSitePrefix)) {
168
+ const secondDot = site.indexOf('.', site.indexOf('.') + 1);
169
+
170
+ siteName = site.substring(0, secondDot);
171
+ }
172
+ });
173
+
174
+ if (siteName) {
175
+ return siteName;
176
+ }
177
+
178
+ siteName = site.substring(0, site.indexOf('.'));
179
+
180
+ return siteName;
181
+ };
182
+
155
183
  /**
156
184
  * Will check to see if the H.264 media codec is supported.
157
185
  * @async
@@ -266,6 +294,49 @@ MeetingsUtil.getThisDevice = (newLocus: any, deviceUrl: string) => {
266
294
  return null;
267
295
  };
268
296
 
297
+ /**
298
+ * Checks if the fullState indicates the meeting has fully ended (not just a breakout move).
299
+ * @param {Object} fullState locus fullState data
300
+ * @returns {boolean}
301
+ */
302
+ MeetingsUtil.isWholeMeetingEnded = (fullState: LocusFullState): boolean => {
303
+ return (
304
+ fullState.state === LOCUS.STATE.INACTIVE &&
305
+ fullState.endMeetingReason !== EndMeetingReason.breakoutEnded
306
+ );
307
+ };
308
+
309
+ /**
310
+ * Checks if the self state in a locus indicates a breakout move or breakout end.
311
+ * Returns true when:
312
+ * - self state is LEFT with reason MOVED (regular breakout move), OR
313
+ * - fullState is INACTIVE with endMeetingReason BREAKOUT_ENDED (breakout session ended)
314
+ * @param {Object} locus locus data
315
+ * @returns {boolean}
316
+ */
317
+ MeetingsUtil.isSelfMovedOrBreakoutEnded = (locus: any): boolean => {
318
+ const isSelfLeftMoved = locus?.self?.state === _LEFT_ && locus?.self?.reason === _MOVED_;
319
+ const isBreakoutEnded =
320
+ locus?.fullState?.state === LOCUS.STATE.INACTIVE &&
321
+ locus?.fullState?.endMeetingReason === EndMeetingReason.breakoutEnded;
322
+
323
+ return isSelfLeftMoved || isBreakoutEnded;
324
+ };
325
+
326
+ /**
327
+ * Checks if a locus is a 1:1 call using locus.fullState.type.
328
+ * Returns true when fullState.type is CALL, SIP_BRIDGE, or SPACE_SHARE.
329
+ * @param {Object} locus locus data
330
+ * @returns {boolean}
331
+ */
332
+ MeetingsUtil.isOneOnOneCall = (locus: any): boolean => {
333
+ const fullStateType = locus?.fullState?.type;
334
+
335
+ return (
336
+ fullStateType === _CALL_ || fullStateType === _SIP_BRIDGE_ || fullStateType === _SPACE_SHARE_
337
+ );
338
+ };
339
+
269
340
  /**
270
341
  * get self device joined status from locus data
271
342
  * @param {Object} meeting current meeting data
@@ -294,7 +365,10 @@ MeetingsUtil.joinedOnThisDevice = (meeting: any, newLocus: any, deviceUrl: strin
294
365
  * @private
295
366
  */
296
367
  MeetingsUtil.isBreakoutLocusDTO = (newLocus: any) => {
297
- return newLocus?.controls?.breakout?.sessionType === BREAKOUTS.SESSION_TYPES.BREAKOUT;
368
+ return (
369
+ newLocus?.controls?.breakout?.sessionType === BREAKOUTS.SESSION_TYPES.BREAKOUT ||
370
+ !!newLocus?.info?.isBreakout
371
+ );
298
372
  };
299
373
 
300
374
  /**
@@ -310,4 +384,26 @@ MeetingsUtil.isValidBreakoutLocus = (locus: any) => {
310
384
 
311
385
  return isLocusAsBreakout && !inActiveStatus && selfJoined;
312
386
  };
387
+ /**
388
+ * check if the breakout locus is associated with the main locus by comparing the breakout control url or the replaces info in self device
389
+ * @param {Object} mainLocus main locus data
390
+ * @param {Object} breakoutLocus breakout locus data
391
+ * @returns {boolean}
392
+ * @private
393
+ */
394
+ MeetingsUtil.isMainAssociatedWithBreakout = (mainLocus: any, breakoutLocus: any) => {
395
+ if (
396
+ mainLocus.controls?.breakout?.url &&
397
+ mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
398
+ ) {
399
+ return true;
400
+ }
401
+ const deviceUrl = breakoutLocus?.self?.deviceUrl;
402
+ const replaceInfo = MeetingsUtil.getThisDevice(breakoutLocus, deviceUrl)?.replaces?.[0];
403
+ if (replaceInfo?.locusUrl && replaceInfo.locusUrl === mainLocus.url) {
404
+ return true;
405
+ }
406
+
407
+ return false;
408
+ };
313
409
  export default MeetingsUtil;
@@ -27,6 +27,7 @@ export default class Member {
27
27
  isModerator: any;
28
28
  isModeratorAssignmentProhibited: any;
29
29
  isPresenterAssignmentProhibited: any;
30
+ isAttendeeAssignmentProhibited: any;
30
31
  isMutable: any;
31
32
  isNotAdmitted: any;
32
33
  isRecording: any;
@@ -292,6 +293,14 @@ export default class Member {
292
293
  */
293
294
  this.isPresenterAssignmentProhibited = null;
294
295
 
296
+ /**
297
+ * @instance
298
+ * @type {Boolean}
299
+ * @public
300
+ * @memberof Member
301
+ */
302
+ this.isAttendeeAssignmentProhibited = null;
303
+
295
304
  /**
296
305
  * @instance
297
306
  * @type {Boolean}
@@ -369,6 +378,7 @@ export default class Member {
369
378
  MemberUtil.isModeratorAssignmentProhibited(participant);
370
379
  this.isPresenterAssignmentProhibited =
371
380
  MemberUtil.isPresenterAssignmentProhibited(participant);
381
+ this.isAttendeeAssignmentProhibited = MemberUtil.isAttendeeAssignmentProhibited(participant);
372
382
  this.canApproveAIEnablement = MemberUtil.canApproveAIEnablement(participant);
373
383
  this.processStatus(participant);
374
384
  this.processRoles(participant);
@@ -103,6 +103,7 @@ export interface Participant {
103
103
  moderator: boolean; // Locus docs say this is deprecated and role control should be used instead
104
104
  moderatorAssignmentNotAllowed: boolean;
105
105
  presenterAssignmentNotAllowed: boolean;
106
+ attendeeAssignmentNotAllowed?: boolean;
106
107
  person: ParticipantPerson;
107
108
  resourceGuest: boolean;
108
109
  state: string; // probably one of MEETING_STATE.STATES