@webex/plugin-meetings 2.60.1-next.7 → 2.60.1-next.8

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 (54) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.d.ts +12 -2
  5. package/dist/constants.js +15 -5
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/locus-info/index.d.ts +1 -1
  10. package/dist/locus-info/index.js +8 -8
  11. package/dist/locus-info/index.js.map +1 -1
  12. package/dist/meeting/index.d.ts +62 -18
  13. package/dist/meeting/index.js +679 -568
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +25 -18
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.d.ts +16 -0
  18. package/dist/meeting/util.js +71 -0
  19. package/dist/meeting/util.js.map +1 -1
  20. package/dist/meetings/index.d.ts +25 -3
  21. package/dist/meetings/index.js +83 -32
  22. package/dist/meetings/index.js.map +1 -1
  23. package/dist/reachability/index.js +11 -6
  24. package/dist/reachability/index.js.map +1 -1
  25. package/dist/reconnection-manager/index.js +3 -1
  26. package/dist/reconnection-manager/index.js.map +1 -1
  27. package/dist/roap/index.js +50 -54
  28. package/dist/roap/index.js.map +1 -1
  29. package/dist/statsAnalyzer/index.js +1 -1
  30. package/dist/statsAnalyzer/index.js.map +1 -1
  31. package/dist/statsAnalyzer/mqaUtil.js +13 -10
  32. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  33. package/dist/webinar/index.js +1 -1
  34. package/package.json +22 -22
  35. package/src/constants.ts +13 -2
  36. package/src/locus-info/index.ts +13 -12
  37. package/src/meeting/index.ts +215 -116
  38. package/src/meeting/request.ts +7 -0
  39. package/src/meeting/util.ts +97 -0
  40. package/src/meetings/index.ts +59 -18
  41. package/src/reachability/index.ts +7 -4
  42. package/src/reconnection-manager/index.ts +1 -1
  43. package/src/roap/index.ts +49 -51
  44. package/src/statsAnalyzer/index.ts +2 -2
  45. package/src/statsAnalyzer/mqaUtil.ts +15 -14
  46. package/test/unit/spec/locus-info/index.js +53 -5
  47. package/test/unit/spec/meeting/index.js +1792 -1139
  48. package/test/unit/spec/meeting/request.js +22 -12
  49. package/test/unit/spec/meeting/utils.js +93 -0
  50. package/test/unit/spec/meetings/index.js +180 -21
  51. package/test/unit/spec/reachability/index.ts +2 -1
  52. package/test/unit/spec/reconnection-manager/index.js +1 -0
  53. package/test/unit/spec/roap/index.ts +28 -42
  54. package/test/unit/spec/stats-analyzer/index.js +415 -30
@@ -233,6 +233,13 @@ export enum ScreenShareFloorStatus {
233
233
  RELEASED = 'floor_released',
234
234
  }
235
235
 
236
+ type FetchMeetingInfoParams = {
237
+ password?: string;
238
+ captchaCode?: string;
239
+ extraParams?: Record<string, any>;
240
+ sendCAevents?: boolean;
241
+ };
242
+
236
243
  /**
237
244
  * MediaDirection
238
245
  * @typedef {Object} MediaDirection
@@ -615,6 +622,8 @@ export default class Meeting extends StatelessWebexPlugin {
615
622
  environment: string;
616
623
  namespace = MEETINGS;
617
624
  allowMediaInLobby: boolean;
625
+ localShareInstanceId: string;
626
+ remoteShareInstanceId: string;
618
627
  turnDiscoverySkippedReason: string;
619
628
  turnServerUsed: boolean;
620
629
  areVoiceaEventsSetup = false;
@@ -1323,6 +1332,24 @@ export default class Meeting extends StatelessWebexPlugin {
1323
1332
  */
1324
1333
  this.keepAliveTimerId = null;
1325
1334
 
