@webex/plugin-meetings 3.6.0-next.2 → 3.6.0-next.21

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 (109) hide show
  1. package/README.md +2 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/common/errors/webinar-registration-error.js +50 -0
  5. package/dist/common/errors/webinar-registration-error.js.map +1 -0
  6. package/dist/config.js +2 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +31 -2
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +1 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/index.js +10 -3
  13. package/dist/controls-options-manager/index.js.map +1 -1
  14. package/dist/controls-options-manager/types.js.map +1 -1
  15. package/dist/controls-options-manager/util.js +12 -0
  16. package/dist/controls-options-manager/util.js.map +1 -1
  17. package/dist/index.js +7 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/interpretation/index.js +1 -1
  20. package/dist/interpretation/siLanguage.js +1 -1
  21. package/dist/locus-info/controlsUtils.js +28 -4
  22. package/dist/locus-info/controlsUtils.js.map +1 -1
  23. package/dist/locus-info/fullState.js +2 -1
  24. package/dist/locus-info/fullState.js.map +1 -1
  25. package/dist/locus-info/index.js +61 -3
  26. package/dist/locus-info/index.js.map +1 -1
  27. package/dist/locus-info/parser.js +5 -1
  28. package/dist/locus-info/parser.js.map +1 -1
  29. package/dist/meeting/in-meeting-actions.js +19 -1
  30. package/dist/meeting/in-meeting-actions.js.map +1 -1
  31. package/dist/meeting/index.js +594 -431
  32. package/dist/meeting/index.js.map +1 -1
  33. package/dist/meeting/muteState.js +5 -2
  34. package/dist/meeting/muteState.js.map +1 -1
  35. package/dist/meeting-info/meeting-info-v2.js +68 -17
  36. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  37. package/dist/meetings/index.js +4 -1
  38. package/dist/meetings/index.js.map +1 -1
  39. package/dist/members/index.js +3 -2
  40. package/dist/members/index.js.map +1 -1
  41. package/dist/members/util.js +9 -5
  42. package/dist/members/util.js.map +1 -1
  43. package/dist/metrics/constants.js +2 -1
  44. package/dist/metrics/constants.js.map +1 -1
  45. package/dist/multistream/remoteMedia.js +4 -0
  46. package/dist/multistream/remoteMedia.js.map +1 -1
  47. package/dist/reachability/index.js +3 -3
  48. package/dist/reachability/index.js.map +1 -1
  49. package/dist/reachability/request.js +2 -1
  50. package/dist/reachability/request.js.map +1 -1
  51. package/dist/types/common/errors/webinar-registration-error.d.ts +14 -0
  52. package/dist/types/config.d.ts +1 -0
  53. package/dist/types/constants.d.ts +25 -0
  54. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  55. package/dist/types/controls-options-manager/index.d.ts +2 -1
  56. package/dist/types/controls-options-manager/types.d.ts +2 -0
  57. package/dist/types/index.d.ts +2 -1
  58. package/dist/types/locus-info/index.d.ts +9 -0
  59. package/dist/types/meeting/in-meeting-actions.d.ts +18 -0
  60. package/dist/types/meeting/index.d.ts +12 -1
  61. package/dist/types/meeting/muteState.d.ts +2 -1
  62. package/dist/types/meeting-info/meeting-info-v2.d.ts +23 -0
  63. package/dist/types/members/index.d.ts +2 -1
  64. package/dist/types/members/util.d.ts +3 -1
  65. package/dist/types/metrics/constants.d.ts +1 -0
  66. package/dist/types/multistream/remoteMedia.d.ts +1 -0
  67. package/dist/webinar/index.js +32 -19
  68. package/dist/webinar/index.js.map +1 -1
  69. package/package.json +22 -22
  70. package/src/common/errors/webinar-registration-error.ts +27 -0
  71. package/src/config.ts +1 -0
  72. package/src/constants.ts +31 -0
  73. package/src/controls-options-manager/enums.ts +1 -0
  74. package/src/controls-options-manager/index.ts +19 -2
  75. package/src/controls-options-manager/types.ts +2 -0
  76. package/src/controls-options-manager/util.ts +12 -0
  77. package/src/index.ts +2 -0
  78. package/src/locus-info/controlsUtils.ts +46 -2
  79. package/src/locus-info/fullState.ts +1 -0
  80. package/src/locus-info/index.ts +60 -0
  81. package/src/locus-info/parser.ts +8 -1
  82. package/src/meeting/in-meeting-actions.ts +37 -0
  83. package/src/meeting/index.ts +139 -20
  84. package/src/meeting/muteState.ts +6 -2
  85. package/src/meeting-info/meeting-info-v2.ts +51 -0
  86. package/src/meetings/index.ts +49 -40
  87. package/src/members/index.ts +4 -2
  88. package/src/members/util.ts +3 -1
  89. package/src/metrics/constants.ts +1 -0
  90. package/src/multistream/remoteMedia.ts +5 -0
  91. package/src/reachability/index.ts +3 -3
  92. package/src/reachability/request.ts +1 -0
  93. package/src/webinar/index.ts +31 -17
  94. package/test/unit/spec/controls-options-manager/index.js +56 -32
  95. package/test/unit/spec/controls-options-manager/util.js +44 -0
  96. package/test/unit/spec/locus-info/controlsUtils.js +80 -4
  97. package/test/unit/spec/locus-info/index.js +88 -2
  98. package/test/unit/spec/meeting/in-meeting-actions.ts +18 -0
  99. package/test/unit/spec/meeting/index.js +263 -64
  100. package/test/unit/spec/meeting/muteState.js +8 -4
  101. package/test/unit/spec/meeting-info/meetinginfov2.js +37 -0
  102. package/test/unit/spec/meetings/index.js +16 -1
  103. package/test/unit/spec/members/index.js +25 -2
  104. package/test/unit/spec/members/request.js +37 -3
  105. package/test/unit/spec/members/utils.js +15 -1
  106. package/test/unit/spec/multistream/remoteMedia.ts +16 -2
  107. package/test/unit/spec/reachability/index.ts +1 -1
  108. package/test/unit/spec/reachability/request.js +13 -8
  109. package/test/unit/spec/webinar/index.ts +82 -16
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import {MEETINGS} from '../constants';
6
+ import ControlsOptionsUtil from '../controls-options-manager/util';
6
7
 
