@webex/plugin-meetings 3.8.0-next.3 → 3.8.0-next.30

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 (78) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +1 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +4 -4
  8. package/dist/interpretation/index.js.map +1 -1
  9. package/dist/interpretation/siLanguage.js +1 -1
  10. package/dist/locus-info/controlsUtils.js +1 -1
  11. package/dist/locus-info/controlsUtils.js.map +1 -1
  12. package/dist/meeting/index.js +89 -5
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/meeting/locusMediaRequest.js +21 -5
  15. package/dist/meeting/locusMediaRequest.js.map +1 -1
  16. package/dist/meeting/util.js +4 -1
  17. package/dist/meeting/util.js.map +1 -1
  18. package/dist/meeting-info/meeting-info-v2.js +359 -60
  19. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  20. package/dist/meetings/index.js +60 -1
  21. package/dist/meetings/index.js.map +1 -1
  22. package/dist/member/index.js +10 -0
  23. package/dist/member/index.js.map +1 -1
  24. package/dist/member/util.js +3 -0
  25. package/dist/member/util.js.map +1 -1
  26. package/dist/metrics/constants.js +9 -0
  27. package/dist/metrics/constants.js.map +1 -1
  28. package/dist/reachability/clusterReachability.js +52 -8
  29. package/dist/reachability/clusterReachability.js.map +1 -1
  30. package/dist/reachability/index.js +70 -45
  31. package/dist/reachability/index.js.map +1 -1
  32. package/dist/reachability/reachability.types.js +14 -0
  33. package/dist/reachability/reachability.types.js.map +1 -1
  34. package/dist/reachability/request.js +19 -3
  35. package/dist/reachability/request.js.map +1 -1
  36. package/dist/recording-controller/util.js +5 -5
  37. package/dist/recording-controller/util.js.map +1 -1
  38. package/dist/types/config.d.ts +1 -0
  39. package/dist/types/constants.d.ts +1 -0
  40. package/dist/types/meeting/index.d.ts +30 -0
  41. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  42. package/dist/types/meetings/index.d.ts +29 -0
  43. package/dist/types/member/index.d.ts +1 -0
  44. package/dist/types/metrics/constants.d.ts +9 -0
  45. package/dist/types/reachability/clusterReachability.d.ts +13 -1
  46. package/dist/types/reachability/index.d.ts +2 -1
  47. package/dist/types/reachability/reachability.types.d.ts +5 -0
  48. package/dist/webinar/index.js +1 -1
  49. package/package.json +22 -22
  50. package/src/config.ts +1 -0
  51. package/src/constants.ts +1 -0
  52. package/src/interpretation/index.ts +3 -3
  53. package/src/locus-info/controlsUtils.ts +2 -2
  54. package/src/meeting/index.ts +85 -7
  55. package/src/meeting/locusMediaRequest.ts +27 -4
  56. package/src/meeting/util.ts +2 -1
  57. package/src/meeting-info/meeting-info-v2.ts +247 -6
  58. package/src/meetings/index.ts +72 -1
  59. package/src/member/index.ts +11 -0
  60. package/src/member/util.ts +3 -0
  61. package/src/metrics/constants.ts +9 -0
  62. package/src/reachability/clusterReachability.ts +47 -1
  63. package/src/reachability/index.ts +15 -0
  64. package/src/reachability/reachability.types.ts +6 -0
  65. package/src/reachability/request.ts +7 -0
  66. package/src/recording-controller/util.ts +17 -13
  67. package/test/unit/spec/interpretation/index.ts +39 -1
  68. package/test/unit/spec/locus-info/controlsUtils.js +8 -0
  69. package/test/unit/spec/meeting/index.js +200 -108
  70. package/test/unit/spec/meeting/locusMediaRequest.ts +96 -58
  71. package/test/unit/spec/meeting/utils.js +55 -0
  72. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  73. package/test/unit/spec/meetings/index.js +78 -1
  74. package/test/unit/spec/member/index.js +7 -0
  75. package/test/unit/spec/member/util.js +24 -0
  76. package/test/unit/spec/reachability/clusterReachability.ts +47 -1
  77. package/test/unit/spec/reachability/index.ts +12 -0
  78. package/test/unit/spec/reachability/request.js +47 -2