1335
+ /**
1336
+ * id for tracking Local Share instances in Call Analyzer
1337
+ * @instance
1338
+ * @type {String}
1339
+ * @private
1340
+ * @memberof Meeting
1341
+ */
1342
+ this.localShareInstanceId = null;
1343
+
1344
+ /**
1345
+ * id for tracking Remote Share instances in Call Analyzer
1346
+ * @instance
1347
+ * @type {String}
1348
+ * @private
1349
+ * @memberof Meeting
1350
+ */
1351
+ this.remoteShareInstanceId = null;
1352
+
1326
1353
  /**
1327
1354
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1328
1355
  * @instance
@@ -1477,6 +1504,97 @@ export default class Meeting extends StatelessWebexPlugin {
1477
1504
  this.callStateForMetrics.correlationId = correlationId;
1478
1505
  }
1479
1506
 
1507
+ /**
1508
+ * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1509
+ * @param {any} info
1510
+ * @param {string} [meetingLookupUrl] Lookup url, defined when the meeting info fetched
1511
+ * @returns {void}
1512
+ */
1513
+ private setMeetingInfo(info, meetingLookupUrl) {
1514
+ this.meetingInfo = info ? {...info, meetingLookupUrl} : null;
1515
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
1516
+
1517
+ this.requiredCaptcha = null;
1518
+ if (
1519
+ this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
1520
+ this.passwordStatus === PASSWORD_STATUS.VERIFIED
1521
+ ) {
1522
+ this.passwordStatus = PASSWORD_STATUS.VERIFIED;
1523
+ } else {
1524
+ this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
1525
+ }
1526
+
1527
+ Trigger.trigger(
1528
+ this,
1529
+ {
1530
+ file: 'meetings',
1531
+ function: 'fetchMeetingInfo',
1532
+ },
1533
+ EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
1534
+ );
1535
+
1536
+ this.updateMeetingActions();
1537
+ }
1538
+
1539
+ /**
1540
+ * Add pre-fetched meeting info
1541
+ *
1542
+ * The passed meeting info should be be complete, e.g.: fetched after password or captcha provided
1543
+ *
1544
+ * @param {Object} meetingInfo - Complete meeting info
1545
+ * @param {FetchMeetingInfoParams} fetchParams - Fetch parameters for validation
1546
+ * @param {String|undefined} meetingLookupUrl - Lookup url, defined when the meeting info fetched
1547
+ * @returns {Promise<void>}
1548
+ */
1549
+ public async injectMeetingInfo(
1550
+ meetingInfo: any,
1551
+ fetchParams: FetchMeetingInfoParams,
1552
+ meetingLookupUrl: string | undefined
1553
+ ): Promise<void> {
1554
+ await this.prepForFetchMeetingInfo(fetchParams, 'injectMeetingInfo');
1555
+
1556
+ this.parseMeetingInfo(meetingInfo, this.destination);
1557
+ this.setMeetingInfo(meetingInfo, meetingLookupUrl);
1558
+ }
1559
+
1560
+ /**
1561
+ * Validate fetch parameters and clear the fetchMeetingInfoTimeout timeout
1562
+ *
1563
+ * @param {FetchMeetingInfoParams} fetchParams - fetch parameters for validation
1564
+ * @param {String} caller - Name of the caller for logging
1565
+ *
1566
+ * @returns {Promise<void>}
1567
+ * @private
1568
+ */
1569
+ private prepForFetchMeetingInfo(
1570
+ {password = null, captchaCode = null, extraParams = {}}: FetchMeetingInfoParams,
1571
+ caller: string
1572
+ ): Promise<void> {
1573
+ // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
1574
+ if (this.fetchMeetingInfoTimeoutId) {
1575
+ clearTimeout(this.fetchMeetingInfoTimeoutId);
1576
+ this.fetchMeetingInfoTimeoutId = undefined;
1577
+ }
1578
+ if (captchaCode && !this.requiredCaptcha) {
1579
+ return Promise.reject(
1580
+ new Error(`${caller}() called with captchaCode when captcha was not required`)
1581
+ );
1582
+ }
1583
+ if (
1584
+ password &&
1585
+ this.passwordStatus !== PASSWORD_STATUS.REQUIRED &&
1586
+ this.passwordStatus !== PASSWORD_STATUS.UNKNOWN
1587
+ ) {
1588
+ return Promise.reject(
1589
+ new Error(`${caller}() called with password when password was not required`)
1590
+ );
1591
+ }
1592
+
1593
+ this.meetingInfoExtraParams = cloneDeep(extraParams);
1594
+
1595
+ return Promise.resolve();
1596
+ }
1597
+
1480
1598
  /**
1481
1599
  * Internal method for fetching meeting info
1482
1600
  *
@@ -1507,29 +1625,8 @@ export default class Meeting extends StatelessWebexPlugin {
1507
1625
  {meetingId: this.id, sendCAevents}
1508
1626
  );
1509
1627
 
1510
- this.parseMeetingInfo(info, this.destination);
1511
- this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
1512
- this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
1513
- this.requiredCaptcha = null;
1514
- if (
1515
- this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
1516
- this.passwordStatus === PASSWORD_STATUS.VERIFIED
1517
- ) {
1518
- this.passwordStatus = PASSWORD_STATUS.VERIFIED;
1519
- } else {
1520
- this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
1521
- }
1522
-
1523
- Trigger.trigger(
1524
- this,
1525
- {
1526
- file: 'meetings',
1527
- function: 'fetchMeetingInfo',
1528
- },
1529
- EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
1530
- );
1531
-
1532
- this.updateMeetingActions();
1628
+ this.parseMeetingInfo(info?.body, this.destination, info?.errors);
1629
+ this.setMeetingInfo(info?.body, info?.url);
1533
1630
 
1534
1631
  return Promise.resolve();
1535
1632
  } catch (err) {
@@ -1673,46 +1770,13 @@ export default class Meeting extends StatelessWebexPlugin {
1673
1770
  * @memberof Meeting
1674
1771
  * @returns {Promise}
1675
1772
  */