7
8
  /**
8
9
  * IInMeetingActions
@@ -64,6 +65,10 @@ interface IInMeetingActions {
64
65
  canUpdateShareControl?: boolean;
65
66
  canEnableViewTheParticipantsList?: boolean;
66
67
  canDisableViewTheParticipantsList?: boolean;
68
+ canEnableViewTheParticipantsListPanelist?: boolean;
69
+ canDisableViewTheParticipantsListPanelist?: boolean;
70
+ canEnableShowAttendeeCount?: boolean;
71
+ canDisableShowAttendeeCount?: boolean;
67
72
  canEnableRaiseHand?: boolean;
68
73
  canDisableRaiseHand?: boolean;
69
74
  canEnableVideo?: boolean;
@@ -83,6 +88,11 @@ interface IInMeetingActions {
83
88
  canShareWhiteBoard?: boolean;
84
89
  enforceVirtualBackground?: boolean;
85
90
  canPollingAndQA?: boolean;
91
+ canStartWebcast?: boolean;
92
+ canStopWebcast?: boolean;
93
+ canShowStageView?: boolean;
94
+ canEnableStageView?: boolean;
95
+ canDisableStageView?: boolean;
86
96
  }
87
97
 
88
98
  /**
@@ -201,6 +211,14 @@ export default class InMeetingActions implements IInMeetingActions {
201
211
 
202
212
  canDisableViewTheParticipantsList = null;
203
213
 
214
+ canEnableViewTheParticipantsListPanelist = null;
215
+
216
+ canDisableViewTheParticipantsListPanelist = null;
217
+
218
+ canEnableShowAttendeeCount = null;
219
+
220
+ canDisableShowAttendeeCount = null;
221
+
204
222
  canEnableRaiseHand = null;
205
223
 
206
224
  canDisableRaiseHand = null;
@@ -238,6 +256,16 @@ export default class InMeetingActions implements IInMeetingActions {
238
256
  canShareWhiteBoard = null;
239
257
 
240
258
  canPollingAndQA = null;
259
+
260
+ canStartWebcast = null;
261
+
262
+ canStopWebcast = null;
263
+
264
+ canShowStageView = null;
265
+
266
+ canEnableStageView = null;
267
+
268
+ canDisableStageView = null;
241
269
  /**
242
270
  * Returns all meeting action options
243
271
  * @returns {Object}
@@ -298,6 +326,10 @@ export default class InMeetingActions implements IInMeetingActions {
298
326
  canUpdateShareControl: this.canUpdateShareControl,
299
327
  canEnableViewTheParticipantsList: this.canEnableViewTheParticipantsList,
300
328
  canDisableViewTheParticipantsList: this.canDisableViewTheParticipantsList,
329
+ canEnableViewTheParticipantsListPanelist: this.canEnableViewTheParticipantsListPanelist,
330
+ canDisableViewTheParticipantsListPanelist: this.canDisableViewTheParticipantsListPanelist,
331
+ canEnableShowAttendeeCount: this.canEnableShowAttendeeCount,
332
+ canDisableShowAttendeeCount: this.canDisableShowAttendeeCount,
301
333
  canEnableRaiseHand: this.canEnableRaiseHand,
302
334
  canDisableRaiseHand: this.canDisableRaiseHand,
303
335
  canEnableVideo: this.canEnableVideo,
@@ -317,6 +349,11 @@ export default class InMeetingActions implements IInMeetingActions {
317
349
  supportHDV: this.supportHDV,
318
350
  canShareWhiteBoard: this.canShareWhiteBoard,
319
351
  canPollingAndQA: this.canPollingAndQA,
352
+ canStartWebcast: this.canStartWebcast,
353
+ canStopWebcast: this.canStopWebcast,
354
+ canShowStageView: this.canShowStageView,
355
+ canEnableStageView: this.canEnableStageView,
356
+ canDisableStageView: this.canDisableStageView,
320
357
  });
321
358
 
322
359
  /**
@@ -128,6 +128,7 @@ import {
128
128
  MeetingInfoV2PasswordError,
129
129
  MeetingInfoV2CaptchaError,
130
130
  MeetingInfoV2PolicyError,
131
+ MeetingInfoV2WebinarRegistrationError,
131
132
  } from '../meeting-info/meeting-info-v2';
132
133
  import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
133
134
  import SendSlotManager from '../multistream/sendSlotManager';
@@ -156,6 +157,7 @@ import ControlsOptionsManager from '../controls-options-manager';
156
157
  import PermissionError from '../common/errors/permission';
157
158
  import {LocusMediaRequest} from './locusMediaRequest';
158
159
  import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
160
+ import WebinarRegistrationError from '../common/errors/webinar-registration-error';
159
161
 
160
162
  // default callback so we don't call an undefined function, but in practice it should never be used
161
163
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -1759,8 +1761,16 @@ export default class Meeting extends StatelessWebexPlugin {
1759
1761
  if (err.meetingInfo) {
1760
1762
  this.meetingInfo = err.meetingInfo;
1761
1763
  }
1762
-
1763
1764
  throw new PermissionError();
1765
+ } else if (err instanceof MeetingInfoV2WebinarRegistrationError) {
1766
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
1767
+ this.meetingInfoFailureCode = err.wbxAppApiCode;
1768
+
1769
+ if (err.meetingInfo) {
1770
+ this.meetingInfo = err.meetingInfo;
1771
+ }
1772
+
1773
+ throw new WebinarRegistrationError();
1764
1774
  } else if (err instanceof MeetingInfoV2PasswordError) {
1765
1775
  LoggerProxy.logger.info(
1766
1776
  // @ts-ignore
@@ -2004,6 +2014,7 @@ export default class Meeting extends StatelessWebexPlugin {
2004
2014
  this.setUpLocusInfoSelfListener();
2005
2015
  this.setUpLocusInfoMeetingListener();
2006
2016
  this.setUpLocusServicesListener();
2017
+ this.setUpLocusResourcesListener();
2007
2018
  // members update listeners
2008
2019
  this.setUpLocusFullStateListener();
2009
2020
  this.setUpLocusUrlListener();
@@ -2625,6 +2636,42 @@ export default class Meeting extends StatelessWebexPlugin {
2625
2636
  );
2626
2637
  });
2627
2638
 
2639
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2640
+ Trigger.trigger(
2641
+ this,
2642
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2643
+ EVENT_TRIGGERS.MEETING_CONTROLS_WEBCAST_UPDATED,
2644
+ {state}
2645
+ );
2646
+ });
2647
+
2648
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_FULL_CHANGED, ({state}) => {
2649
+ Trigger.trigger(
2650
+ this,
2651
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2652
+ EVENT_TRIGGERS.MEETING_CONTROLS_MEETING_FULL_UPDATED,
2653
+ {state}
2654
+ );
2655
+ });
2656
+
2657
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_PRACTICE_SESSION_STATUS_UPDATED, ({state}) => {
2658
+ Trigger.trigger(
2659
+ this,
2660
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2661
+ EVENT_TRIGGERS.MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED,
2662
+ {state}
2663
+ );
2664
+ });
2665
+
2666
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_STAGE_VIEW_UPDATED, ({state}) => {
2667
+ Trigger.trigger(
2668
+ this,
2669
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2670
+ EVENT_TRIGGERS.MEETING_CONTROLS_STAGE_VIEW_UPDATED,
2671
+ {state}
2672
+ );
2673
+ });
2674
+
2628
2675
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED, ({state}) => {
2629
2676
  Trigger.trigger(
2630
2677
  this,
@@ -2986,10 +3033,20 @@ export default class Meeting extends StatelessWebexPlugin {
2986
3033
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
2987
3034
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
2988
3035
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
2989
- this.webinar.webcastUrlUpdate(payload?.services?.webcast?.url);
2990
- this.webinar.webinarAttendeesSearchingUrlUpdate(
2991
- payload?.services?.webinarAttendeesSearching?.url
2992
- );
3036
+ });
3037
+ }
3038
+
3039
+ /**
3040
+ * Set up the locus info resources link listener
3041
+ * update the locusInfo for webcast instance url
3042
+ * @param {Object} payload - The event payload
3043
+ * @returns {undefined}
3044
+ * @private
3045
+ * @memberof Meeting
3046
+ */
3047
+ private setUpLocusResourcesListener() {
3048
+ this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_RESOURCES, (payload) => {
3049
+ this.webinar.updateWebcastUrl(payload);
2993
3050
  });