@@ -206,8 +206,8 @@ ControlsUtils.getControls = (oldControls: any, newControls: any) => {
206
206
  ),
207
207
 
208
208
  hasPracticeSessionEnabledChanged: !isEqual(
209
- previous?.practiceSession?.enabled,
210
- current?.practiceSession?.enabled
209
+ !!previous?.practiceSession?.enabled,
210
+ !!current?.practiceSession?.enabled
211
211
  ),
212
212
 
213
213
  hasStageViewChanged: !isEqual(previous?.videoLayout, current?.videoLayout),
@@ -241,6 +241,8 @@ export type CallStateForMetrics = {
241
241
  sessionCorrelationId?: string;
242
242
  joinTrigger?: string;
243
243
  loginType?: string;
244
+ userNameInput?: string;
245
+ emailInput?: string;
244
246
  };
245
247
 
246
248
  export const MEDIA_UPDATE_TYPE = {
@@ -1627,6 +1629,38 @@ export default class Meeting extends StatelessWebexPlugin {
1627
1629
  this.callStateForMetrics.correlationId = correlationId;
1628
1630
  }
1629
1631
 
1632
+ /**
1633
+ * Getter - Returns callStateForMetrics.userNameInput
1634
+ * @returns {string}
1635
+ */
1636
+ get userNameInput() {
1637
+ return this.callStateForMetrics?.userNameInput;
1638
+ }
1639
+
1640
+ /**
1641
+ * Setter - sets callStateForMetrics.userNameInput
1642
+ * @param {string} userNameInput
1643
+ */
1644
+ set userNameInput(userNameInput: string) {
1645
+ this.callStateForMetrics.userNameInput = userNameInput;
1646
+ }
1647
+
1648
+ /**
1649
+ * Getter - Returns callStateForMetrics.emailInput
1650
+ * @returns {string}
1651
+ */
1652
+ get emailInput() {
1653
+ return this.callStateForMetrics?.emailInput;
1654
+ }
1655
+
1656
+ /**
1657
+ * Setter - sets callStateForMetrics.emailInput
1658
+ * @param {string} emailInput
1659
+ */
1660
+ set emailInput(emailInput: string) {
1661
+ this.callStateForMetrics.emailInput = emailInput;
1662
+ }
1663
+
1630
1664
  /**
1631
1665
  * Getter - Returns callStateForMetrics.sessionCorrelationId
1632
1666
  * @returns {string}
@@ -1652,6 +1686,33 @@ export default class Meeting extends StatelessWebexPlugin {
1652
1686
  return this.#isoLocalClientMeetingJoinTime;
1653
1687
  }
1654
1688
 
1689
+ /**
1690
+ * Setter - sets isoLocalClientMeetingJoinTime
1691
+ * This will be set once on meeting join, and not updated again
1692
+ * this will always produce an ISO string
1693
+ * If the iso string is invalid, it will fallback to the current system time
1694
+ * @param {string | undefined} time
1695
+ */
1696
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1697
+ const fallback = new Date().toISOString();
1698
+ if (!time) {
1699
+ this.#isoLocalClientMeetingJoinTime = fallback;
1700
+ } else {
1701
+ const date = new Date(time);
1702
+
1703
+ // Check if the date is valid
1704
+ if (Number.isNaN(date.getTime())) {
1705
+ LoggerProxy.logger.info(
1706
+ // @ts-ignore
1707
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1708
+ );
1709
+ this.#isoLocalClientMeetingJoinTime = fallback;
1710
+ } else {
1711
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1712
+ }
1713
+ }
1714
+ }
1715
+
1655
1716
  /**
1656
1717
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1657
1718
  * @param {any} info
@@ -5684,8 +5745,6 @@ export default class Meeting extends StatelessWebexPlugin {
5684
5745
  // @ts-ignore
5685
5746
  this.webex.internal.device.meetingStarted();
5686
5747
 
5687
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5688
-
5689
5748
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5690
5749
 
5691
5750
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6146,10 +6205,18 @@ export default class Meeting extends StatelessWebexPlugin {
6146
6205
  },
6147
6206
  options: {meetingId: this.id, rawError: error},
6148
6207
  });
6149
- } else if (
6150
- error instanceof Errors.SdpOfferHandlingError ||
6151
- error instanceof Errors.SdpAnswerHandlingError
6152
- ) {
6208
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6209
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6210
+
6211
+ // @ts-ignore
6212
+ this.webex.internal.newMetrics.submitClientEvent({
6213
+ name: 'client.media-engine.remote-sdp-received',
6214
+ payload: {
6215
+ canProceed: false,
6216
+ },
6217
+ options: {meetingId: this.id, rawError: error},
6218
+ });
6219
+ } else if (error instanceof Errors.SdpAnswerHandlingError) {
6153
6220
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6154
6221
 
6155
6222
  // @ts-ignore
@@ -6160,6 +6227,13 @@ export default class Meeting extends StatelessWebexPlugin {
6160
6227
  },
6161
6228
  options: {meetingId: this.id, rawError: error},
6162
6229
  });
6230
+
6231
+ if (this.deferSDPAnswer) {
6232
+ clearTimeout(this.sdpResponseTimer);
6233
+ this.sdpResponseTimer = undefined;
6234
+
6235
+ this.deferSDPAnswer.reject();
6236
+ }
6163
6237
  } else if (error instanceof Errors.SdpError) {
6164
6238
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6165
6239
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -8681,6 +8755,9 @@ export default class Meeting extends StatelessWebexPlugin {
8681
8755
  LoggerProxy.logger.log(
8682
8756
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8683
8757
  );
8758
+
8759
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8760
+
8684
8761
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8685
8762
  correlationId: this.correlationId,
8686
8763
  muted,
@@ -8689,8 +8766,9 @@ export default class Meeting extends StatelessWebexPlugin {
8689
8766
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8690
8767
  // all we're doing here is adding metrics.
8691
8768
  // eslint-disable-next-line dot-notation
8692
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8769
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8693
8770
  isMultistream: this.isMultistream,
8771
+ frameRate: shareVideoStreamSettings?.frameRate,
8694
8772
  });
8695
8773
  };
8696
8774
 
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable valid-jsdoc */
2
2
  import {defer} from 'lodash';