1676
- public async fetchMeetingInfo({
1677
- password = null,
1678
- captchaCode = null,
1679
- extraParams = {},
1680
- sendCAevents = false,
1681
- }: {
1682
- password?: string;
1683
- captchaCode?: string;
1684
- extraParams?: Record<string, any>;
1685
- sendCAevents?: boolean;
1686
- }) {
1687
- // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
1688
- if (this.fetchMeetingInfoTimeoutId) {
1689
- clearTimeout(this.fetchMeetingInfoTimeoutId);
1690
- this.fetchMeetingInfoTimeoutId = undefined;
1691
- }
1692
- if (captchaCode && !this.requiredCaptcha) {
1693
- return Promise.reject(
1694
- new Error('fetchMeetingInfo() called with captchaCode when captcha was not required')
1695
- );
1696
- }
1697
- if (
1698
- password &&
1699
- this.passwordStatus !== PASSWORD_STATUS.REQUIRED &&
1700
- this.passwordStatus !== PASSWORD_STATUS.UNKNOWN
1701
- ) {
1702
- return Promise.reject(
1703
- new Error('fetchMeetingInfo() called with password when password was not required')
1704
- );
1705
- }
1706
-
1707
- this.meetingInfoExtraParams = cloneDeep(extraParams);
1773
+ public async fetchMeetingInfo(options: FetchMeetingInfoParams) {
1774
+ await this.prepForFetchMeetingInfo(options, 'fetchMeetingInfo');
1708
1775
 
1709
1776
  return this.fetchMeetingInfoInternal({
1710
1777
  destination: this.destination,
1711
1778
  destinationType: this.destinationType,
1712
- password,
1713
- captchaCode,
1714
- extraParams,
1715
- sendCAevents,
1779
+ ...options,
1716
1780
  });
1717
1781
  }
1718
1782
 
@@ -2367,16 +2431,6 @@ export default class Meeting extends StatelessWebexPlugin {
2367
2431
  }
2368
2432
  );
2369
2433
 
