@webex/plugin-meetings 3.8.0-next.5 → 3.8.0-next.50

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 (133) 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 +14 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/controls-options-manager/enums.js +2 -0
  8. package/dist/controls-options-manager/enums.js.map +1 -1
  9. package/dist/controls-options-manager/types.js.map +1 -1
  10. package/dist/controls-options-manager/util.js +52 -0
  11. package/dist/controls-options-manager/util.js.map +1 -1
  12. package/dist/interpretation/index.js +1 -1
  13. package/dist/interpretation/siLanguage.js +1 -1
  14. package/dist/locus-info/controlsUtils.js +28 -10
  15. package/dist/locus-info/controlsUtils.js.map +1 -1
  16. package/dist/locus-info/index.js +20 -1
  17. package/dist/locus-info/index.js.map +1 -1
  18. package/dist/media/index.js +3 -15
  19. package/dist/media/index.js.map +1 -1
  20. package/dist/meeting/in-meeting-actions.js +11 -1
  21. package/dist/meeting/in-meeting-actions.js.map +1 -1
  22. package/dist/meeting/index.js +544 -324
  23. package/dist/meeting/index.js.map +1 -1
  24. package/dist/meeting/locusMediaRequest.js +26 -23
  25. package/dist/meeting/locusMediaRequest.js.map +1 -1
  26. package/dist/meeting/muteState.js +0 -2
  27. package/dist/meeting/muteState.js.map +1 -1
  28. package/dist/meeting/request.js +30 -0
  29. package/dist/meeting/request.js.map +1 -1
  30. package/dist/meeting/request.type.js.map +1 -1
  31. package/dist/meeting/util.js +27 -2
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +359 -60
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +69 -1
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/meetings/util.js +14 -0
  38. package/dist/meetings/util.js.map +1 -1
  39. package/dist/member/index.js +10 -0
  40. package/dist/member/index.js.map +1 -1
  41. package/dist/member/util.js +3 -0
  42. package/dist/member/util.js.map +1 -1
  43. package/dist/metrics/constants.js +9 -0
  44. package/dist/metrics/constants.js.map +1 -1
  45. package/dist/reachability/clusterReachability.js +63 -27
  46. package/dist/reachability/clusterReachability.js.map +1 -1
  47. package/dist/reachability/index.js +111 -46
  48. package/dist/reachability/index.js.map +1 -1
  49. package/dist/reachability/reachability.types.js +14 -0
  50. package/dist/reachability/reachability.types.js.map +1 -1
  51. package/dist/reachability/request.js +19 -3
  52. package/dist/reachability/request.js.map +1 -1
  53. package/dist/reconnection-manager/index.js +2 -2
  54. package/dist/reconnection-manager/index.js.map +1 -1
  55. package/dist/recording-controller/util.js +5 -5
  56. package/dist/recording-controller/util.js.map +1 -1
  57. package/dist/roap/index.js.map +1 -1
  58. package/dist/roap/turnDiscovery.js +45 -27
  59. package/dist/roap/turnDiscovery.js.map +1 -1
  60. package/dist/roap/types.js +17 -0
  61. package/dist/roap/types.js.map +1 -0
  62. package/dist/types/config.d.ts +1 -0
  63. package/dist/types/constants.d.ts +10 -0
  64. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  65. package/dist/types/controls-options-manager/types.d.ts +7 -1
  66. package/dist/types/locus-info/index.d.ts +1 -0
  67. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  68. package/dist/types/meeting/index.d.ts +50 -3
  69. package/dist/types/meeting/muteState.d.ts +0 -1
  70. package/dist/types/meeting/request.d.ts +12 -1
  71. package/dist/types/meeting/request.type.d.ts +6 -0
  72. package/dist/types/meeting/util.d.ts +8 -1
  73. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  74. package/dist/types/meetings/index.d.ts +29 -0
  75. package/dist/types/member/index.d.ts +1 -0
  76. package/dist/types/metrics/constants.d.ts +9 -0
  77. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  78. package/dist/types/reachability/index.d.ts +10 -1
  79. package/dist/types/reachability/reachability.types.d.ts +5 -0
  80. package/dist/types/roap/index.d.ts +3 -2
  81. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  82. package/dist/types/roap/types.d.ts +16 -0
  83. package/dist/webinar/index.js +1 -1
  84. package/package.json +22 -22
  85. package/src/config.ts +1 -0
  86. package/src/constants.ts +17 -0
  87. package/src/controls-options-manager/enums.ts +2 -0
  88. package/src/controls-options-manager/types.ts +11 -1
  89. package/src/controls-options-manager/util.ts +62 -0
  90. package/src/locus-info/controlsUtils.ts +44 -14
  91. package/src/locus-info/index.ts +23 -1
  92. package/src/media/index.ts +5 -21
  93. package/src/meeting/in-meeting-actions.ts +20 -0
  94. package/src/meeting/index.ts +351 -99
  95. package/src/meeting/locusMediaRequest.ts +33 -23
  96. package/src/meeting/muteState.ts +0 -2
  97. package/src/meeting/request.ts +36 -1
  98. package/src/meeting/request.type.ts +7 -0
  99. package/src/meeting/util.ts +27 -2
  100. package/src/meeting-info/meeting-info-v2.ts +247 -6
  101. package/src/meetings/index.ts +87 -1
  102. package/src/meetings/util.ts +18 -0
  103. package/src/member/index.ts +11 -0
  104. package/src/member/util.ts +3 -0
  105. package/src/metrics/constants.ts +9 -0
  106. package/src/reachability/clusterReachability.ts +73 -26
  107. package/src/reachability/index.ts +69 -0
  108. package/src/reachability/reachability.types.ts +6 -0
  109. package/src/reachability/request.ts +7 -0
  110. package/src/reconnection-manager/index.ts +2 -2
  111. package/src/recording-controller/util.ts +17 -13
  112. package/src/roap/index.ts +3 -7
  113. package/src/roap/turnDiscovery.ts +34 -39
  114. package/src/roap/types.ts +23 -0
  115. package/test/unit/spec/controls-options-manager/util.js +120 -0
  116. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  117. package/test/unit/spec/locus-info/index.js +28 -0
  118. package/test/unit/spec/media/index.ts +6 -16
  119. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -4
  120. package/test/unit/spec/meeting/index.js +558 -145
  121. package/test/unit/spec/meeting/locusMediaRequest.ts +101 -88
  122. package/test/unit/spec/meeting/muteState.js +0 -2
  123. package/test/unit/spec/meeting/request.js +32 -1
  124. package/test/unit/spec/meeting/utils.js +123 -18
  125. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  126. package/test/unit/spec/meetings/index.js +96 -1
  127. package/test/unit/spec/member/index.js +7 -0
  128. package/test/unit/spec/member/util.js +24 -0
  129. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  130. package/test/unit/spec/reachability/index.ts +47 -0
  131. package/test/unit/spec/reachability/request.js +47 -2
  132. package/test/unit/spec/reconnection-manager/index.js +4 -4
  133. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -60,11 +60,8 @@ import {
60
60
  import LoggerProxy from '../common/logs/logger-proxy';
61
61
  import EventsUtil from '../common/events/util';
62
62
  import Trigger from '../common/events/trigger-proxy';
63
- import Roap, {
64
- type TurnDiscoveryResult,
65
- type TurnServerInfo,
66
- type TurnDiscoverySkipReason,
67
- } from '../roap/index';
63
+ import Roap, {type TurnDiscoveryResult, type TurnDiscoverySkipReason} from '../roap/index';
64
+ import {type TurnServerInfo} from '../roap/types';
68
65
  import Media, {type BundlePolicy} from '../media';
69
66
  import MediaProperties from '../media/properties';
70
67
  import MeetingStateMachine from './state';
@@ -241,6 +238,8 @@ export type CallStateForMetrics = {
241
238
  sessionCorrelationId?: string;
242
239
  joinTrigger?: string;
243
240
  loginType?: string;
241
+ userNameInput?: string;
242
+ emailInput?: string;
244
243
  };
245
244
 
246
245
  export const MEDIA_UPDATE_TYPE = {
@@ -648,6 +647,13 @@ export default class Meeting extends StatelessWebexPlugin {
648
647
  allowMediaInLobby: boolean;
649
648
  localShareInstanceId: string;
650
649
  remoteShareInstanceId: string;
650
+ shareCAEventSentStatus: {
651
+ transmitStart: boolean;
652
+ transmitStop: boolean;
653
+ receiveStart: boolean;
654
+ receiveStop: boolean;
655
+ };
656
+
651
657
  turnDiscoverySkippedReason: TurnDiscoverySkipReason;
652
658
  turnServerUsed: boolean;
653
659
  areVoiceaEventsSetup = false;
@@ -716,6 +722,7 @@ export default class Meeting extends StatelessWebexPlugin {
716
722
  private rtcMetrics?: RtcMetrics;
717
723
  private uploadLogsTimer?: ReturnType<typeof setTimeout>;
718
724
  private logUploadIntervalIndex: number;
725
+ private mediaServerIp: string;
719
726
 
720
727
  /**
721
728
  * @param {Object} attrs
@@ -1420,6 +1427,19 @@ export default class Meeting extends StatelessWebexPlugin {
1420
1427
  */
1421
1428
  this.remoteShareInstanceId = null;
1422
1429
 
1430
+ /**
1431
+ * Status used for ensuring we do not oversend metrics
1432
+ * @instance
1433
+ * @private
1434
+ * @memberof Meeting
1435
+ */
1436
+ this.shareCAEventSentStatus = {
1437
+ transmitStart: false,
1438
+ transmitStop: false,
1439
+ receiveStart: false,
1440
+ receiveStop: false,
1441
+ };
1442
+
1423
1443
  /**
1424
1444
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1425
1445
  * @instance
@@ -1579,6 +1599,15 @@ export default class Meeting extends StatelessWebexPlugin {
1579
1599
  * @memberof Meeting
1580
1600
  */
1581
1601
  this.#isoLocalClientMeetingJoinTime = undefined;
1602
+
1603
+ /**
1604
+ * IP Address of the remote media server
1605
+ * @instance
1606
+ * @type {string}
1607
+ * @private
1608
+ * @memberof Meeting
1609
+ */
1610
+ this.mediaServerIp = undefined;
1582
1611
  }
1583
1612
 
1584
1613
  /**
@@ -1627,6 +1656,38 @@ export default class Meeting extends StatelessWebexPlugin {
1627
1656
  this.callStateForMetrics.correlationId = correlationId;
1628
1657
  }
1629
1658
 
1659
+ /**
1660
+ * Getter - Returns callStateForMetrics.userNameInput
1661
+ * @returns {string}
1662
+ */
1663
+ get userNameInput() {
1664
+ return this.callStateForMetrics?.userNameInput;
1665
+ }
1666
+
1667
+ /**
1668
+ * Setter - sets callStateForMetrics.userNameInput
1669
+ * @param {string} userNameInput
1670
+ */
1671
+ set userNameInput(userNameInput: string) {
1672
+ this.callStateForMetrics.userNameInput = userNameInput;
1673
+ }
1674
+
1675
+ /**
1676
+ * Getter - Returns callStateForMetrics.emailInput
1677
+ * @returns {string}
1678
+ */
1679
+ get emailInput() {
1680
+ return this.callStateForMetrics?.emailInput;
1681
+ }
1682
+
1683
+ /**
1684
+ * Setter - sets callStateForMetrics.emailInput
1685
+ * @param {string} emailInput
1686
+ */
1687
+ set emailInput(emailInput: string) {
1688
+ this.callStateForMetrics.emailInput = emailInput;
1689
+ }
1690
+
1630
1691
  /**
1631
1692
  * Getter - Returns callStateForMetrics.sessionCorrelationId
1632
1693
  * @returns {string}
@@ -1652,6 +1713,33 @@ export default class Meeting extends StatelessWebexPlugin {
1652
1713
  return this.#isoLocalClientMeetingJoinTime;
1653
1714
  }
1654
1715
 
1716
+ /**
1717
+ * Setter - sets isoLocalClientMeetingJoinTime
1718
+ * This will be set once on meeting join, and not updated again
1719
+ * this will always produce an ISO string
1720
+ * If the iso string is invalid, it will fallback to the current system time
1721
+ * @param {string | undefined} time
1722
+ */
1723
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1724
+ const fallback = new Date().toISOString();
1725
+ if (!time) {
1726
+ this.#isoLocalClientMeetingJoinTime = fallback;
1727
+ } else {
1728
+ const date = new Date(time);
1729
+
1730
+ // Check if the date is valid
1731
+ if (Number.isNaN(date.getTime())) {
1732
+ LoggerProxy.logger.info(
1733
+ // @ts-ignore
1734
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1735
+ );
1736
+ this.#isoLocalClientMeetingJoinTime = fallback;
1737
+ } else {
1738
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1739
+ }
1740
+ }
1741
+ }
1742
+
1655
1743
  /**
1656
1744
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1657
1745
  * @param {any} info
@@ -2793,6 +2881,24 @@ export default class Meeting extends StatelessWebexPlugin {
2793
2881
  {state}
2794
2882
  );
2795
2883
  });
2884
+
2885
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2886
+ Trigger.trigger(
2887
+ this,
2888
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2889
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
2890
+ {state}
2891
+ );
2892
+ });
2893
+
2894
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED, ({state}) => {
2895
+ Trigger.trigger(
2896
+ this,
2897
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2898
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
2899
+ {state}
2900
+ );
2901
+ });
2796
2902
  }
2797
2903
 
2798
2904
  /**
@@ -2965,6 +3071,8 @@ export default class Meeting extends StatelessWebexPlugin {
2965
3071
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
2966
3072
  const sendStartedSharingRemote = () => {
2967
3073
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3074
+ this.shareCAEventSentStatus.receiveStart = false;
3075
+ this.shareCAEventSentStatus.receiveStop = false;
2968
3076
 
2969
3077
  Trigger.trigger(
2970
3078
  this,
@@ -3018,6 +3126,7 @@ export default class Meeting extends StatelessWebexPlugin {
3018
3126
  },
3019
3127
  options: {meetingId: this.id},
3020
3128
  });
3129
+
3021
3130
  break;
3022
3131
 
3023
3132
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3058,6 +3167,8 @@ export default class Meeting extends StatelessWebexPlugin {
3058
3167
  // if we got here, then some remote participant has stolen
3059
3168
  // the presentation from another remote participant
3060
3169
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3170
+ this.shareCAEventSentStatus.receiveStart = false;
3171
+ this.shareCAEventSentStatus.receiveStop = false;
3061
3172
 
3062
3173
  Trigger.trigger(
3063
3174
  this,
@@ -3741,7 +3852,13 @@ export default class Meeting extends StatelessWebexPlugin {
3741
3852
  return Promise.reject(error);
3742
3853
  }
3743
3854
 
3744
- return this.brbState.enable(enabled, this.sendSlotManager);
3855
+ return this.brbState.enable(enabled, this.sendSlotManager).then(() => {
3856
+ if (this.audio && enabled) {
3857
+ // locus mutes the participant with brb enabled request,
3858
+ // so we need to explicitly update remote mute for correct logic flow
3859
+ this.audio.handleServerRemoteMuteUpdate(this, enabled);
3860
+ }
3861
+ });
3745
3862
  }
3746
3863
 
3747
3864
  /**
@@ -3933,7 +4050,10 @@ export default class Meeting extends StatelessWebexPlugin {
3933
4050
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3934
4051
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3935
4052
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3936
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4053
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4054
+ this.userDisplayHints,
4055
+ this.selfUserPolicies
4056
+ ),
3937
4057
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3938
4058
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
3939
4059
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -3982,6 +4102,9 @@ export default class Meeting extends StatelessWebexPlugin {
3982
4102
  this.inMeetingActions.canSendReactions,
3983
4103
  this.userDisplayHints
3984
4104
  ),
4105
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4106
+ this.userDisplayHints
4107
+ ),
3985
4108
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
3986
4109
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
3987
4110
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -4177,6 +4300,22 @@ export default class Meeting extends StatelessWebexPlugin {
4177
4300
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4178
4301
  policies: this.selfUserPolicies,
4179
4302
  }),
4303
+ canEnableAnnotation: ControlsOptionsUtil.hasHints({
4304
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
4305
+ displayHints: this.userDisplayHints,
4306
+ }),
4307
+ canDisableAnnotation: ControlsOptionsUtil.hasHints({
4308
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
4309
+ displayHints: this.userDisplayHints,
4310
+ }),
4311
+ canEnableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4312
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
4313
+ displayHints: this.userDisplayHints,
4314
+ }),
4315
+ canDisableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4316
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
4317
+ displayHints: this.userDisplayHints,
4318
+ }),
4180
4319
  }) || changed;
4181
4320
  }
4182
4321
  if (changed) {
@@ -5684,8 +5823,6 @@ export default class Meeting extends StatelessWebexPlugin {
5684
5823
  // @ts-ignore
5685
5824
  this.webex.internal.device.meetingStarted();
5686
5825
 
5687
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5688
-
5689
5826
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5690
5827
 
5691
5828
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -5729,11 +5866,13 @@ export default class Meeting extends StatelessWebexPlugin {
5729
5866
  this
5730
5867
  );
5731
5868
 
5732
- joinFailed(error);
5869
+ const proxyError = MeetingUtil.markErrorAsHandledBySdk(error);
5870
+
5871
+ joinFailed(proxyError);
5733
5872
 
5734
5873
  this.deferJoin = undefined;
5735
5874
 
5736
- return Promise.reject(error);
5875
+ return Promise.reject(proxyError);
5737
5876
  })
5738
5877
  .then((join) => {
5739
5878
  // @ts-ignore - config coming from registerPlugin
@@ -6116,10 +6255,10 @@ export default class Meeting extends StatelessWebexPlugin {
6116
6255
  /**
6117
6256
  * Handles ROAP_FAILURE event from the webrtc media connection
6118
6257
  *
6119
- * @param {Error} error
6258
+ * @param {Error} roapError
6120
6259
  * @returns {void}
6121
6260
  */
6122
- handleRoapFailure = (error) => {
6261
+ handleRoapFailure = (roapError) => {
6123
6262
  // eslint-disable-next-line @typescript-eslint/no-shadow
6124
6263
  const sendBehavioralMetric = (metricName, error, correlationId) => {
6125
6264
  const data = {
@@ -6135,6 +6274,8 @@ export default class Meeting extends StatelessWebexPlugin {
6135
6274
  Metrics.sendBehavioralMetric(metricName, data, metadata);
6136
6275
  };
6137
6276
 
6277
+ const error = MeetingUtil.markErrorAsHandledBySdk(roapError);
6278
+
6138
6279
  if (error instanceof Errors.SdpOfferCreationError) {
6139
6280
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6140
6281
 
@@ -6146,10 +6287,7 @@ export default class Meeting extends StatelessWebexPlugin {
6146
6287
  },
6147
6288
  options: {meetingId: this.id, rawError: error},
6148
6289
  });
6149
- } else if (
6150
- error instanceof Errors.SdpOfferHandlingError ||
6151
- error instanceof Errors.SdpAnswerHandlingError
6152
- ) {
6290
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6153
6291
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6154
6292
 
6155
6293
  // @ts-ignore
@@ -6160,6 +6298,24 @@ export default class Meeting extends StatelessWebexPlugin {
6160
6298
  },
6161
6299
  options: {meetingId: this.id, rawError: error},
6162
6300
  });
6301
+ } else if (error instanceof Errors.SdpAnswerHandlingError) {
6302
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6303
+
6304
+ // @ts-ignore
6305
+ this.webex.internal.newMetrics.submitClientEvent({
6306
+ name: 'client.media-engine.remote-sdp-received',
6307
+ payload: {
6308
+ canProceed: false,
6309
+ },
6310
+ options: {meetingId: this.id, rawError: error},
6311
+ });
6312
+
6313
+ if (this.deferSDPAnswer) {
6314
+ clearTimeout(this.sdpResponseTimer);
6315
+ this.sdpResponseTimer = undefined;
6316
+
6317
+ this.deferSDPAnswer.reject(error);
6318
+ }
6163
6319
  } else if (error instanceof Errors.SdpError) {
6164
6320
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6165
6321
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -6187,6 +6343,11 @@ export default class Meeting extends StatelessWebexPlugin {
6187
6343
  ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6188
6344
  : undefined;
6189
6345
 
6346
+ const mediaServerIp =
6347
+ roapMessage.messageType === 'ANSWER'
6348
+ ? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6349
+ : undefined;
6350
+
6190
6351
  if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6191
6352
  throw new MultistreamNotSupportedError(
6192
6353
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6197,6 +6358,10 @@ export default class Meeting extends StatelessWebexPlugin {
6197
6358
  if (mediaServer) {
6198
6359
  this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
6199
6360
  }
6361
+
6362
+ if (this.isMultistream && mediaServerIp) {
6363
+ this.mediaServerIp = mediaServerIp;
6364
+ }
6200
6365
  };
6201
6366
 
6202
6367
  /**
@@ -6313,7 +6478,9 @@ export default class Meeting extends StatelessWebexPlugin {
6313
6478
  {
6314
6479
  logText: `${LOG_HEADER} Roap Offer`,
6315
6480
  }
6316
- ).catch((error) => {
6481
+ ).catch((originalError) => {
6482
+ const error = MeetingUtil.markErrorAsHandledBySdk(originalError);
6483
+
6317
6484
  const multistreamNotSupported = error instanceof MultistreamNotSupportedError;
6318
6485
 
6319
6486
  // @ts-ignore
@@ -6694,30 +6861,42 @@ export default class Meeting extends StatelessWebexPlugin {
6694
6861
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6695
6862
  data
6696
6863
  );
6697
- // @ts-ignore
6698
- this.webex.internal.newMetrics.submitClientEvent({
6699
- name: 'client.media.tx.start',
6700
- payload: {
6701
- mediaType: data.mediaType,
6702
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6703
- },
6704
- options: {
6705
- meetingId: this.id,
6706
- },
6707
- });
6864
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStart) {
6865
+ // @ts-ignore
6866
+ this.webex.internal.newMetrics.submitClientEvent({
6867
+ name: 'client.media.tx.start',
6868
+ payload: {
6869
+ mediaType: data.mediaType,
6870
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6871
+ },
6872
+ options: {
6873
+ meetingId: this.id,
6874
+ },
6875
+ });
6876
+
6877
+ if (data.mediaType === 'share') {
6878
+ this.shareCAEventSentStatus.transmitStart = true;
6879
+ }
6880
+ }
6708
6881
  });
6709
6882
  this.statsAnalyzer.on(StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED, (data) => {
6710
- // @ts-ignore
6711
- this.webex.internal.newMetrics.submitClientEvent({
6712
- name: 'client.media.tx.stop',
6713
- payload: {
6714
- mediaType: data.mediaType,
6715
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6716
- },
6717
- options: {
6718
- meetingId: this.id,
6719
- },
6720
- });
6883
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStop) {
6884
+ // @ts-ignore
6885
+ this.webex.internal.newMetrics.submitClientEvent({
6886
+ name: 'client.media.tx.stop',
6887
+ payload: {
6888
+ mediaType: data.mediaType,
6889
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6890
+ },
6891
+ options: {
6892
+ meetingId: this.id,
6893
+ },
6894
+ });
6895
+
6896
+ if (data.mediaType === 'share') {
6897
+ this.shareCAEventSentStatus.transmitStop = true;
6898
+ }
6899
+ }
6721
6900
  });
6722
6901
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6723
6902
  Trigger.trigger(
@@ -6729,57 +6908,65 @@ export default class Meeting extends StatelessWebexPlugin {
6729
6908
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6730
6909
  data
6731
6910
  );
6732
- // @ts-ignore
6733
- this.webex.internal.newMetrics.submitClientEvent({
6734
- name: 'client.media.rx.start',
6735
- payload: {
6736
- mediaType: data.mediaType,
6737
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6738
- },
6739
- options: {
6740
- meetingId: this.id,
6741
- },
6742
- });
6743
-
6744
- if (data.mediaType === 'share') {
6911
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6745
6912
  // @ts-ignore
6746
6913
  this.webex.internal.newMetrics.submitClientEvent({
6747
- name: 'client.media.render.start',
6914
+ name: 'client.media.rx.start',
6748
6915
  payload: {
6749
- mediaType: 'share',
6750
- shareInstanceId: this.remoteShareInstanceId,
6916
+ mediaType: data.mediaType,
6917
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6751
6918
  },
6752
6919
  options: {
6753
6920
  meetingId: this.id,
6754
6921
  },
6755
6922
  });
6923
+
6924
+ if (data.mediaType === 'share') {
6925
+ // @ts-ignore
6926
+ this.webex.internal.newMetrics.submitClientEvent({
6927
+ name: 'client.media.render.start',
6928
+ payload: {
6929
+ mediaType: 'share',
6930
+ shareInstanceId: this.remoteShareInstanceId,
6931
+ },
6932
+ options: {
6933
+ meetingId: this.id,
6934
+ },
6935
+ });
6936
+
6937
+ this.shareCAEventSentStatus.receiveStart = true;
6938
+ }
6756
6939
  }
6757
6940
  });
6758
6941
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED, (data) => {
6759
- // @ts-ignore
6760
- this.webex.internal.newMetrics.submitClientEvent({
6761
- name: 'client.media.rx.stop',
6762
- payload: {
6763
- mediaType: data.mediaType,
6764
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6765
- },
6766
- options: {
6767
- meetingId: this.id,
6768
- },
6769
- });
6770
-
6771
- if (data.mediaType === 'share') {
6942
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6772
6943
  // @ts-ignore
6773
6944
  this.webex.internal.newMetrics.submitClientEvent({
6774
- name: 'client.media.render.stop',
6945
+ name: 'client.media.rx.stop',
6775
6946
  payload: {
6776
- mediaType: 'share',
6777
- shareInstanceId: this.remoteShareInstanceId,
6947
+ mediaType: data.mediaType,
6948
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6778
6949
  },
6779
6950
  options: {
6780
6951
  meetingId: this.id,
6781
6952
  },
6782
6953
  });
6954
+
6955
+ if (data.mediaType === 'share') {
6956
+ // @ts-ignore
6957
+ this.webex.internal.newMetrics.submitClientEvent({
6958
+ name: 'client.media.render.stop',
6959
+ payload: {
6960
+ mediaType: 'share',
6961
+ shareInstanceId: this.remoteShareInstanceId,
6962
+ },
6963
+ options: {
6964
+ meetingId: this.id,
6965
+ },
6966
+ });
6967
+
6968
+ this.shareCAEventSentStatus.receiveStop = true;
6969
+ }
6783
6970
  }
6784
6971
  });
6785
6972
  };
@@ -6796,7 +6983,10 @@ export default class Meeting extends StatelessWebexPlugin {
6796
6983
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6797
6984
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6798
6985
  */
6799
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6986
+ private async createMediaConnection(
6987
+ turnServerInfo?: TurnServerInfo,
6988
+ bundlePolicy?: BundlePolicy
6989
+ ) {
6800
6990
  this.rtcMetrics = this.isMultistream
6801
6991
  ? // @ts-ignore
6802
6992
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6938,7 +7128,28 @@ export default class Meeting extends StatelessWebexPlugin {
6938
7128
  } catch (error) {
6939
7129
  const {iceConnected} = error;
6940
7130
 
7131
+ let handledBySdk = false;
7132
+
6941
7133
  if (!this.hasMediaConnectionConnectedAtLeastOnce) {
7134
+ const caError =
7135
+ // @ts-ignore
7136
+ this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode({
7137
+ clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
7138
+ signalingState:
7139
+ this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
7140
+ ?.signalingState ||
7141
+ this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
7142
+ 'unknown',
7143
+ iceConnected,
7144
+ turnServerUsed: this.turnServerUsed,
7145
+ unreachable:
7146
+ // @ts-ignore
7147
+ await this.webex.meetings.reachability
7148
+ .isWebexMediaBackendUnreachable()
7149
+ .catch(() => false),
7150
+ }),
7151
+ });
7152
+
6942
7153
  // Only send CA event for join flow if we haven't successfully connected media yet
6943
7154
  // @ts-ignore
6944
7155
  this.webex.internal.newMetrics.submitClientEvent({
@@ -6946,37 +7157,25 @@ export default class Meeting extends StatelessWebexPlugin {
6946
7157
  payload: {
6947
7158
  canProceed: !this.turnServerUsed, // If we haven't done turn tls retry yet we will proceed with join attempt
6948
7159
  icePhase: this.addMediaData.icePhaseCallback(),
6949
- errors: [
6950
- // @ts-ignore
6951
- this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
6952
- {
6953
- clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
6954
- signalingState:
6955
- this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
6956
- ?.signalingState ||
6957
- this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
6958
- ?.signalingState ||
6959
- 'unknown',
6960
- iceConnected,
6961
- turnServerUsed: this.turnServerUsed,
6962
- unreachable:
6963
- // @ts-ignore
6964
- await this.webex.meetings.reachability
6965
- .isWebexMediaBackendUnreachable()
6966
- .catch(() => false),
6967
- }),
6968
- }
6969
- ),
6970
- ],
7160
+ errors: [caError],
6971
7161
  },
6972
7162
  options: {
6973
7163
  meetingId: this.id,
6974
7164
  },
6975
7165
  });
7166
+
7167
+ handledBySdk = true;
6976
7168
  }
6977
- throw new Error(
7169
+
7170
+ let timedOutError = new Error(
6978
7171
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
6979
7172
  );
7173
+
7174
+ if (handledBySdk) {
7175
+ timedOutError = MeetingUtil.markErrorAsHandledBySdk(timedOutError);
7176
+ }
7177
+
7178
+ throw timedOutError;
6980
7179
  }
6981
7180
  }
6982
7181
 
@@ -6997,6 +7196,12 @@ export default class Meeting extends StatelessWebexPlugin {
6997
7196
  networkQualityMonitor: this.networkQualityMonitor,
6998
7197
  isMultistream: this.isMultistream,
6999
7198
  });
7199
+ this.shareCAEventSentStatus = {
7200
+ transmitStart: false,
7201
+ transmitStop: false,
7202
+ receiveStart: false,
7203
+ receiveStop: false,
7204
+ };
7000
7205
  this.setupStatsAnalyzerEventHandlers();
7001
7206
  this.networkQualityMonitor.on(
7002
7207
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -7031,6 +7236,11 @@ export default class Meeting extends StatelessWebexPlugin {
7031
7236
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7032
7237
  } seconds`
7033
7238
  );
7239
+
7240
+ const timeoutError = new Error('Timeout waiting for SDP answer');
7241
+
7242
+ const timeoutErrorProxy = MeetingUtil.markErrorAsHandledBySdk(timeoutError);
7243
+
7034
7244
  // @ts-ignore
7035
7245
  this.webex.internal.newMetrics.submitClientEvent({
7036
7246
  name: 'client.media-engine.remote-sdp-received',
@@ -7043,7 +7253,7 @@ export default class Meeting extends StatelessWebexPlugin {
7043
7253
  }),
7044
7254
  ],
7045
7255
  },
7046
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7256
+ options: {meetingId: this.id, rawError: timeoutErrorProxy},
7047
7257
  });
7048
7258
 
7049
7259
  deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
@@ -7151,7 +7361,14 @@ export default class Meeting extends StatelessWebexPlugin {
7151
7361
  error
7152
7362
  );
7153
7363
 
7154
- throw new AddMediaFailed();
7364
+ let addMediaFailedError = new AddMediaFailed();
7365
+
7366
+ // @ts-ignore - handledBySdk is added by a proxy
7367
+ if (error.handledBySdk) {
7368
+ addMediaFailedError = MeetingUtil.markErrorAsHandledBySdk(addMediaFailedError);
7369
+ }
7370
+
7371
+ throw addMediaFailedError;
7155
7372
  }
7156
7373
  }
7157
7374
 
@@ -7570,6 +7787,11 @@ export default class Meeting extends StatelessWebexPlugin {
7570
7787
  const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7571
7788
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7572
7789
 
7790
+ // @ts-ignore
7791
+ const isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(
7792
+ this.mediaServerIp
7793
+ );
7794
+
7573
7795
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7574
7796
  correlation_id: this.correlationId,
7575
7797
  locus_id: this.locusUrl.split('/').pop(),
@@ -7579,6 +7801,7 @@ export default class Meeting extends StatelessWebexPlugin {
7579
7801
  isMultistream: this.isMultistream,
7580
7802
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7581
7803
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7804
+ isSubnetReachable,
7582
7805
  ...reachabilityStats,
7583
7806
  ...iceCandidateErrors,
7584
7807
  iceCandidatesCount: this.iceCandidatesCount,
@@ -7608,6 +7831,11 @@ export default class Meeting extends StatelessWebexPlugin {
7608
7831
 
7609
7832
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7610
7833
 
7834
+ // @ts-ignore
7835
+ const isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(
7836
+ this.mediaServerIp
7837
+ );
7838
+
7611
7839
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
7612
7840
  correlation_id: this.correlationId,
7613
7841
  locus_id: this.locusUrl.split('/').pop(),
@@ -7637,6 +7865,7 @@ export default class Meeting extends StatelessWebexPlugin {
7637
7865
  this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
7638
7866
  'unknown',
7639
7867
  ...reachabilityMetrics,
7868
+ isSubnetReachable,
7640
7869
  ...iceCandidateErrors,
7641
7870
  iceCandidatesCount: this.iceCandidatesCount,
7642
7871
  });
@@ -8681,6 +8910,9 @@ export default class Meeting extends StatelessWebexPlugin {
8681
8910
  LoggerProxy.logger.log(
8682
8911
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8683
8912
  );
8913
+
8914
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8915
+
8684
8916
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8685
8917
  correlationId: this.correlationId,
8686
8918
  muted,
@@ -8689,8 +8921,9 @@ export default class Meeting extends StatelessWebexPlugin {
8689
8921
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8690
8922
  // all we're doing here is adding metrics.
8691
8923
  // eslint-disable-next-line dot-notation
8692
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8924
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8693
8925
  isMultistream: this.isMultistream,
8926
+ frameRate: shareVideoStreamSettings?.frameRate,
8694
8927
  });
8695
8928
  };
8696
8929
 
@@ -9017,6 +9250,23 @@ export default class Meeting extends StatelessWebexPlugin {
9017
9250
  });
9018
9251
  }
9019
9252
 
9253
+ /**
9254
+ * Method to set post meeting data consent.
9255
+ *
9256
+ * @param {boolean} accept - whether consent accepted or declined
9257
+ * @returns {Promise}
9258
+ * @public
9259
+ * @memberof Meeting
9260
+ */
9261
+ public setPostMeetingDataConsent(accept: boolean) {
9262
+ return this.meetingRequest.setPostMeetingDataConsent({
9263
+ postMeetingDataConsent: accept,
9264
+ locusUrl: this.locusUrl,
9265
+ deviceUrl: this.deviceUrl,
9266
+ selfId: this.members.selfId,
9267
+ });
9268
+ }
9269
+
9020
9270
  /**
9021
9271
  * Throws if we don't have a media connection created
9022
9272
  *
@@ -9257,6 +9507,8 @@ export default class Meeting extends StatelessWebexPlugin {
9257
9507
 
9258
9508
  if (floorRequestNeeded) {
9259
9509
  this.localShareInstanceId = uuid.v4();
9510
+ this.shareCAEventSentStatus.transmitStart = false;
9511
+ this.shareCAEventSentStatus.transmitStop = false;
9260
9512
 
9261
9513
  // @ts-ignore
9262
9514
  this.webex.internal.newMetrics.submitClientEvent({