@webex/plugin-meetings 3.7.0-next.2 → 3.7.0-next.20

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 (82) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  4. package/dist/common/errors/join-webinar-error.js.map +1 -0
  5. package/dist/config.js +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +27 -6
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.js +8 -15
  10. package/dist/index.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/meeting/in-meeting-actions.js +11 -1
  14. package/dist/meeting/in-meeting-actions.js.map +1 -1
  15. package/dist/meeting/index.js +84 -131
  16. package/dist/meeting/index.js.map +1 -1
  17. package/dist/meeting/util.js +0 -8
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meeting-info/meeting-info-v2.js +29 -17
  20. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  21. package/dist/meetings/index.js +6 -3
  22. package/dist/meetings/index.js.map +1 -1
  23. package/dist/members/util.js +4 -2
  24. package/dist/members/util.js.map +1 -1
  25. package/dist/metrics/constants.js +3 -1
  26. package/dist/metrics/constants.js.map +1 -1
  27. package/dist/multistream/remoteMedia.js +30 -15
  28. package/dist/multistream/remoteMedia.js.map +1 -1
  29. package/dist/reachability/clusterReachability.js +12 -11
  30. package/dist/reachability/clusterReachability.js.map +1 -1
  31. package/dist/recording-controller/enums.js +8 -4
  32. package/dist/recording-controller/enums.js.map +1 -1
  33. package/dist/recording-controller/index.js +18 -9
  34. package/dist/recording-controller/index.js.map +1 -1
  35. package/dist/recording-controller/util.js +13 -9
  36. package/dist/recording-controller/util.js.map +1 -1
  37. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  38. package/dist/types/constants.d.ts +19 -1
  39. package/dist/types/index.d.ts +3 -3
  40. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  41. package/dist/types/meeting/index.d.ts +1 -10
  42. package/dist/types/meeting/util.d.ts +0 -1
  43. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  44. package/dist/types/meetings/index.d.ts +3 -0
  45. package/dist/types/members/util.d.ts +2 -0
  46. package/dist/types/metrics/constants.d.ts +3 -1
  47. package/dist/types/recording-controller/enums.d.ts +5 -2
  48. package/dist/types/recording-controller/index.d.ts +1 -0
  49. package/dist/types/recording-controller/util.d.ts +2 -1
  50. package/dist/webinar/index.js +357 -7
  51. package/dist/webinar/index.js.map +1 -1
  52. package/package.json +22 -22
  53. package/src/common/errors/join-webinar-error.ts +24 -0
  54. package/src/config.ts +1 -1
  55. package/src/constants.ts +24 -3
  56. package/src/index.ts +2 -3
  57. package/src/meeting/in-meeting-actions.ts +21 -0
  58. package/src/meeting/index.ts +54 -48
  59. package/src/meeting/util.ts +0 -9
  60. package/src/meeting-info/meeting-info-v2.ts +23 -11
  61. package/src/meetings/index.ts +8 -2
  62. package/src/members/util.ts +1 -0
  63. package/src/metrics/constants.ts +3 -1
  64. package/src/multistream/remoteMedia.ts +28 -15
  65. package/src/reachability/clusterReachability.ts +4 -1
  66. package/src/recording-controller/enums.ts +5 -2
  67. package/src/recording-controller/index.ts +17 -4
  68. package/src/recording-controller/util.ts +20 -5
  69. package/src/webinar/index.ts +201 -9
  70. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -1
  71. package/test/unit/spec/meeting/index.js +106 -77
  72. package/test/unit/spec/meeting/utils.js +0 -15
  73. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  74. package/test/unit/spec/meetings/index.js +9 -5
  75. package/test/unit/spec/members/utils.js +95 -0
  76. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  77. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  78. package/test/unit/spec/recording-controller/index.js +61 -5
  79. package/test/unit/spec/recording-controller/util.js +39 -3
  80. package/test/unit/spec/webinar/index.ts +363 -0
  81. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  82. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -26,6 +26,7 @@ interface IInMeetingActions {
26
26
  canStartRecording?: boolean;
27
27
  canPauseRecording?: boolean;
28
28
  canResumeRecording?: boolean;
29
+ isPremiseRecordingEnabled?: boolean;
29
30
  canStopRecording?: boolean;
30
31
  canRaiseHand?: boolean;
31
32
  canLowerAllHands?: boolean;
@@ -93,6 +94,10 @@ interface IInMeetingActions {
93
94
  canShowStageView?: boolean;
94
95
  canEnableStageView?: boolean;
95
96
  canDisableStageView?: boolean;
97
+ isPracticeSessionOn?: boolean;
98
+ isPracticeSessionOff?: boolean;
99
+ canStartPracticeSession?: boolean;
100
+ canStopPracticeSession?: boolean;
96
101
  }
97
102
 
98
103
  /**
@@ -117,6 +122,8 @@ export default class InMeetingActions implements IInMeetingActions {
117
122
 
118
123
  canResumeRecording = null;
119
124
 
125
+ isPremiseRecordingEnabled = null;
126
+
120
127
  canStopRecording = null;
121
128
 
122
129
  canSetMuteOnEntry = null;
@@ -266,6 +273,15 @@ export default class InMeetingActions implements IInMeetingActions {
266
273
  canEnableStageView = null;
267
274
 
268
275
  canDisableStageView = null;
276
+
277
+ isPracticeSessionOn = null;
278
+
279
+ isPracticeSessionOff = null;
280
+
281
+ canStartPracticeSession = null;
282
+
283
+ canStopPracticeSession = null;
284
+
269
285
  /**
270
286
  * Returns all meeting action options
271
287
  * @returns {Object}
@@ -288,6 +304,7 @@ export default class InMeetingActions implements IInMeetingActions {
288
304
  canPauseRecording: this.canPauseRecording,
289
305
  canResumeRecording: this.canResumeRecording,
290
306
  canStopRecording: this.canStopRecording,
307
+ isPremiseRecordingEnabled: this.isPremiseRecordingEnabled,
291
308
  canRaiseHand: this.canRaiseHand,
292
309
  canLowerAllHands: this.canLowerAllHands,
293
310
  canLowerSomeoneElsesHand: this.canLowerSomeoneElsesHand,
@@ -354,6 +371,10 @@ export default class InMeetingActions implements IInMeetingActions {
354
371
  canShowStageView: this.canShowStageView,
355
372
  canEnableStageView: this.canEnableStageView,
356
373
  canDisableStageView: this.canDisableStageView,
374
+ isPracticeSessionOn: this.isPracticeSessionOn,
375
+ isPracticeSessionOff: this.isPracticeSessionOff,
376
+ canStartPracticeSession: this.canStartPracticeSession,
377
+ canStopPracticeSession: this.canStopPracticeSession,
357
378
  });
358
379
 
359
380
  /**
@@ -31,7 +31,6 @@ import {
31
31
  } from '@webex/internal-media-core';
32
32
 
33
33
  import {
34
- getDevices,
35
34
  LocalStream,
36
35
  LocalCameraStream,
37
36
  LocalDisplayStream,
@@ -122,6 +121,8 @@ import {
122
121
  MEETING_PERMISSION_TOKEN_REFRESH_REASON,
123
122
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
124
123
  NAMED_MEDIA_GROUP_TYPE_AUDIO,
124
+ WEBINAR_ERROR_WEBCAST,
125
+ WEBINAR_ERROR_REGISTRATIONID,
125
126
  } from '../constants';
126
127
  import BEHAVIORAL_METRICS from '../metrics/constants';
127
128
  import ParameterError from '../common/errors/parameter';
@@ -129,7 +130,7 @@ import {
129
130
  MeetingInfoV2PasswordError,
130
131
  MeetingInfoV2CaptchaError,
131
132
  MeetingInfoV2PolicyError,
132
- MeetingInfoV2WebinarRegistrationError,
133
+ MeetingInfoV2JoinWebinarError,
133
134
  } from '../meeting-info/meeting-info-v2';
134
135
  import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
135
136
  import SendSlotManager from '../multistream/sendSlotManager';
@@ -158,7 +159,7 @@ import ControlsOptionsManager from '../controls-options-manager';
158
159
  import PermissionError from '../common/errors/permission';
159
160
  import {LocusMediaRequest} from './locusMediaRequest';
160
161
  import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
161
- import WebinarRegistrationError from '../common/errors/webinar-registration-error';
162
+ import JoinWebinarError from '../common/errors/join-webinar-error';
162
163
 
163
164
  // default callback so we don't call an undefined function, but in practice it should never be used
164
165
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -1767,15 +1768,20 @@ export default class Meeting extends StatelessWebexPlugin {
1767
1768
  this.meetingInfo = err.meetingInfo;
1768
1769
  }
1769
1770
  throw new PermissionError();
1770
- } else if (err instanceof MeetingInfoV2WebinarRegistrationError) {
1771
+ } else if (err instanceof MeetingInfoV2JoinWebinarError) {
1771
1772
  this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
1773
+ if (WEBINAR_ERROR_WEBCAST.includes(err.wbxAppApiCode)) {
1774
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST;
1775
+ } else if (WEBINAR_ERROR_REGISTRATIONID.includes(err.wbxAppApiCode)) {
1776
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID;
1777
+ }
1772
1778
  this.meetingInfoFailureCode = err.wbxAppApiCode;
1773
1779
 
1774
1780
  if (err.meetingInfo) {
1775
1781
  this.meetingInfo = err.meetingInfo;
1776
1782
  }
1777
1783
 
1778
- throw new WebinarRegistrationError();
1784
+ throw new JoinWebinarError();
1779
1785
  } else if (err instanceof MeetingInfoV2PasswordError) {
1780
1786
  LoggerProxy.logger.info(
1781
1787
  // @ts-ignore
@@ -2660,6 +2666,7 @@ export default class Meeting extends StatelessWebexPlugin {
2660
2666
  });
2661
2667
 
2662
2668
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_PRACTICE_SESSION_STATUS_UPDATED, ({state}) => {
2669
+ this.webinar.updatePracticeSessionStatus(state);
2663
2670
  Trigger.trigger(
2664
2671
  this,
2665
2672
  {file: 'meeting/index', function: 'setupLocusControlsListener'},
@@ -3254,6 +3261,9 @@ export default class Meeting extends StatelessWebexPlugin {
3254
3261
  options: {meetingId: this.id},
3255
3262
  });
3256
3263
  }
3264
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY, {
3265
+ correlation_id: this.correlationId,
3266
+ });
3257
3267
  this.updateLLMConnection();
3258
3268
  });
3259
3269
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
@@ -3277,6 +3287,9 @@ export default class Meeting extends StatelessWebexPlugin {
3277
3287
  name: 'client.lobby.exited',
3278
3288
  options: {meetingId: this.id},
3279
3289
  });
3290
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY, {
3291
+ correlation_id: this.correlationId,
3292
+ });
3280
3293
  }
3281
3294
  this.rtcMetrics?.sendNextMetrics();
3282
3295
  this.updateLLMConnection();
@@ -3510,6 +3523,7 @@ export default class Meeting extends StatelessWebexPlugin {
3510
3523
  emailAddress: string;
3511
3524
  email: string;
3512
3525
  phoneNumber: string;
3526
+ roles: Array<string>;
3513
3527
  },
3514
3528
  alertIfActive = true
3515
3529
  ) {
@@ -3766,6 +3780,10 @@ export default class Meeting extends StatelessWebexPlugin {
3766
3780
  this.userDisplayHints,
3767
3781
  this.selfUserPolicies
3768
3782
  ),
3783
+ isPremiseRecordingEnabled: RecordingUtil.isPremiseRecordingEnabled(
3784
+ this.userDisplayHints,
3785
+ this.selfUserPolicies
3786
+ ),
3769
3787
  canRaiseHand: MeetingUtil.canUserRaiseHand(this.userDisplayHints),
3770
3788
  canLowerAllHands: MeetingUtil.canUserLowerAllHands(this.userDisplayHints),
3771
3789
  canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(this.userDisplayHints),
@@ -3909,6 +3927,22 @@ export default class Meeting extends StatelessWebexPlugin {
3909
3927
  requiredHints: [DISPLAY_HINTS.DISABLE_STAGE_VIEW],
3910
3928
  displayHints: this.userDisplayHints,
3911
3929
  }),
3930
+ isPracticeSessionOn: ControlsOptionsUtil.hasHints({
3931
+ requiredHints: [DISPLAY_HINTS.PRACTICE_SESSION_ON],
3932
+ displayHints: this.userDisplayHints,
3933
+ }),
3934
+ isPracticeSessionOff: ControlsOptionsUtil.hasHints({
3935
+ requiredHints: [DISPLAY_HINTS.PRACTICE_SESSION_OFF],
3936
+ displayHints: this.userDisplayHints,
3937
+ }),
3938
+ canStartPracticeSession: ControlsOptionsUtil.hasHints({
3939
+ requiredHints: [DISPLAY_HINTS.SHOW_PRACTICE_SESSION_START],
3940
+ displayHints: this.userDisplayHints,
3941
+ }),
3942
+ canStopPracticeSession: ControlsOptionsUtil.hasHints({
3943
+ requiredHints: [DISPLAY_HINTS.SHOW_PRACTICE_SESSION_STOP],
3944
+ displayHints: this.userDisplayHints,
3945
+ }),
3912
3946
  canShareFile:
3913
3947
  (ControlsOptionsUtil.hasHints({
3914
3948
  requiredHints: [DISPLAY_HINTS.SHARE_FILE],
@@ -4071,10 +4105,11 @@ export default class Meeting extends StatelessWebexPlugin {
4071
4105
  */
4072
4106
  private setLogUploadTimer() {
4073
4107
  // start with short timeouts and increase them later on so in case users have very long multi-hour meetings we don't get too fragmented logs
4074
- const LOG_UPLOAD_INTERVALS = [0.1, 1, 15, 15, 30, 30, 30, 60];
4108
+ const LOG_UPLOAD_INTERVALS = [0.1, 15, 30, 60]; // in minutes
4075
4109
 
4076
4110
  const delay =
4077
4111
  1000 *
4112
+ 60 *
4078
4113
  // @ts-ignore - config coming from registerPlugin
4079
4114
  this.config.logUploadIntervalMultiplicationFactor *
4080
4115
  LOG_UPLOAD_INTERVALS[this.logUploadIntervalIndex];
@@ -5345,16 +5380,19 @@ export default class Meeting extends StatelessWebexPlugin {
5345
5380
  this.meetingFiniteStateMachine.reset();
5346
5381
  }
5347
5382
 
5348
- // @ts-ignore
5349
- this.webex.internal.newMetrics.submitClientEvent({
5350
- name: 'client.call.initiated',
5351
- payload: {
5352
- trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
5353
- isRoapCallEnabled: true,
5354
- pstnAudioType: options?.pstnAudioType,
5355
- },
5356
- options: {meetingId: this.id},
5357
- });
5383
+ // send client.call.initiated unless told not to
5384
+ if (options.sendCallInitiated !== false) {
5385
+ // @ts-ignore
5386
+ this.webex.internal.newMetrics.submitClientEvent({
5387
+ name: 'client.call.initiated',
5388
+ payload: {
5389
+ trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
5390
+ isRoapCallEnabled: true,
5391
+ pstnAudioType: options?.pstnAudioType,
5392
+ },
5393
+ options: {meetingId: this.id},
5394
+ });
5395
+ }
5358
5396
 
5359
5397
  LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
5360
5398
 
@@ -6739,32 +6777,6 @@ export default class Meeting extends StatelessWebexPlugin {
6739
6777
  }
6740
6778
  }
6741
6779
 
6742
- /**
6743
- * Handles device logging
6744
- *
6745
- * @private
6746
- * @static
6747
- * @param {boolean} isAudioEnabled
6748
- * @param {boolean} isVideoEnabled
6749
- * @returns {Promise<void>}
6750
- */
6751
-
6752
- private static async handleDeviceLogging(isAudioEnabled, isVideoEnabled): Promise<void> {
6753
- try {
6754
- let devices = [];
6755
- if (isVideoEnabled && isAudioEnabled) {
6756
- devices = await getDevices();
6757
- } else if (isVideoEnabled) {
6758
- devices = await getDevices(Media.DeviceKind.VIDEO_INPUT);
6759
- } else if (isAudioEnabled) {
6760
- devices = await getDevices(Media.DeviceKind.AUDIO_INPUT);
6761
- }
6762
- MeetingUtil.handleDeviceLogging(devices);
6763
- } catch {
6764
- // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
6765
- }
6766
- }
6767
-
6768
6780
  /**
6769
6781
  * Returns a promise. This promise is created once the local sdp offer has been successfully created and is resolved
6770
6782
  * once the remote sdp answer has been received.
@@ -7267,12 +7279,6 @@ export default class Meeting extends StatelessWebexPlugin {
7267
7279
  turnServerInfo
7268
7280
  );
7269
7281
 
7270
- if (audioEnabled || videoEnabled) {
7271
- await Meeting.handleDeviceLogging(audioEnabled, videoEnabled);
7272
- } else {
7273
- LoggerProxy.logger.info(`${LOG_HEADER} device logging not required`);
7274
- }
7275
-
7276
7282
  if (this.mediaProperties.hasLocalShareStream()) {
7277
7283
  await this.enqueueScreenShareFloorRequest();
7278
7284
  }
@@ -496,15 +496,6 @@ const MeetingUtil = {
496
496
  }
497
497
  },
498
498
 
499
- handleDeviceLogging: (devices = []) => {
500
- const LOG_HEADER = 'MeetingUtil#handleDeviceLogging -->';
501
-
502
- devices.forEach((device) => {
503
- LoggerProxy.logger.log(LOG_HEADER, `deviceId = ${device.deviceId}`);
504
- LoggerProxy.logger.log(LOG_HEADER, 'settings', JSON.stringify(device));
505
- });
506
- },
507
-
508
499
  endMeetingForAll: (meeting) => {
509
500
  if (meeting.meetingState === FULL_STATE.INACTIVE) {
510
501
  return Promise.reject(new MeetingNotActiveError());
@@ -18,7 +18,19 @@ 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
+ * 403021 - Meeting registration is required
23
+ * 403022 - Meeting registration is still pending
24
+ * 403024 - Meeting registration have been rejected
25
+ * 403137 - Registration ID verified failure
26
+ * 423007 - Registration ID input too many time,please input captcha code
27
+ * 403026 - Need to join meeting via webcast
28
+ * 403037 - Meeting join required registration ID
29
+ * 403137 - Registration ID verified failure
30
+ *
31
+ */
32
+ const JOIN_WEBINAR_ERROR_CODES = [403021, 403022, 403024, 403137, 423007, 403026, 403037, 403137];
33
+
22
34
  /**
23
35
  * Error to indicate that wbxappapi requires a password
24
36
  */
@@ -126,9 +138,9 @@ export class MeetingInfoV2CaptchaError extends Error {
126
138
  }
127
139
 
128
140
  /**
129
- * Error preventing join because of a webinar registration error
141
+ * Error preventing join because of a webinar have some error
130
142
  */
131
- export class MeetingInfoV2WebinarRegistrationError extends Error {
143
+ export class MeetingInfoV2JoinWebinarError extends Error {
132
144
  meetingInfo: any;
133
145
  sdkMessage: any;
134
146
  wbxAppApiCode: any;
@@ -142,7 +154,7 @@ export class MeetingInfoV2WebinarRegistrationError extends Error {
142
154
  */
143
155
  constructor(wbxAppApiErrorCode?: number, meetingInfo?: object, message?: string) {
144
156
  super(`${message}, code=${wbxAppApiErrorCode}`);
145
- this.name = 'MeetingInfoV2WebinarRegistrationError';
157
+ this.name = 'MeetingInfoV2JoinWebinarError';
146
158
  this.sdkMessage = message;
147
159
  this.stack = new Error().stack;
148
160
  this.wbxAppApiCode = wbxAppApiErrorCode;
@@ -204,21 +216,21 @@ export default class MeetingInfoV2 {
204
216
  };
205
217
 
206
218
  /**
207
- * Raises a handleWebinarRegistrationError for webinar registration error codes
219
+ * Raises a handleJoinWebinarError for join webinar error codes
208
220
  * @param {any} err the error from the request
209
221
  * @returns {void}
210
222
  */
211
- handleWebinarRegistrationError = (err) => {
223
+ handleJoinWebinarError = (err) => {
212
224
  if (!err.body) {
213
225
  return;
214
226
  }
215
227
 
216
- if (WEBINAR_REGISTRATION_ERROR_CODES.includes(err.body?.code)) {
217
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.WEBINAR_REGISTRATION_ERROR, {
228
+ if (JOIN_WEBINAR_ERROR_CODES.includes(err.body?.code)) {
229
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_WEBINAR_ERROR, {
218
230
  code: err.body?.code,
219
231
  });
220
232
 
221
- throw new MeetingInfoV2WebinarRegistrationError(
233
+ throw new MeetingInfoV2JoinWebinarError(
222
234
  err.body?.code,
223
235
  err.body?.data?.meetingInfo,
224
236
  err.body?.message
@@ -286,7 +298,7 @@ export default class MeetingInfoV2 {
286
298
  })
287
299
  .catch((err) => {
288
300
  this.handlePolicyError(err);
289
- this.handleWebinarRegistrationError(err);
301
+ this.handleJoinWebinarError(err);
290
302
 
291
303
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADHOC_MEETING_FAILURE, {
292
304
  reason: err.message,
@@ -441,7 +453,7 @@ export default class MeetingInfoV2 {
441
453
 
442
454
  if (err?.statusCode === 403) {
443
455
  this.handlePolicyError(err);
444
- this.handleWebinarRegistrationError(err);
456
+ this.handleJoinWebinarError(err);
445
457
 
446
458
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR, {
447
459
  reason: err.message,
@@ -56,7 +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
+ import JoinWebinarError from '../common/errors/join-webinar-error';
60
60
  import {SpaceIDDeprecatedError} from '../common/errors/webex-errors';
61
61
  import NoMeetingInfoError from '../common/errors/no-meeting-info';
62
62
 
@@ -155,6 +155,9 @@ export type BasicMeetingInformation = {
155
155
  };
156
156
  meetingInfo: any;
157
157
  sessionCorrelationId: string;
158
+ roles: string[];
159
+ getCurUserType: () => string | null;
160
+ callStateForMetrics: CallStateForMetrics;
158
161
  };
159
162
 
160
163
  /**
@@ -1143,6 +1146,9 @@ export default class Meetings extends WebexPlugin {
1143
1146
  sessionId: meeting.locusInfo?.fullState?.sessionId,
1144
1147
  },
1145
1148
  },
1149
+ roles: meeting.roles,
1150
+ callStateForMetrics: meeting.callStateForMetrics,
1151
+ getCurUserType: meeting.getCurUserType,
1146
1152
  });
1147
1153
  this.meetingCollection.delete(meeting.id);
1148
1154
  Trigger.trigger(
@@ -1406,7 +1412,7 @@ export default class Meetings extends WebexPlugin {
1406
1412
  !(err instanceof CaptchaError) &&
1407
1413
  !(err instanceof PasswordError) &&
1408
1414
  !(err instanceof PermissionError) &&
1409
- !(err instanceof WebinarRegistrationError)
1415
+ !(err instanceof JoinWebinarError)
1410
1416
  ) {
1411
1417
  LoggerProxy.logger.info(
1412
1418
  `Meetings:index#createMeeting --> Info Unable to fetch meeting info for ${destination}.`
@@ -46,6 +46,7 @@ const MembersUtil = {
46
46
  {
47
47
  address:
48
48
  options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,
49
+ ...(options.invitee.roles ? {roles: options.invitee.roles} : {}),
49
50
  },
50
51
  ],
51
52
  alertIfActive: options.alertIfActive,
@@ -70,7 +70,9 @@ 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
+ JOIN_WEBINAR_ERROR: 'js_sdk_join_webinar_error',
74
+ GUEST_ENTERED_LOBBY: 'js_sdk_guest_entered_lobby',
75
+ GUEST_EXITED_LOBBY: 'js_sdk_guest_exited_lobby',
74
76
  };
75
77
 
76
78
  export {BEHAVIORAL_METRICS as default};
@@ -19,6 +19,14 @@ export type RemoteVideoResolution =
19
19
  | 'large' // 1080p or less
20
20
  | 'best'; // highest possible resolution
21
21
 
22
+ const MAX_FS_VALUES = {
23
+ '90p': 60,
24
+ '180p': 240,
25
+ '360p': 920,
26
+ '720p': 3600,
27
+ '1080p': 8192,
28
+ };
29
+
22
30
  /**
23
31
  * Converts pane size into h264 maxFs
24
32
  * @param {PaneSize} paneSize
@@ -29,28 +37,28 @@ export function getMaxFs(paneSize: RemoteVideoResolution): number {
29
37
 
30
38
  switch (paneSize) {
31
39
  case 'thumbnail':
32
- maxFs = 60;
40
+ maxFs = MAX_FS_VALUES['90p'];
33
41
  break;
34
42
  case 'very small':
35
- maxFs = 240;
43
+ maxFs = MAX_FS_VALUES['180p'];
36
44
  break;
37
45
  case 'small':
38
- maxFs = 920;
46
+ maxFs = MAX_FS_VALUES['360p'];
39
47
  break;
40
48
  case 'medium':
41
- maxFs = 3600;
49
+ maxFs = MAX_FS_VALUES['720p'];
42
50
  break;
43
51
  case 'large':
44
- maxFs = 8192;
52
+ maxFs = MAX_FS_VALUES['1080p'];
45
53
  break;
46
54
  case 'best':
47
- maxFs = 8192; // for now 'best' is 1080p, so same as 'large'
55
+ maxFs = MAX_FS_VALUES['1080p']; // for now 'best' is 1080p, so same as 'large'
48
56
  break;
49
57
  default:
50
58
  LoggerProxy.logger.warn(
51
59
  `RemoteMedia#getMaxFs --> unsupported paneSize: ${paneSize}, using "medium" instead`
52
60
  );
53
- maxFs = 3600;
61
+ maxFs = MAX_FS_VALUES['720p'];
54
62
  }
55
63
 
56
64
  return maxFs;
@@ -117,16 +125,21 @@ export class RemoteMedia extends EventsScope {
117
125
  return;
118
126
  }
119
127
 
120
- if (height < 135) {
121
- fs = 60;
122
- } else if (height < 270) {
123
- fs = 240;
124
- } else if (height < 540) {
125
- fs = 920;
128
+ // we switch to the next resolution level when the height is 10% more than the current resolution height
129
+ // except for 1080p - we switch to it immediately when the height is more than 720p
130
+ const threshold = 1.1;
131
+ const getThresholdHeight = (h: number) => Math.round(h * threshold);
132
+
133
+ if (height < getThresholdHeight(90)) {
134
+ fs = MAX_FS_VALUES['90p'];
135
+ } else if (height < getThresholdHeight(180)) {
136
+ fs = MAX_FS_VALUES['180p'];
137
+ } else if (height < getThresholdHeight(360)) {
138
+ fs = MAX_FS_VALUES['360p'];
126
139
  } else if (height <= 720) {
127
- fs = 3600;
140
+ fs = MAX_FS_VALUES['720p'];
128
141
  } else {
129
- fs = 8192;
142
+ fs = MAX_FS_VALUES['1080p'];
130
143
  }
131
144
 
132
145
  this.receiveSlot?.setMaxFs(fs);
@@ -357,11 +357,14 @@ export class ClusterReachability extends EventsScope {
357
357
 
358
358
  this.startTimestamp = performance.now();
359
359
 
360
+ // Set up the state change listeners before triggering the ICE gathering
361
+ const gatherIceCandidatePromise = this.gatherIceCandidates();
362
+
360
363
  // not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
361
364
  // we just need to make this call to trigger the ICE gathering process
362
365
  this.pc.setLocalDescription(offer);
363
366
 
364
- await this.gatherIceCandidates();
367
+ await gatherIceCandidatePromise;
365
368
  } catch (error) {
366
369
  LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
367
370
  }
@@ -1,8 +1,11 @@
1
- enum RecordingAction {
1
+ export enum RecordingAction {
2
2
  Start = 'Start',
3
3
  Stop = 'Stop',
4
4
  Pause = 'Pause',
5
5
  Resume = 'Resume',
6
6
  }
7
7
 
8
- export default RecordingAction;
8
+ export enum RecordingType {
9
+ Premise = 'premise',
10
+ Cloud = 'cloud',
11
+ }
@@ -1,9 +1,9 @@
1
1
  import PermissionError from '../common/errors/permission';
2
+ import LoggerProxy from '../common/logs/logger-proxy';
2
3
  import {CONTROLS, HTTP_VERBS, SELF_POLICY} from '../constants';
3
4
  import MeetingRequest from '../meeting/request';
4
- import RecordingAction from './enums';
5
+ import {RecordingAction, RecordingType} from './enums';
5
6
  import Util from './util';
6
- import LoggerProxy from '../common/logs/logger-proxy';
7
7
 
8
8
  /**
9
9
  * @description Recording manages the recording functionality of the meeting object, there should only be one instantation of recording per meeting
@@ -228,11 +228,12 @@ export default class RecordingController {
228
228
 
229
229
  /**
230
230
  * @param {RecordingAction} action
231
+ * @param {RecordingType} recordingType
231
232
  * @private
232
233
  * @memberof RecordingController
233
234
  * @returns {Promise}
234
235
  */
235
- private recordingService(action: RecordingAction): Promise<any> {
236
+ private recordingService(action: RecordingAction, recordingType: RecordingType): Promise<any> {
236
237
  // @ts-ignore
237
238
  return this.request.request({
238
239
  body: {
@@ -242,6 +243,7 @@ export default class RecordingController {
242
243
  recording: {
243
244
  action: action.toLowerCase(),
244
245
  },
246
+ recordingType,
245
247
  },
246
248
  uri: `${this.serviceUrl}/loci/${this.locusId}/recording`,
247
249
  method: HTTP_VERBS.PUT,
@@ -276,14 +278,25 @@ export default class RecordingController {
276
278
  * @returns {Promise}
277
279
  */
278
280
  private recordingFacade(action: RecordingAction): Promise<any> {
281
+ const isPremiseRecordingEnabled = Util.isPremiseRecordingEnabled(
282
+ this.displayHints,
283
+ this.selfUserPolicies
284
+ );
279
285
  LoggerProxy.logger.log(
280
286
  `RecordingController:index#recordingFacade --> recording action [${action}]`
281
287
  );
282
288
 
289
+ let recordingType: RecordingType;
290
+ if (isPremiseRecordingEnabled) {
291
+ recordingType = RecordingType.Premise;
292
+ } else {
293
+ recordingType = RecordingType.Cloud;
294
+ }
295
+
283
296
  // assumes action is proper cased (i.e., Example)
284
297
  if (Util?.[`canUser${action}`](this.displayHints, this.selfUserPolicies)) {
285
298
  if (this.serviceUrl) {
286
- return this.recordingService(action);
299
+ return this.recordingService(action, recordingType);
287
300
  }
288
301
 
289
302
  return this.recordingControls(action);
@@ -1,33 +1,47 @@
1
1
  import {DISPLAY_HINTS, SELF_POLICY} from '../constants';
2
- import RecordingAction from './enums';
2
+ import {RecordingAction} from './enums';
3
3
  import MeetingUtil from '../meeting/util';
4
4
 
5
5
  const canUserStart = (
6
6
  displayHints: Array<string>,
7
7
  userPolicies: Record<SELF_POLICY, boolean>
8
8
  ): boolean =>
9
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) &&
9
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) ||
10
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START)) &&
10
11
  MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
11
12
 
12
13
  const canUserPause = (
13
14
  displayHints: Array<string>,
14
15
  userPolicies: Record<SELF_POLICY, boolean>
15
16
  ): boolean =>
16
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) &&
17
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) ||
18
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE)) &&
17
19
  MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
18
20
 
19
21
  const canUserResume = (
20
22
  displayHints: Array<string>,
21
23
  userPolicies: Record<SELF_POLICY, boolean>
22
24
  ): boolean =>
23
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) &&
25
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) ||
26
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
24
27
  MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
25
28
 
26
29
  const canUserStop = (
27
30
  displayHints: Array<string>,
28
31
  userPolicies: Record<SELF_POLICY, boolean>
29
32
  ): boolean =>
30
- displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) &&
33
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) ||
34
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP)) &&
35
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
36
+
37
+ const isPremiseRecordingEnabled = (
38
+ displayHints: Array<string>,
39
+ userPolicies: Record<SELF_POLICY, boolean>
40
+ ): boolean =>
41
+ (displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START) ||
42
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) ||
43
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) ||
44
+ displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
31
45
  MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
32
46
 
33
47
  const extractLocusId = (url: string) => {
@@ -70,6 +84,7 @@ export default {
70
84
  canUserPause,
71
85
  canUserResume,
72
86
  canUserStop,
87
+ isPremiseRecordingEnabled,
73
88
  deriveRecordingStates,
74
89
  extractLocusId,
75
90
  };