2370
- this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
2371
- this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
2372
- // clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
2373
- // which means main session is not active for the attendee
2374
- if (error?.statusCode === 403) {
2375
- this.locusInfo.clearMainSessionLocusCache();
2376
- }
2377
- });
2378
- });
2379
-
2380
2434
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
2381
2435
  Trigger.trigger(
2382
2436
  this,
@@ -2611,6 +2665,8 @@ export default class Meeting extends StatelessWebexPlugin {
2611
2665
  switch (newShareStatus) {
2612
2666
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
2613
2667
  const sendStartedSharingRemote = () => {
2668
+ this.remoteShareInstanceId = contentShare.shareInstanceId;
2669
+
2614
2670
  Trigger.trigger(
2615
2671
  this,
2616
2672
  {
@@ -2621,7 +2677,7 @@ export default class Meeting extends StatelessWebexPlugin {
2621
2677
  {
2622
2678
  memberId: contentShare.beneficiaryId,
2623
2679
  url: contentShare.url,
2624
- shareInstanceId: contentShare.shareInstanceId,
2680
+ shareInstanceId: this.remoteShareInstanceId,
2625
2681
  annotationInfo: contentShare.annotation,
2626
2682
  }
2627
2683
  );
@@ -2658,6 +2714,7 @@ export default class Meeting extends StatelessWebexPlugin {
2658
2714
  name: 'client.share.floor-granted.local',
2659
2715
  payload: {
2660
2716
  mediaType: 'share',
2717
+ shareInstanceId: this.localShareInstanceId,
2661
2718
  },
2662
2719
  options: {meetingId: this.id},
2663
2720
  });
@@ -2700,6 +2757,8 @@ export default class Meeting extends StatelessWebexPlugin {
2700
2757
  } else if (newShareStatus === SHARE_STATUS.REMOTE_SHARE_ACTIVE) {
2701
2758
  // if we got here, then some remote participant has stolen
2702
2759
  // the presentation from another remote participant
2760
+ this.remoteShareInstanceId = contentShare.shareInstanceId;
2761
+
2703
2762
  Trigger.trigger(
2704
2763
  this,
2705
2764
  {
@@ -2710,7 +2769,7 @@ export default class Meeting extends StatelessWebexPlugin {
2710
2769
  {
2711
2770
  memberId: contentShare.beneficiaryId,
2712
2771
  url: contentShare.url,
2713
- shareInstanceId: contentShare.shareInstanceId,
2772
+ shareInstanceId: this.remoteShareInstanceId,
2714
2773
  annotationInfo: contentShare.annotation,
2715
2774
  }
2716
2775
  );
@@ -3328,30 +3387,40 @@ export default class Meeting extends StatelessWebexPlugin {
3328
3387
  /**
3329
3388
  * Sets the meeting info on the class instance
3330
3389
  * @param {Object} meetingInfo
3331
- * @param {Object} meetingInfo.body
3332
- * @param {String} meetingInfo.body.conversationUrl
3333
- * @param {String} meetingInfo.body.locusUrl
3334
- * @param {String} meetingInfo.body.sipUri
3335
- * @param {Object} meetingInfo.body.owner
3390
+ * @param {String} meetingInfo.conversationUrl
3391
+ * @param {String} meetingInfo.locusUrl
3392
+ * @param {String} meetingInfo.sipUri
3393
+ * @param {String} [meetingInfo.sipUrl]
3394
+ * @param {String} [meetingInfo.sipMeetingUri]
3395
+ * @param {String} [meetingInfo.meetingNumber]
3396
+ * @param {String} [meetingInfo.meetingJoinUrl]
3397
+ * @param {String} [meetingInfo.hostId]
3398
+ * @param {String} [meetingInfo.permissionToken]
3399
+ * @param {String} [meetingInfo.channel]
3400
+ * @param {Object} meetingInfo.owner
3336
3401
  * @param {Object | String} destination locus object with meeting data or destination string (sip url, meeting link, etc)
3402
+ * @param {Object | String} errors Meeting info request error
3337
3403
  * @returns {undefined}
3338
3404
  * @private
3339
3405
  * @memberof Meeting
3340
3406
  */
3341
3407
  parseMeetingInfo(
3342
- meetingInfo:
3343
- | {
3344
- body: {
3345
- conversationUrl: string;
3346
- locusUrl: string;
3347
- sipUri: string;
3348
- owner: object;
3349
- };
3350
- }
3351
- | any,
3352
- destination: object | string | null = null
3408
+ meetingInfo: {
3409
+ conversationUrl: string;
3410
+ locusUrl: string;
3411
+ sipUri: string;
3412
+ owner: object;
3413
+ sipUrl?: string;
3414
+ sipMeetingUri?: string;
3415
+ meetingNumber?: string;
3416
+ meetingJoinUrl?: string;
3417
+ hostId?: string;
3418
+ permissionToken?: string;
3419
+ channel?: string;
3420
+ },
3421
+ destination: object | string | null = null,
3422
+ errors: any = undefined
3353
3423
  ) {
3354
- const webexMeetingInfo = meetingInfo?.body;
3355
3424
  // We try to use as much info from Locus meeting object, stored in destination
3356
3425
 
3357
3426
  let locusMeetingObject;
@@ -3361,40 +3430,31 @@ export default class Meeting extends StatelessWebexPlugin {
3361
3430
  }
3362
3431
 
3363
3432
  // MeetingInfo will be undefined for 1:1 calls
3364
- if (
3365
- locusMeetingObject ||
3366
- (webexMeetingInfo && !(meetingInfo?.errors && meetingInfo?.errors.length > 0))
3367
- ) {
3433
+ if (locusMeetingObject || (meetingInfo && !(errors?.length > 0))) {
3368
3434
  this.conversationUrl =
3369
- locusMeetingObject?.conversationUrl ||
3370
- webexMeetingInfo?.conversationUrl ||
3371
- this.conversationUrl;
3372
- this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
3435
+ locusMeetingObject?.conversationUrl || meetingInfo?.conversationUrl || this.conversationUrl;
3436
+ this.locusUrl = locusMeetingObject?.url || meetingInfo?.locusUrl || this.locusUrl;
3373
3437
  // @ts-ignore - config coming from registerPlugin
3374
3438
  this.setSipUri(
3375
3439
  // @ts-ignore
3376
3440
  this.config.experimental.enableUnifiedMeetings
3377
- ? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl
3378
- : locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri
3441
+ ? locusMeetingObject?.info.sipUri || meetingInfo?.sipUrl
3442
+ : locusMeetingObject?.info.sipUri || meetingInfo?.sipMeetingUri || this.sipUri
3379
3443
  );
3380
3444
  // @ts-ignore - config coming from registerPlugin
3381
3445
  if (this.config.experimental.enableUnifiedMeetings) {
3382
- this.meetingNumber =
3383
- locusMeetingObject?.info.webExMeetingId || webexMeetingInfo?.meetingNumber;
3384
- this.meetingJoinUrl = webexMeetingInfo?.meetingJoinUrl;
3446
+ this.meetingNumber = locusMeetingObject?.info.webExMeetingId || meetingInfo?.meetingNumber;
3447
+ this.meetingJoinUrl = meetingInfo?.meetingJoinUrl;
3385
3448
  }
3386
3449
  this.owner =
3387
- locusMeetingObject?.info.owner ||
3388
- webexMeetingInfo?.owner ||
3389
- webexMeetingInfo?.hostId ||
3390
- this.owner;
3391
- this.permissionToken = webexMeetingInfo?.permissionToken;
3392
- this.setPermissionTokenPayload(webexMeetingInfo?.permissionToken);
3450
+ locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
3451
+ this.permissionToken = meetingInfo?.permissionToken;
3452
+ this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3393
3453
  this.setSelfUserPolicies();
3394
3454
  // Need to populate environment when sending CA event
3395
- this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
3455
+ this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
3396
3456
  }
3397
- MeetingUtil.parseInterpretationInfo(this, webexMeetingInfo);
3457
+ MeetingUtil.parseInterpretationInfo(this, meetingInfo);
3398
3458
  }
3399
3459
 
3400
3460
  /**
@@ -5534,7 +5594,6 @@ export default class Meeting extends StatelessWebexPlugin {
5534
5594
  seq: event.roapMessage.seq,
5535
5595
  tieBreaker: event.roapMessage.tieBreaker,
5536
5596
  meeting: this, // or can pass meeting ID
5537
- reconnect: this.reconnectionManager.isReconnectInProgress(),
5538
5597
  })
5539
5598
  .then(({roapAnswer}) => {
5540
5599
  if (roapAnswer) {
@@ -5851,7 +5910,10 @@ export default class Meeting extends StatelessWebexPlugin {
5851
5910
  // @ts-ignore
5852
5911
  this.webex.internal.newMetrics.submitClientEvent({
5853
5912
  name: 'client.media.tx.start',
5854
- payload: {mediaType: data.type},
5913
+ payload: {
5914
+ mediaType: data.type,
5915
+ shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
5916
+ },
5855
5917
  options: {
5856
5918
  meetingId: this.id,
5857
5919
  },
@@ -5861,7 +5923,10 @@ export default class Meeting extends StatelessWebexPlugin {
5861
5923
  // @ts-ignore
5862
5924
  this.webex.internal.newMetrics.submitClientEvent({
5863
5925
  name: 'client.media.tx.stop',
5864
- payload: {mediaType: data.type},
5926
+ payload: {
5927
+ mediaType: data.type,
5928
+ shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
5929
+ },
5865
5930
  options: {
5866
5931
  meetingId: this.id,
5867
5932
  },
@@ -5880,7 +5945,10 @@ export default class Meeting extends StatelessWebexPlugin {
5880
5945
  // @ts-ignore
5881
5946
  this.webex.internal.newMetrics.submitClientEvent({
5882
5947
  name: 'client.media.rx.start',
5883
- payload: {mediaType: data.type},
5948
+ payload: {
5949
+ mediaType: data.type,
5950
+ shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
5951
+ },
5884
5952
  options: {
5885
5953
  meetingId: this.id,
5886
5954
  },
@@ -5890,7 +5958,10 @@ export default class Meeting extends StatelessWebexPlugin {
5890
5958
  // @ts-ignore
5891
5959
  this.webex.internal.newMetrics.submitClientEvent({
5892
5960
  name: 'client.media.rx.stop',
5893
- payload: {mediaType: data.type},
5961
+ payload: {
5962
+ mediaType: data.type,
5963
+ shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
5964
+ },
5894
5965
  options: {
5895
5966
  meetingId: this.id,
5896
5967
  },
@@ -7136,11 +7207,14 @@ export default class Meeting extends StatelessWebexPlugin {
7136
7207
  if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
7137
7208
  // @ts-ignore
7138
7209
  this.webex.internal.newMetrics.submitClientEvent({
7139
- name: 'client.share.initiated',
7210
+ name: 'client.share.floor-grant.request',
7140
7211
  payload: {
7141
7212
  mediaType: 'share',
7213
+ shareInstanceId: this.localShareInstanceId,
7214
+ },
7215
+ options: {
7216
+ meetingId: this.id,
7142
7217
  },
7143
- options: {meetingId: this.id},
7144
7218
  });
7145
7219
 
7146
7220
  return this.meetingRequest
@@ -7171,6 +7245,19 @@ export default class Meeting extends StatelessWebexPlugin {
7171
7245
  stack: error.stack,
7172
7246
  });
7173
7247
 
7248
+ // @ts-ignore
7249
+ this.webex.internal.newMetrics.submitClientEvent({
7250
+ name: 'client.share.floor-granted.local',
7251
+ payload: {
7252
+ mediaType: 'share',
7253
+ errors: MeetingUtil.getChangeMeetingFloorErrorPayload(error.message),
7254
+ shareInstanceId: this.localShareInstanceId,
7255
+ },
7256
+ options: {
7257
+ meetingId: this.id,
7258
+ },
7259
+ });
7260
+
7174
7261
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
7175
7262
 
7176
7263
  return Promise.reject(error);
@@ -7222,6 +7309,7 @@ export default class Meeting extends StatelessWebexPlugin {
7222
7309
  name: 'client.share.stopped',
7223
7310
  payload: {
7224
7311
  mediaType: 'share',
7312
+ shareInstanceId: this.localShareInstanceId,
7225
7313
  },
7226
7314
  options: {meetingId: this.id},
7227
7315
  });
@@ -8104,6 +8192,17 @@ export default class Meeting extends StatelessWebexPlugin {
8104
8192
  }
8105
8193
 
8106
8194
  if (floorRequestNeeded) {
8195
+ this.localShareInstanceId = uuid.v4();
8196
+
8197
+ // @ts-ignore
8198
+ this.webex.internal.newMetrics.submitClientEvent({
8199
+ name: 'client.share.initiated',
8200
+ payload: {
8201
+ mediaType: 'share',
8202
+ shareInstanceId: this.localShareInstanceId,
8203
+ },
8204
+ options: {meetingId: this.id},
8205
+ });
8107
8206
  // we're sending the http request to Locus to request the screen share floor
8108
8207
  // only after the SDP update, because that's how it's always been done for transcoded meetings
8109
8208
  // and also if sharing from the start, we need confluence to have been created
@@ -185,6 +185,13 @@ export default class MeetingRequest extends StatelessWebexPlugin {
185
185
  deviceCapabilities.push(ANNOTATION.ANNOTATION_ON_SHARE_SUPPORTED);
186
186
  }
187
187
 
188
+ // append installationId to device config if it exists
189
+ // @ts-ignore
190
+ if (this.webex.internal.device.config.installationId) {
191
+ // @ts-ignore
192
+ body.device.installationId = this.webex.internal.device.config.installationId;
193
+ }
194
+
188
195
  if (locale) {
189
196
  body.locale = locale;
190
197
  }
@@ -13,6 +13,7 @@ import {
13
13
  FULL_STATE,
14
14
  SELF_POLICY,
15
15
  EVENT_TRIGGERS,
16
+ LOCAL_SHARE_ERRORS,
16
17
  IP_VERSION,
17
18
  } from '../constants';
18
19
  import BrowserDetection from '../common/browser-detection';
@@ -692,6 +693,102 @@ const MeetingUtil = {
692
693
  EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
693
694
  );
694
695
  },
696
+
697
+ /**
698
+ * Returns a CA-recognized error payload for the specified raw error message/reason.
699
+ *
700
+ * New errors can be added to this function for handling in the future
701
+ *
702
+ * @param {String} reason the raw error message
703
+ * @returns {Array<object>} an array of payload objects
704
+ */
705
+ getChangeMeetingFloorErrorPayload: (reason: string) => {
706
+ const errorPayload = {
707
+ errorDescription: reason,
708
+ name: 'locus.response',
709
+ shownToUser: false,
710
+ };
711
+ if (reason.includes(LOCAL_SHARE_ERRORS.UNDEFINED)) {
712
+ return [
713
+ {
714
+ ...errorPayload,
715
+ fatal: true,
716
+ category: 'signaling',
717
+ errorCode: 1100,
718
+ },
719
+ ];
720
+ }
721
+ if (reason.includes(LOCAL_SHARE_ERRORS.DEVICE_NOT_JOINED)) {
722
+ return [
723
+ {
724
+ ...errorPayload,
725
+ fatal: true,
726
+ category: 'signaling',
727
+ errorCode: 4050,
728
+ },
729
+ ];
730
+ }
731
+ if (reason.includes(LOCAL_SHARE_ERRORS.NO_MEDIA_FOR_DEVICE)) {
732
+ return [
733
+ {
734
+ ...errorPayload,
735
+ fatal: true,
736
+ category: 'media',
737
+ errorCode: 2048,
738
+ },
739
+ ];
740
+ }
741
+ if (reason.includes(LOCAL_SHARE_ERRORS.NO_CONFLUENCE_ID)) {
742
+ return [
743
+ {
744
+ ...errorPayload,
745
+ fatal: true,
746
+ category: 'signaling',
747
+ errorCode: 4064,
748
+ },
749
+ ];
750
+ }
751
+ if (reason.includes(LOCAL_SHARE_ERRORS.CONTENT_SHARING_DISABLED)) {
752
+ return [
753
+ {
754
+ ...errorPayload,
755
+ fatal: true,
756
+ category: 'expected',
757
+ errorCode: 4065,
758
+ },
759
+ ];
760
+ }
761
+ if (reason.includes(LOCAL_SHARE_ERRORS.LOCUS_PARTICIPANT_DNE)) {
762
+ return [
763
+ {
764
+ ...errorPayload,
765
+ fatal: true,
766
+ category: 'signaling',
767
+ errorCode: 4066,
768
+ },
769
+ ];
770
+ }
771
+ if (reason.includes(LOCAL_SHARE_ERRORS.CONTENT_REQUEST_WHILE_PENDING_WHITEBOARD)) {
772
+ return [
773
+ {
774
+ ...errorPayload,
775
+ fatal: true,
776
+ category: 'expected',
777
+ errorCode: 4067,
778
+ },
779
+ ];
780
+ }
781
+
782
+ // return unknown error
783
+ return [
784
+ {
785
+ ...errorPayload,
786
+ fatal: true,
787
+ category: 'signaling',
788
+ errorCode: 1100,
789
+ },
790
+ ];
791
+ },
695
792
  };
696
793
 
697
794
  export default MeetingUtil;