3
- import {Defer} from '@webex/common';
3
+ import {Defer, transferEvents} from '@webex/common';
4
+ import {EventEmitter} from 'events';
4
5
  import {WebexPlugin} from '@webex/webex-core';
5
6
  import {MEDIA, HTTP_VERBS, ROAP} from '../constants';
6
7
  import LoggerProxy from '../common/logs/logger-proxy';
@@ -250,12 +251,19 @@ export class LocusMediaRequest extends WebexPlugin {
250
251
  this.confluenceState = 'creation in progress';
251
252
  }
252
253
 
253
- // @ts-ignore
254
- return this.request({
254
+ const upload = new EventEmitter();
255
+ const download = new EventEmitter();
256
+
257
+ const options = {
255
258
  method: HTTP_VERBS.PUT,
256
259
  uri,
257
260
  body,
258
- })
261
+ upload,
262
+ download,
263
+ };
264
+
265
+ // @ts-ignore
266
+ const promise = this.request(options)
259
267
  .then((result) => {
260
268
  if (isRequestAffectingConfluenceState(request)) {
261
269
  this.confluenceState = 'created';
@@ -294,6 +302,21 @@ export class LocusMediaRequest extends WebexPlugin {
294
302
 
295
303
  throw e;
296
304
  });
305
+
306
+ if (request.type === 'RoapMessage') {
307
+ const setupProgressListener = (direction: string, eventEmitter: EventEmitter) => {
308
+ eventEmitter.on('progress', (progressEvent: ProgressEvent) => {
309
+ LoggerProxy.logger.info(
310
+ `${request.type}: ${direction} Progress, Timestamp: ${progressEvent.timeStamp}, Progress: ${progressEvent.loaded}/${progressEvent.total}`
311
+ );
312
+ });
313
+ };
314
+
315
+ setupProgressListener('Upload', options.upload);
316
+ setupProgressListener('Download', options.download);
317
+ }
318
+
319
+ return promise;
297
320
  }
298
321
 
299
322
  /**
@@ -176,11 +176,12 @@ const MeetingUtil = {
176
176
  deviceCapabilities: options.deviceCapabilities,
177
177
  liveAnnotationSupported: options.liveAnnotationSupported,
178
178
  clientMediaPreferences,
179
+ alias: options.alias,
179
180
  })
180
181
  .then((res) => {
181
182
  const parsed = MeetingUtil.parseLocusJoin(res);
182
183
  meeting.setLocus(parsed);
183
-
184
+ meeting.isoLocalClientMeetingJoinTime = res?.headers?.date; // read from header if exist, else fall back to system clock : https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-555657
184
185
  webex.internal.newMetrics.submitClientEvent({
185
186
  name: 'client.locus.join.response',
186
187
  payload: {
@@ -16,6 +16,9 @@ const CAPTCHA_ERROR_DEFAULT_MESSAGE =
16
16
  'Captcha required. Call fetchMeetingInfo() with captchaInfo argument';
17
17
  const ADHOC_MEETING_DEFAULT_ERROR =
18
18
  'Failed starting the adhoc meeting, Please contact support team ';
19
+ const MEETING_IS_IN_PROGRESS_MESSAGE = 'Meeting is in progress';
20
+ const STATIC_MEETING_LINK_ALREADY_EXISTS_MESSAGE = 'Static meeting link already exists';
21
+ const FETCH_STATIC_MEETING_LINK = 'Meeting link does not exists for conversation';
19
22
  const CAPTCHA_ERROR_REQUIRES_PASSWORD_CODES = [423005, 423006];
20
23
  const CAPTCHA_ERROR_REQUIRES_REGISTRATION_ID_CODES = [423007];
21
24
 
@@ -193,6 +196,77 @@ export class MeetingInfoV2JoinForbiddenError extends Error {
193
196
  }
194
197
  }
195
198
 
199
+ /**
200
+ * Error fetching static link for a conversation when it does not exist
201
+ */
202
+ export class MeetingInfoV2StaticLinkDoesNotExistError extends Error {
203
+ sdkMessage: any;
204
+ wbxAppApiCode: any;
205
+ body: any;
206
+ /**
207
+ *
208
+ * @constructor
209
+ * @param {Number} [wbxAppApiErrorCode]
210
+ * @param {String} [message]
211
+ */
212
+ constructor(wbxAppApiErrorCode?: number, message: string = FETCH_STATIC_MEETING_LINK) {
213
+ super(`${message}, code=${wbxAppApiErrorCode}`);
214
+ this.name = 'MeetingInfoV2StaticLinkDoesNotExistError';
215
+ this.sdkMessage = message;
216
+ this.stack = new Error().stack;
217
+ this.wbxAppApiCode = wbxAppApiErrorCode;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Error enabling/disabling static meeting link
223
+ */
224
+ export class MeetingInfoV2MeetingIsInProgressError extends Error {
225
+ sdkMessage: any;
226
+ wbxAppApiCode: any;
227
+ body: any;
228
+ /**
229
+ *
230
+ * @constructor
231
+ * @param {Number} [wbxAppApiErrorCode]
232
+ * @param {String} [message]
233
+ * @param {Boolean} [enable]
234
+ */
235
+ constructor(
236
+ wbxAppApiErrorCode?: number,
237
+ message = MEETING_IS_IN_PROGRESS_MESSAGE,
238
+ enable = false
239
+ ) {
240
+ super(`${message}, code=${wbxAppApiErrorCode}, enable=${enable}`);
241
+ this.name = 'MeetingInfoV2MeetingIsInProgressError';
242
+ this.sdkMessage = message;
243
+ this.stack = new Error().stack;
244
+ this.wbxAppApiCode = wbxAppApiErrorCode;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Error enabling/disabling static meeting link
250
+ */
251
+ export class MeetingInfoV2StaticMeetingLinkAlreadyExists extends Error {
252
+ sdkMessage: any;
253
+ wbxAppApiCode: any;
254
+ body: any;
255
+ /**
256
+ *
257
+ * @constructor
258
+ * @param {Number} [wbxAppApiErrorCode]
259
+ * @param {String} [message]
260
+ */
261
+ constructor(wbxAppApiErrorCode?: number, message = STATIC_MEETING_LINK_ALREADY_EXISTS_MESSAGE) {
262
+ super(`${message}, code=${wbxAppApiErrorCode}`);
263
+ this.name = 'MeetingInfoV2StaticMeetingLinkAlreadyExists';
264
+ this.sdkMessage = message;
265
+ this.stack = new Error().stack;
266
+ this.wbxAppApiCode = wbxAppApiErrorCode;
267
+ }
268
+ }
269
+
196
270
  /**
197
271
  * @class MeetingInfo
198
272
  */
@@ -293,17 +367,20 @@ export default class MeetingInfoV2 {
293
367
  };
294
368
 
295
369
  /**
296
- * Creates adhoc space meetings for a space by fetching the conversation infomation
370
+ * helper function to either create an adhoc space meeting or enable static meeting link
297
371
  * @param {String} conversationUrl conversationUrl to start adhoc meeting on
298
372
  * @param {String} installedOrgID org ID of user's machine
373
+ * @param {Boolean} enableStaticMeetingLink whether or not to enable static meeting link
299
374
  * @returns {Promise} returns a meeting info object
300
375
  * @public
301
376
  * @memberof MeetingInfo
302
377
  */
303
- async createAdhocSpaceMeeting(conversationUrl: string, installedOrgID?: string) {
304
- if (!this.webex.meetings.preferredWebexSite) {
305
- throw Error('No preferred webex site found');
306
- }
378
+ async createAdhocSpaceMeetingOrEnableStaticMeetingLink(
379
+ conversationUrl: string,
380
+ installedOrgID?: string,
381
+ // setting this to true enables static meeting link
382
+ enableStaticMeetingLink = false
383
+ ) {
307
384
  const getInvitees = (particpants = []) => {
308
385
  const invitees = [];
309
386
 
@@ -329,6 +406,7 @@ export default class MeetingInfoV2 {
329
406
  kroUrl: conversation.kmsResourceObjectUrl,
330
407
  invitees: getInvitees(conversation.participants?.items),
331
408
  installedOrgID,
409
+ schedule: enableStaticMeetingLink,
332
410
  };
333
411
 
334
412
  if (installedOrgID) {
@@ -344,7 +422,23 @@ export default class MeetingInfoV2 {
344
422
  uri,
345
423
  body,
346
424
  });
347
- })
425
+ });
426
+ }
427
+
428
+ /**
429
+ * Creates adhoc space meetings for a space by fetching the conversation infomation
430
+ * @param {String} conversationUrl conversationUrl to start adhoc meeting on
431
+ * @param {String} installedOrgID org ID of user's machine
432
+ * @returns {Promise} returns a meeting info object
433
+ * @public
434
+ * @memberof MeetingInfo
435
+ */
436
+ async createAdhocSpaceMeeting(conversationUrl: string, installedOrgID?: string) {
437
+ if (!this.webex.meetings.preferredWebexSite) {
438
+ throw Error('No preferred webex site found');
439
+ }
440
+
441
+ return this.createAdhocSpaceMeetingOrEnableStaticMeetingLink(conversationUrl, installedOrgID)
348
442
  .then((requestResult) => {
349
443
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADHOC_MEETING_SUCCESS);
350
444
 
@@ -363,6 +457,153 @@ export default class MeetingInfoV2 {
363
457
  });
364
458
  }
365
459
 
460
+ /**
461
+ * Fetches details for static meeting link
462
+ * @param {String} conversationUrl conversationUrl that's required to find static meeting link if it exists
463
+ * @returns {Promise} returns a Promise
464
+ * @public
465
+ * @memberof MeetingInfo
466
+ */
467
+ async fetchStaticMeetingLink(conversationUrl: string) {
468
+ if (!this.webex.meetings.preferredWebexSite) {
469
+ throw Error('No preferred webex site found');
470
+ }
471
+
472
+ const body = {
473
+ spaceUrl: conversationUrl,
474
+ };
475
+
476
+ const uri = this.webex.meetings.preferredWebexSite
477
+ ? `https://${this.webex.meetings.preferredWebexSite}/wbxappapi/v2/meetings/spaceInstant/query`
478
+ : '';
479
+
480
+ return this.webex
481
+ .request({
482
+ method: HTTP_VERBS.POST,
483
+ uri,
484
+ body,
485
+ })
486
+ .then((requestResult) => {
487
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.FETCH_STATIC_MEETING_LINK_SUCCESS);
488
+
489
+ return requestResult;
490
+ })
491
+ .catch((err) => {
492
+ if (err?.statusCode === 403) {
493
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_LINK_DOES_NOT_EXIST_ERROR, {
494
+ reason: err.message,
495
+ stack: err.stack,
496
+ });
497
+
498
+ throw new MeetingInfoV2StaticLinkDoesNotExistError(err.body?.code, err.body?.message);
499
+ }
500
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.FETCH_STATIC_MEETING_LINK_FAILURE, {
501
+ reason: err.message,
502
+ stack: err.stack,
503
+ });
504
+
505
+ throw err;
506
+ });
507
+ }
508
+
509
+ /**
510
+ * Enables static meeting link
511
+ * @param {String} conversationUrl conversationUrl that's required to enable static meeting link
512
+ * @returns {Promise} returns a Promise
513
+ * @public
514
+ * @memberof MeetingInfo
515
+ */
516
+ async enableStaticMeetingLink(conversationUrl: string) {
517
+ if (!this.webex.meetings.preferredWebexSite) {
518
+ throw Error('No preferred webex site found');
519
+ }
520
+
521
+ return this.createAdhocSpaceMeetingOrEnableStaticMeetingLink(conversationUrl, undefined, true)
522
+ .then((requestResult) => {
523
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ENABLE_STATIC_METTING_LINK_SUCCESS);
524
+
525
+ return requestResult;
526
+ })
527
+ .catch((err) => {
528
+ if (err?.statusCode === 403) {
529
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_IS_IN_PROGRESS_ERROR, {
530
+ reason: err.message,
531
+ stack: err.stack,
532
+ });
533
+
534
+ throw new MeetingInfoV2MeetingIsInProgressError(err.body?.code, err.body?.message, true);
535
+ }
536
+
537
+ if (err?.statusCode === 409) {
538
+ Metrics.sendBehavioralMetric(
539
+ BEHAVIORAL_METRICS.STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR,
540
+ {
541
+ reason: err.message,
542
+ stack: err.stack,
543
+ }
544
+ );
545
+
546
+ throw new MeetingInfoV2StaticMeetingLinkAlreadyExists(err.body?.code, err.body?.message);
547
+ }
548
+
549
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ENABLE_STATIC_METTING_LINK_FAILURE, {
550
+ reason: err.message,
551
+ stack: err.stack,
552
+ });
553
+
554
+ throw err;
555
+ });
556
+ }
557
+
558
+ /**
559
+ * Disables static meeting link for given conversation url
560
+ * @param {String} conversationUrl conversationUrl that's required to disable static meeting link if it exists
561
+ * @returns {Promise} returns a Promise
562
+ * @public
563
+ * @memberof MeetingInfo
564
+ */
565
+ async disableStaticMeetingLink(conversationUrl: string) {
566
+ if (!this.webex.meetings.preferredWebexSite) {
567
+ throw Error('No preferred webex site found');
568
+ }
569
+
570
+ const body = {
571
+ spaceUrl: conversationUrl,
572
+ };
573
+
574
+ const uri = this.webex.meetings.preferredWebexSite
575
+ ? `https://${this.webex.meetings.preferredWebexSite}/wbxappapi/v2/meetings/spaceInstant/deletePersistentMeeting`
576
+ : '';
577
+
578
+ return this.webex
579
+ .request({
580
+ method: HTTP_VERBS.POST,
581
+ uri,
582
+ body,
583
+ })
584
+ .then((requestResult) => {
585
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.DISABLE_STATIC_MEETING_LINK_SUCCESS);
586
+
587
+ return requestResult;
588
+ })
589
+ .catch((err) => {
590
+ if (err?.statusCode === 403) {
591
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_IS_IN_PROGRESS_ERROR, {
592
+ reason: err.message,
593
+ stack: err.stack,
594
+ });
595
+
596
+ throw new MeetingInfoV2MeetingIsInProgressError(err.body?.code, err.body?.message);
597
+ }
598
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.DISABLE_STATIC_MEETING_LINK_FAILURE, {
599
+ reason: err.message,
600
+ stack: err.stack,
601
+ });
602
+
603
+ throw err;
604
+ });
605
+ }
606
+
366
607
  /**
367
608
  * Fetches meeting info from the server
368
609
  * @param {String} destination one of many different types of destinations to look up info for
@@ -854,7 +854,7 @@ export default class Meetings extends WebexPlugin {
854
854
  this.executeRegistrationStep(
855
855
  () =>
856
856
  this.startReachability('registration').catch((error) => {
857
- LoggerProxy.logger.error(`Meetings:index#register --> GDM error, ${error.message}`);
857
+ LoggerProxy.logger.warn(`Meetings:index#register --> startReachability failed:`, error);
858
858
  }),
859
859
  'startReachability'
860
860
  ),
@@ -1217,6 +1217,29 @@ export default class Meetings extends WebexPlugin {
1217
1217
  );
1218
1218
  }
1219
1219
 
1220
+ /**
1221
+ * Fetch static meeting link for given conversation url.
1222
+ *
1223
+ * @param {string} conversationUrl - url for conversation
1224
+ * @returns {Promise}
1225
+ * @public
1226
+ * @memberof Meetings
1227
+ */
1228
+ public fetchStaticMeetingLink(conversationUrl: string): Promise<any> {
1229
+ return (
1230
+ this.meetingInfo
1231
+ .fetchStaticMeetingLink(conversationUrl)
1232
+ // Catch a failure to fetch static meeting link.
1233
+ .catch((error) => {
1234
+ LoggerProxy.logger.error(
1235
+ `Meetings:index#fetchStaticMeetingLink --> ERROR, unable to fetch persistent meeting link: ${error.message}`
1236
+ );
1237
+
1238
+ return Promise.reject(error);
1239
+ })
1240
+ );
1241
+ }
1242
+
1220
1243
  /**
1221
1244
  * Create a meeting or return an existing meeting.
1222
1245
  *
@@ -1369,6 +1392,54 @@ export default class Meetings extends WebexPlugin {
1369
1392
  );
1370
1393
  }
1371
1394
 
1395
+ /**
1396
+ * Enable static meeting links for given conversation url.
1397
+ *
1398
+ *
1399
+ * @param {string} conversationUrl - url for conversation
1400
+ * @returns {Promise}
1401
+ * @public
1402
+ * @memberof Meetings
1403
+ */
1404
+ public enableStaticMeetingLink(conversationUrl: string): Promise<any> {
1405
+ return (
1406
+ this.meetingInfo
1407
+ .enableStaticMeetingLink(conversationUrl)
1408
+ // Catch a failure to enable static meeting link.
1409
+ .catch((error) => {
1410
+ LoggerProxy.logger.error(
1411
+ `Meetings:index#enableStaticMeetingLink --> ERROR, unable to enable static meeting link: ${error.message}`
1412
+ );
1413
+
1414
+ return Promise.reject(error);
1415
+ })
1416
+ );
1417
+ }
1418
+
1419
+ /**
1420
+ * Disable static meeting links for given conversation url.
1421
+ *
1422
+ *
1423
+ * @param {string} conversationUrl - url for conversation
1424
+ * @returns {Promise}
1425
+ * @public
1426
+ * @memberof Meetings
1427
+ */
1428
+ public disableStaticMeetingLink(conversationUrl: string): Promise<any> {
1429
+ return (
1430
+ this.meetingInfo
1431
+ .disableStaticMeetingLink(conversationUrl)
1432
+ // Catch a failure to disable static meeting link.
1433
+ .catch((error) => {
1434
+ LoggerProxy.logger.error(
1435
+ `Meetings:index#disableStaticMeetingLink --> ERROR, unable to disable static meeting link: ${error.message}`
1436
+ );
1437
+
1438
+ return Promise.reject(error);
1439
+ })
1440
+ );
1441
+ }
1442
+
1372
1443
  /**
1373
1444
  * Create meeting
1374
1445
  *
@@ -23,6 +23,7 @@ export default class Member {
23
23
  isInMeeting: any;
24
24
  isModerator: any;
25
25
  isModeratorAssignmentProhibited: any;
26
+ isPresenterAssignmentProhibited: any;
26
27
  isMutable: any;
27
28
  isNotAdmitted: any;
28
29
  isRecording: any;
@@ -257,6 +258,14 @@ export default class Member {
257
258
  */