2994
3051
  }
2995
3052
 
@@ -3094,7 +3151,7 @@ export default class Meeting extends StatelessWebexPlugin {
3094
3151
  private setUpLocusInfoSelfListener() {
3095
3152
  this.locusInfo.on(LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED, (payload) => {
3096
3153
  if (this.audio) {
3097
- this.audio.handleServerLocalUnmuteRequired(this);
3154
+ this.audio.handleServerLocalUnmuteRequired(this, payload.unmuteAllowed);
3098
3155
  Trigger.trigger(
3099
3156
  this,
3100
3157
  {
@@ -3301,7 +3358,7 @@ export default class Meeting extends StatelessWebexPlugin {
3301
3358
  this.simultaneousInterpretation.updateCanManageInterpreters(
3302
3359
  payload.newRoles?.includes(SELF_ROLES.MODERATOR)
3303
3360
  );
3304
- this.webinar.updateCanManageWebcast(payload.newRoles?.includes(SELF_ROLES.MODERATOR));
3361
+ this.webinar.updateRoleChanged(payload);
3305
3362
  Trigger.trigger(
3306
3363
  this,
3307
3364
  {
@@ -3795,6 +3852,22 @@ export default class Meeting extends StatelessWebexPlugin {
3795
3852
  requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST],
3796
3853
  displayHints: this.userDisplayHints,
3797
3854
  }),
3855
+ canEnableViewTheParticipantsListPanelist: ControlsOptionsUtil.hasHints({
3856
+ requiredHints: [DISPLAY_HINTS.ENABLE_VIEW_THE_PARTICIPANT_LIST_PANELIST],
3857
+ displayHints: this.userDisplayHints,
3858
+ }),
3859
+ canDisableViewTheParticipantsListPanelist: ControlsOptionsUtil.hasHints({
3860
+ requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST_PANELIST],
3861
+ displayHints: this.userDisplayHints,
3862
+ }),
3863
+ canEnableShowAttendeeCount: ControlsOptionsUtil.hasHints({
3864
+ requiredHints: [DISPLAY_HINTS.ENABLE_SHOW_ATTENDEE_COUNT],
3865
+ displayHints: this.userDisplayHints,
3866
+ }),
3867
+ canDisableShowAttendeeCount: ControlsOptionsUtil.hasHints({
3868
+ requiredHints: [DISPLAY_HINTS.DISABLE_SHOW_ATTENDEE_COUNT],
3869
+ displayHints: this.userDisplayHints,
3870
+ }),
3798
3871
  canEnableRaiseHand: ControlsOptionsUtil.hasHints({
3799
3872
  requiredHints: [DISPLAY_HINTS.ENABLE_RAISE_HAND],
3800
3873
  displayHints: this.userDisplayHints,
@@ -3811,6 +3884,26 @@ export default class Meeting extends StatelessWebexPlugin {
3811
3884
  requiredHints: [DISPLAY_HINTS.DISABLE_VIDEO],
3812
3885
  displayHints: this.userDisplayHints,
3813
3886
  }),
3887
+ canStartWebcast: ControlsOptionsUtil.hasHints({
3888
+ requiredHints: [DISPLAY_HINTS.WEBCAST_CONTROL_START],
3889
+ displayHints: this.userDisplayHints,
3890
+ }),
3891
+ canStopWebcast: ControlsOptionsUtil.hasHints({
3892
+ requiredHints: [DISPLAY_HINTS.WEBCAST_CONTROL_STOP],
3893
+ displayHints: this.userDisplayHints,
3894
+ }),
3895
+ canShowStageView: ControlsOptionsUtil.hasHints({
3896
+ requiredHints: [DISPLAY_HINTS.STAGE_VIEW_ACTIVE],
3897
+ displayHints: this.userDisplayHints,
3898
+ }),
3899
+ canEnableStageView: ControlsOptionsUtil.hasHints({
3900
+ requiredHints: [DISPLAY_HINTS.ENABLE_STAGE_VIEW],
3901
+ displayHints: this.userDisplayHints,
3902
+ }),
3903
+ canDisableStageView: ControlsOptionsUtil.hasHints({
3904
+ requiredHints: [DISPLAY_HINTS.DISABLE_STAGE_VIEW],
3905
+ displayHints: this.userDisplayHints,
3906
+ }),
3814
3907
  canShareFile:
3815
3908
  (ControlsOptionsUtil.hasHints({
3816
3909
  requiredHints: [DISPLAY_HINTS.SHARE_FILE],
@@ -4811,6 +4904,8 @@ export default class Meeting extends StatelessWebexPlugin {
4811
4904
  );
4812
4905
  }
4813
4906
 
4907
+ this.cleanUpBeforeReconnection();
4908
+
4814
4909
  return this.reconnectionManager
4815
4910
  .reconnect(options, async () => {
4816
4911
  await this.waitForRemoteSDPAnswer();
@@ -6228,7 +6323,7 @@ export default class Meeting extends StatelessWebexPlugin {
6228
6323
  this.mediaProperties.webrtcMediaConnection.on(
6229
6324
  MediaConnectionEventNames.ICE_CANDIDATE,
6230
6325
  (event) => {
6231
- if (event.candidate) {
6326
+ if (event.candidate && event.candidate.candidate && event.candidate.candidate.length > 0) {
6232
6327
  this.iceCandidatesCount += 1;
6233
6328
  }
6234
6329
  }
@@ -6939,6 +7034,23 @@ export default class Meeting extends StatelessWebexPlugin {
6939
7034
  }
6940
7035
  }
6941
7036
 
7037
+ private async cleanUpBeforeReconnection(): Promise<void> {
7038
+ try {
7039
+ // when media fails, we want to upload a webrtc dump to see whats going on
7040
+ // this function is async, but returns once the stats have been gathered
7041
+ await this.forceSendStatsReport({callFrom: 'cleanUpBeforeReconnection'});
7042
+
7043
+ if (this.statsAnalyzer) {
7044
+ await this.statsAnalyzer.stopAnalyzer();
7045
+ }
7046
+ } catch (error) {
7047
+ LoggerProxy.logger.error(
7048
+ 'Meeting:index#cleanUpBeforeReconnection --> Error during cleanup: ',
7049
+ error
7050
+ );
7051
+ }
7052
+ }
7053
+
6942
7054
  /**
6943
7055
  * Creates an instance of LocusMediaRequest for this meeting - it is needed for doing any calls
6944
7056
  * to Locus /media API (these are used for sending Roap messages and updating audio/video mute status).
@@ -7030,7 +7142,7 @@ export default class Meeting extends StatelessWebexPlugin {
7030
7142
  shareAudioEnabled = true,
7031
7143
  shareVideoEnabled = true,
7032
7144
  remoteMediaManagerConfig,
7033
- bundlePolicy,
7145
+ bundlePolicy = 'max-bundle',
7034
7146
  } = options;
7035
7147
 
7036
7148
  this.allowMediaInLobby = options?.allowMediaInLobby;
@@ -7917,18 +8029,21 @@ export default class Meeting extends StatelessWebexPlugin {
7917
8029
  * @param {boolean} mutedEnabled
7918
8030
  * @param {boolean} disallowUnmuteEnabled
7919
8031
  * @param {boolean} muteOnEntryEnabled
8032
+ * @param {array} roles
7920
8033
  * @public
7921
8034
  * @memberof Meeting
7922
8035
  */
7923
8036
  public setMuteAll(
7924
8037
  mutedEnabled: boolean,
7925
8038
  disallowUnmuteEnabled: boolean,
7926
- muteOnEntryEnabled: boolean
8039
+ muteOnEntryEnabled: boolean,
8040
+ roles: Array<string>
7927
8041
  ) {
7928
8042
  return this.controlsOptionsManager.setMuteAll(
7929
8043
  mutedEnabled,
7930
8044
  disallowUnmuteEnabled,
7931
- muteOnEntryEnabled
8045
+ muteOnEntryEnabled,
8046
+ roles
7932
8047
  );
7933
8048
  }
7934
8049
 
@@ -8735,15 +8850,19 @@ export default class Meeting extends StatelessWebexPlugin {
8735
8850
  return;
8736
8851
  }
8737
8852
 
8738
- if (
8739
- streams?.microphone?.readyState === 'ended' ||
8740
- streams?.camera?.readyState === 'ended' ||
8741
- streams?.screenShare?.audio?.readyState === 'ended' ||
8742
- streams?.screenShare?.video?.readyState === 'ended'
8743
- ) {
8744
- throw new Error(
8745
- `Attempted to publish stream with ended readyState, correlationId=${this.correlationId}`
8746
- );
8853
+ const streamChecks = [
8854
+ {stream: streams?.microphone, name: 'microphone'},
8855
+ {stream: streams?.camera, name: 'camera'},
8856
+ {stream: streams?.screenShare?.audio, name: 'screenShare audio'},
8857
+ {stream: streams?.screenShare?.video, name: 'screenShare video'},
8858
+ ];
8859
+
8860
+ for (const {stream, name} of streamChecks) {
8861
+ if (stream?.readyState === 'ended') {
8862
+ throw new Error(
8863
+ `Attempted to publish ${name} stream with ended readyState, correlationId=${this.correlationId}`
8864
+ );
8865
+ }
8747
8866
  }
8748
8867
 
8749
8868
  let floorRequestNeeded = false;
@@ -394,21 +394,25 @@ export class MuteState {
394
394
  * @public
395
395
  * @memberof MuteState
396
396
  * @param {Object} [meeting] the meeting object
397
+ * @param {Boolean} [unmuteAllowed] whether the user is allowed to unmute self
397
398
  * @returns {undefined}
398
399
  */
399
- public handleServerLocalUnmuteRequired(meeting?: any) {
400
+ public handleServerLocalUnmuteRequired(meeting: any, unmuteAllowed: boolean) {
400
401
  if (!this.state.client.enabled) {
401
402
  LoggerProxy.logger.warn(
402
403
  `Meeting:muteState#handleServerLocalUnmuteRequired --> ${this.type}: localAudioUnmuteRequired received while ${this.type} is disabled -> local unmute will not result in ${this.type} being sent`
403
404
  );
404
405
  } else {
405
406
  LoggerProxy.logger.info(
406
- `Meeting:muteState#handleServerLocalUnmuteRequired --> ${this.type}: localAudioUnmuteRequired received -> doing local unmute`
407
+ `Meeting:muteState#handleServerLocalUnmuteRequired --> ${this.type}: localAudioUnmuteRequired received -> doing local unmute (unmuteAllowed=${unmuteAllowed})`
407
408
  );
408
409
  }
409
410
 
410
411
  // todo: I'm seeing "you can now unmute yourself " popup when this happens - but same thing happens on web.w.c so we can ignore for now
411
412
  this.state.server.remoteMute = false;
413
+ this.state.server.unmuteAllowed = unmuteAllowed;
414
+
415
+ this.applyUnmuteAllowedToStream(meeting);
412
416
 
413
417
  // change user mute state to false, but keep localMute true if overall mute state is still true
414
418
  this.muteLocalStream(meeting, false, 'localUnmuteRequired');
@@ -18,6 +18,7 @@ const ADHOC_MEETING_DEFAULT_ERROR =
18
18
  'Failed starting the adhoc meeting, Please contact support team ';
19
19
  const CAPTCHA_ERROR_REQUIRES_PASSWORD_CODES = [423005, 423006];
20
20
  const POLICY_ERROR_CODES = [403049, 403104, 403103, 403048, 403102, 403101];
21
+ const WEBINAR_REGISTRATION_ERROR_CODES = [403021, 403022, 403024];
21
22
  /**
22
23
  * Error to indicate that wbxappapi requires a password
23
24
  */
@@ -124,6 +125,31 @@ export class MeetingInfoV2CaptchaError extends Error {
124
125
  }
125
126
  }
126
127
 
128
+ /**
129
+ * Error preventing join because of a webinar registration error
130
+ */
131
+ export class MeetingInfoV2WebinarRegistrationError extends Error {
132
+ meetingInfo: any;
133
+ sdkMessage: any;
134
+ wbxAppApiCode: any;
135
+ body: any;
136
+ /**
137
+ *
138
+ * @constructor
139
+ * @param {Number} [wbxAppApiErrorCode]
140
+ * @param {Object} [meetingInfo]
141
+ * @param {String} [message]
142
+ */
143
+ constructor(wbxAppApiErrorCode?: number, meetingInfo?: object, message?: string) {
144
+ super(`${message}, code=${wbxAppApiErrorCode}`);
145
+ this.name = 'MeetingInfoV2WebinarRegistrationError';
146
+ this.sdkMessage = message;
147
+ this.stack = new Error().stack;
148
+ this.wbxAppApiCode = wbxAppApiErrorCode;
149
+ this.meetingInfo = meetingInfo;
150
+ }
151
+ }
152
+
127
153
  /**
128
154
  * @class MeetingInfo
129
155
  */
@@ -177,6 +203,29 @@ export default class MeetingInfoV2 {
177
203
  }
178
204
  };
179
205
 
206
+ /**
207
+ * Raises a handleWebinarRegistrationError for webinar registration error codes
208
+ * @param {any} err the error from the request
209
+ * @returns {void}
210
+ */
211
+ handleWebinarRegistrationError = (err) => {
212
+ if (!err.body) {
213
+ return;
214
+ }
215
+
216
+ if (WEBINAR_REGISTRATION_ERROR_CODES.includes(err.body?.code)) {
217
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.WEBINAR_REGISTRATION_ERROR, {
218
+ code: err.body?.code,
219
+ });
220
+
221
+ throw new MeetingInfoV2WebinarRegistrationError(
222
+ err.body?.code,
223
+ err.body?.data?.meetingInfo,
224
+ err.body?.message
225
+ );
226
+ }
227
+ };
228
+
180
229
  /**
181
230
  * Creates adhoc space meetings for a space by fetching the conversation infomation
182
231
  * @param {String} conversationUrl conversationUrl to start adhoc meeting on
@@ -237,6 +286,7 @@ export default class MeetingInfoV2 {
237
286
  })
238
287
  .catch((err) => {
239
288
  this.handlePolicyError(err);
289
+ this.handleWebinarRegistrationError(err);
240
290
 
241
291
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADHOC_MEETING_FAILURE, {
242
292
  reason: err.message,
@@ -391,6 +441,7 @@ export default class MeetingInfoV2 {
391
441
 
392
442
  if (err?.statusCode === 403) {
393
443
  this.handlePolicyError(err);
444
+ this.handleWebinarRegistrationError(err);
394
445
 
395
446
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR, {
396
447
  reason: err.message,
@@ -56,6 +56,7 @@ import MeetingCollection from './collection';
56
56
  import {MEETING_KEY, INoiseReductionEffect, IVirtualBackgroundEffect} from './meetings.types';
57
57
  import MeetingsUtil from './util';
58
58
  import PermissionError from '../common/errors/permission';
59
+ import WebinarRegistrationError from '../common/errors/webinar-registration-error';
59
60
  import {SpaceIDDeprecatedError} from '../common/errors/webex-errors';
60
61
  import NoMeetingInfoError from '../common/errors/no-meeting-info';
61
62
 
@@ -1043,48 +1044,55 @@ export default class Meetings extends WebexPlugin {
1043
1044
  */
1044
1045
  fetchUserPreferredWebexSite() {
1045
1046
  // @ts-ignore
1046
- return this.webex.people._getMe().then((me) => {
1047
- const isGuestUser = me.type === 'appuser';
1048
- if (!isGuestUser) {
1049
- return this.request.getMeetingPreferences().then((res) => {
1050
- if (res) {
1051
- const preferredWebexSite = MeetingsUtil.parseDefaultSiteFromMeetingPreferences(res);
1052
- this.preferredWebexSite = preferredWebexSite;
1053
- // @ts-ignore
1054
- this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1055
- }
1047
+ return this.webex.people
1048
+ ._getMe()
1049
+ .then((me) => {
1050
+ const isGuestUser = me.type === 'appuser';
1051
+ if (!isGuestUser) {
1052
+ return this.request.getMeetingPreferences().then((res) => {
1053
+ if (res) {
1054
+ const preferredWebexSite = MeetingsUtil.parseDefaultSiteFromMeetingPreferences(res);
1055
+ this.preferredWebexSite = preferredWebexSite;
1056
+ // @ts-ignore
1057
+ this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1058
+ }
1056
1059
 
1057
- // fall back to getting the preferred site from the user information
1058
- if (!this.preferredWebexSite) {
1059
- // @ts-ignore
1060
- return this.webex.internal.user
1061
- .get()
1062
- .then((user) => {
1063
- const preferredWebexSite =
1064
- user?.userPreferences?.userPreferencesItems?.preferredWebExSite;
1065
- if (preferredWebexSite) {
1066
- this.preferredWebexSite = preferredWebexSite;
1067
- // @ts-ignore
1068
- this.webex.internal.services
1069
- ._getCatalog()
1070
- .addAllowedDomains([preferredWebexSite]);
1071
- } else {
1072
- throw new Error('site not found');
1073
- }
1074
- })
1075
- .catch(() => {
1076
- LoggerProxy.logger.error(
1077
- 'Failed to fetch preferred site from user - no site will be set'
1078
- );
1079
- });
1080
- }
1060
+ // fall back to getting the preferred site from the user information
1061
+ if (!this.preferredWebexSite) {
1062
+ // @ts-ignore
1063
+ return this.webex.internal.user
1064
+ .get()
1065
+ .then((user) => {
1066
+ const preferredWebexSite =
1067
+ user?.userPreferences?.userPreferencesItems?.preferredWebExSite;
1068
+ if (preferredWebexSite) {
1069
+ this.preferredWebexSite = preferredWebexSite;
1070
+ // @ts-ignore
1071
+ this.webex.internal.services
1072
+ ._getCatalog()
1073
+ .addAllowedDomains([preferredWebexSite]);
1074
+ } else {
1075
+ throw new Error('site not found');
1076
+ }
1077
+ })
1078
+ .catch(() => {
1079
+ LoggerProxy.logger.error(
1080
+ 'Failed to fetch preferred site from user - no site will be set'
1081
+ );
1082
+ });
1083
+ }
1081
1084
 
1082
- return Promise.resolve();
1083
- });
1084
- }
1085
+ return Promise.resolve();
1086
+ });
1087
+ }
1085
1088
 
1086
- return Promise.resolve();
1087
- });
1089
+ return Promise.resolve();
1090
+ })
1091
+ .catch(() => {
1092
+ LoggerProxy.logger.error(
1093
+ 'Failed to retrieve user information. No preferredWebexSite will be set'
1094
+ );
1095
+ });
1088
1096
  }
1089
1097
 
1090
1098
  /**
@@ -1397,7 +1405,8 @@ export default class Meetings extends WebexPlugin {
1397
1405
  if (
1398
1406
  !(err instanceof CaptchaError) &&
1399
1407
  !(err instanceof PasswordError) &&
1400
- !(err instanceof PermissionError)
1408
+ !(err instanceof PermissionError) &&
1409
+ !(err instanceof WebinarRegistrationError)
1401
1410
  ) {
1402
1411
  LoggerProxy.logger.info(
1403
1412
  `Meetings:index#createMeeting --> Info Unable to fetch meeting info for ${destination}.`
@@ -915,11 +915,12 @@ export default class Members extends StatelessWebexPlugin {
915
915
  /**
916
916
  * Lower all hands of members in a meeting
917
917
  * @param {String} requestingMemberId - id of the participant which requested the lower all hands
918
+ * @param {array} roles which should be lowered
918
919
  * @returns {Promise}
919
920
  * @public
920
921
  * @memberof Members
921
922
  */
922
- public lowerAllHands(requestingMemberId: string) {
923
+ public lowerAllHands(requestingMemberId: string, roles: Array<string>) {
923
924
  if (!this.locusUrl) {
924
925
  return Promise.reject(
925
926
  new ParameterError(
@@ -936,7 +937,8 @@ export default class Members extends StatelessWebexPlugin {
936
937
  }
937
938
  const options = MembersUtil.generateLowerAllHandsMemberOptions(
938
939
  requestingMemberId,
939
- this.locusUrl
940
+ this.locusUrl,
941
+ roles
940
942
  );
941
943
 
942
944
  return this.membersRequest.lowerAllHandsMember(options);
@@ -166,9 +166,10 @@ const MembersUtil = {
166
166
  locusUrl,
167
167
  }),
168
168
 
169
- generateLowerAllHandsMemberOptions: (requestingParticipantId, locusUrl) => ({
169
+ generateLowerAllHandsMemberOptions: (requestingParticipantId, locusUrl, roles) => ({
170
170
  requestingParticipantId,
171
171
  locusUrl,
172
+ ...(roles !== undefined && {roles}),
172
173
  }),
173
174
 
174
175
  /**
@@ -253,6 +254,7 @@ const MembersUtil = {
253
254
  const body = {
254
255
  hand: {
255
256
  raised: false,
257
+ ...(options.roles !== undefined && {roles: options.roles}),
256
258
  },
257
259
  requestingParticipantId: options.requestingParticipantId,
258
260
  };
@@ -70,6 +70,7 @@ const BEHAVIORAL_METRICS = {
70
70
  ROAP_HTTP_RESPONSE_MISSING: 'js_sdk_roap_http_response_missing',
71
71
  TURN_DISCOVERY_REQUIRES_OK: 'js_sdk_turn_discovery_requires_ok',
72
72
  REACHABILITY_COMPLETED: 'js_sdk_reachability_completed',
73
+ WEBINAR_REGISTRATION_ERROR: 'js_sdk_webinar_registration_error',
73
74
  };
74
75
 
75
76
  export {BEHAVIORAL_METRICS as default};
@@ -107,11 +107,16 @@ export class RemoteMedia extends EventsScope {
107
107
  * to restrict the requested resolution to this size
108
108
  * @param width width of the video element
109
109
  * @param height height of the video element
110
+ * @note width/height of 0 will be ignored
110
111
  */
111
112
  public setSizeHint(width, height) {
112
113
  // only base on height for now
113
114
  let fs: number;
114
115
 
116
+ if (width === 0 || height === 0) {
117
+ return;
118
+ }
119
+
115
120
  if (height < 135) {
116
121
  fs = 60;
117
122
  } else if (height < 270) {
@@ -154,10 +154,10 @@ export default class Reachability extends EventsScope {
154
154
  try {
155
155
  this.lastTrigger = trigger;
156
156
 
157
- // kick off ip version detection. For now we don't await it, as we're doing it
158
- // to gather the timings and send them with our reachability metrics
157
+ // kick off ip version detection. We don't await it, as we don't want to waste time
158
+ // and if it fails, that's ok we can still carry on
159
159
  // @ts-ignore
160
- this.webex.internal.device.ipNetworkDetector.detect();
160
+ this.webex.internal.device.ipNetworkDetector.detect(true);
161
161
 
162
162
  const {clusters, joinCookie} = await this.getClusters();
163
163