258
259
  this.isModeratorAssignmentProhibited = null;
259
260
 
261
+ /**
262
+ * @instance
263
+ * @type {Boolean}
264
+ * @public
265
+ * @memberof Member
266
+ */
267
+ this.isPresenterAssignmentProhibited = null;
268
+
260
269
  /**
261
270
  * @instance
262
271
  * @type {IExternalRoles}
@@ -309,6 +318,8 @@ export default class Member {
309
318
  this.isModerator = MemberUtil.isModerator(participant);
310
319
  this.isModeratorAssignmentProhibited =
311
320
  MemberUtil.isModeratorAssignmentProhibited(participant);
321
+ this.isPresenterAssignmentProhibited =
322
+ MemberUtil.isPresenterAssignmentProhibited(participant);
312
323
  this.processStatus(participant);
313
324
  this.processRoles(participant as ParticipantWithRoles);
314
325
  // must be done last
@@ -127,6 +127,9 @@ MemberUtil.isDevice = (participant: any) => participant && participant.type ===
127
127
  MemberUtil.isModeratorAssignmentProhibited = (participant) =>
128
128
  participant && participant.moderatorAssignmentNotAllowed;
129
129
 
130
+ MemberUtil.isPresenterAssignmentProhibited = (participant) =>
131
+ participant && participant.presenterAssignmentNotAllowed;
132
+
130
133
  /**
131
134
  * checks to see if the participant id is the same as the passed id
132
135
  * there are multiple ids that can be used
@@ -48,10 +48,19 @@ const BEHAVIORAL_METRICS = {
48
48
  UPLOAD_LOGS_FAILURE: 'js_sdk_upload_logs_failure',
49
49
  UPLOAD_LOGS_SUCCESS: 'js_sdk_upload_logs_success',
50
50
  RECEIVE_TRANSCRIPTION_FAILURE: 'js_sdk_receive_transcription_failure',
51
+ MEETING_IS_IN_PROGRESS_ERROR: 'js_sdk_meeting_is_in_progress_error',
52
+ STATIC_MEETING_LINK_ALREADY_EXISTS_ERROR: 'js_sdk_static_meeting_link_already_exists_error',
51
53
  FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',
52
54
  FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',
55
+ ENABLE_STATIC_METTING_LINK_SUCCESS: 'js_sdk_enable_static_meeting_link_success',
56
+ ENABLE_STATIC_METTING_LINK_FAILURE: 'js_sdk_enable_static_meeting_link_failure',
57
+ DISABLE_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_disable_static_meeting_link_success',
58
+ DISABLE_STATIC_MEETING_LINK_FAILURE: 'js_sdk_disable_static_meeting_link_failure',
53
59
  ADHOC_MEETING_SUCCESS: 'js_sdk_adhoc_meeting_success',
54
60
  ADHOC_MEETING_FAILURE: 'js_sdk_adhoc_meeting_failure',
61
+ FETCH_STATIC_MEETING_LINK_SUCCESS: 'js_sdk_fetch_static_meeting_link_success',
62
+ FETCH_STATIC_MEETING_LINK_FAILURE: 'js_sdk_fetch_static_meeting_link_failure',
63
+ MEETING_LINK_DOES_NOT_EXIST_ERROR: 'js_sdk_meeting_link_does_not_exist_error',
55
64
  VERIFY_PASSWORD_SUCCESS: 'js_sdk_verify_password_success',
56
65
  VERIFY_PASSWORD_ERROR: 'js_sdk_verify_password_error',
57
66
  VERIFY_CAPTCHA_ERROR: 'js_sdk_verify_captcha_error',