@webex/plugin-meetings 3.8.0-next.6 → 3.8.0-next.61

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 (156) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/webex-errors.js +12 -2
  4. package/dist/common/errors/webex-errors.js.map +1 -1
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +17 -121
  8. package/dist/constants.js.map +1 -1
  9. package/dist/controls-options-manager/enums.js +2 -0
  10. package/dist/controls-options-manager/enums.js.map +1 -1
  11. package/dist/controls-options-manager/types.js.map +1 -1
  12. package/dist/controls-options-manager/util.js +52 -0
  13. package/dist/controls-options-manager/util.js.map +1 -1
  14. package/dist/interpretation/index.js +1 -1
  15. package/dist/interpretation/siLanguage.js +1 -1
  16. package/dist/locus-info/controlsUtils.js +28 -10
  17. package/dist/locus-info/controlsUtils.js.map +1 -1
  18. package/dist/locus-info/index.js +20 -1
  19. package/dist/locus-info/index.js.map +1 -1
  20. package/dist/locus-info/selfUtils.js +405 -418
  21. package/dist/locus-info/selfUtils.js.map +1 -1
  22. package/dist/media/index.js +8 -16
  23. package/dist/media/index.js.map +1 -1
  24. package/dist/meeting/in-meeting-actions.js +17 -1
  25. package/dist/meeting/in-meeting-actions.js.map +1 -1
  26. package/dist/meeting/index.js +556 -288
  27. package/dist/meeting/index.js.map +1 -1
  28. package/dist/meeting/locusMediaRequest.js +0 -17
  29. package/dist/meeting/locusMediaRequest.js.map +1 -1
  30. package/dist/meeting/muteState.js +0 -2
  31. package/dist/meeting/muteState.js.map +1 -1
  32. package/dist/meeting/request.js +30 -0
  33. package/dist/meeting/request.js.map +1 -1
  34. package/dist/meeting/request.type.js.map +1 -1
  35. package/dist/meeting/util.js +13 -2
  36. package/dist/meeting/util.js.map +1 -1
  37. package/dist/meeting-info/meeting-info-v2.js +359 -60
  38. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  39. package/dist/meetings/index.js +91 -1
  40. package/dist/meetings/index.js.map +1 -1
  41. package/dist/meetings/util.js +14 -0
  42. package/dist/meetings/util.js.map +1 -1
  43. package/dist/member/index.js +10 -0
  44. package/dist/member/index.js.map +1 -1
  45. package/dist/member/util.js +3 -0
  46. package/dist/member/util.js.map +1 -1
  47. package/dist/members/index.js +23 -0
  48. package/dist/members/index.js.map +1 -1
  49. package/dist/members/request.js +21 -0
  50. package/dist/members/request.js.map +1 -1
  51. package/dist/members/util.js +15 -0
  52. package/dist/members/util.js.map +1 -1
  53. package/dist/metrics/constants.js +9 -0
  54. package/dist/metrics/constants.js.map +1 -1
  55. package/dist/reachability/clusterReachability.js +63 -27
  56. package/dist/reachability/clusterReachability.js.map +1 -1
  57. package/dist/reachability/index.js +112 -47
  58. package/dist/reachability/index.js.map +1 -1
  59. package/dist/reachability/reachability.types.js +14 -0
  60. package/dist/reachability/reachability.types.js.map +1 -1
  61. package/dist/reachability/request.js +19 -3
  62. package/dist/reachability/request.js.map +1 -1
  63. package/dist/reconnection-manager/index.js +2 -2
  64. package/dist/reconnection-manager/index.js.map +1 -1
  65. package/dist/recording-controller/util.js +5 -5
  66. package/dist/recording-controller/util.js.map +1 -1
  67. package/dist/roap/index.js.map +1 -1
  68. package/dist/roap/turnDiscovery.js +45 -27
  69. package/dist/roap/turnDiscovery.js.map +1 -1
  70. package/dist/roap/types.js +17 -0
  71. package/dist/roap/types.js.map +1 -0
  72. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  73. package/dist/types/config.d.ts +1 -0
  74. package/dist/types/constants.d.ts +12 -85
  75. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  76. package/dist/types/controls-options-manager/types.d.ts +7 -1
  77. package/dist/types/locus-info/index.d.ts +1 -0
  78. package/dist/types/locus-info/selfUtils.d.ts +247 -1
  79. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  80. package/dist/types/meeting/index.d.ts +54 -1
  81. package/dist/types/meeting/muteState.d.ts +0 -1
  82. package/dist/types/meeting/request.d.ts +12 -1
  83. package/dist/types/meeting/request.type.d.ts +6 -0
  84. package/dist/types/meeting/util.d.ts +3 -1
  85. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  86. package/dist/types/meetings/index.d.ts +38 -0
  87. package/dist/types/member/index.d.ts +1 -0
  88. package/dist/types/members/index.d.ts +8 -0
  89. package/dist/types/members/request.d.ts +19 -0
  90. package/dist/types/members/util.d.ts +13 -0
  91. package/dist/types/metrics/constants.d.ts +9 -0
  92. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  93. package/dist/types/reachability/index.d.ts +10 -1
  94. package/dist/types/reachability/reachability.types.d.ts +5 -0
  95. package/dist/types/roap/index.d.ts +3 -2
  96. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  97. package/dist/types/roap/types.d.ts +16 -0
  98. package/dist/webinar/index.js +1 -1
  99. package/package.json +23 -23
  100. package/src/common/errors/webex-errors.ts +8 -1
  101. package/src/config.ts +1 -0
  102. package/src/constants.ts +19 -90
  103. package/src/controls-options-manager/enums.ts +2 -0
  104. package/src/controls-options-manager/types.ts +11 -1
  105. package/src/controls-options-manager/util.ts +62 -0
  106. package/src/locus-info/controlsUtils.ts +44 -14
  107. package/src/locus-info/index.ts +23 -1
  108. package/src/locus-info/selfUtils.ts +451 -447
  109. package/src/media/index.ts +11 -21
  110. package/src/meeting/in-meeting-actions.ts +32 -0
  111. package/src/meeting/index.ts +372 -92
  112. package/src/meeting/locusMediaRequest.ts +0 -18
  113. package/src/meeting/muteState.ts +0 -2
  114. package/src/meeting/request.ts +36 -1
  115. package/src/meeting/request.type.ts +7 -0
  116. package/src/meeting/util.ts +11 -2
  117. package/src/meeting-info/meeting-info-v2.ts +247 -6
  118. package/src/meetings/index.ts +107 -1
  119. package/src/meetings/util.ts +18 -0
  120. package/src/member/index.ts +11 -0
  121. package/src/member/util.ts +3 -0
  122. package/src/members/index.ts +25 -0
  123. package/src/members/request.ts +26 -0
  124. package/src/members/util.ts +16 -0
  125. package/src/metrics/constants.ts +9 -0
  126. package/src/reachability/clusterReachability.ts +73 -26
  127. package/src/reachability/index.ts +70 -1
  128. package/src/reachability/reachability.types.ts +6 -0
  129. package/src/reachability/request.ts +7 -0
  130. package/src/reconnection-manager/index.ts +2 -2
  131. package/src/recording-controller/util.ts +17 -13
  132. package/src/roap/index.ts +3 -7
  133. package/src/roap/turnDiscovery.ts +34 -39
  134. package/src/roap/types.ts +23 -0
  135. package/test/unit/spec/controls-options-manager/util.js +120 -0
  136. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  137. package/test/unit/spec/locus-info/index.js +28 -0
  138. package/test/unit/spec/media/index.ts +36 -16
  139. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  140. package/test/unit/spec/meeting/index.js +528 -34
  141. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  142. package/test/unit/spec/meeting/muteState.js +0 -2
  143. package/test/unit/spec/meeting/request.js +32 -1
  144. package/test/unit/spec/meeting/utils.js +119 -18
  145. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  146. package/test/unit/spec/meetings/index.js +120 -2
  147. package/test/unit/spec/member/index.js +7 -0
  148. package/test/unit/spec/member/util.js +24 -0
  149. package/test/unit/spec/members/index.js +103 -26
  150. package/test/unit/spec/members/request.js +45 -22
  151. package/test/unit/spec/members/utils.js +33 -0
  152. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  153. package/test/unit/spec/reachability/index.ts +101 -0
  154. package/test/unit/spec/reachability/request.js +47 -2
  155. package/test/unit/spec/reconnection-manager/index.js +4 -4
  156. 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';
@@ -103,7 +100,6 @@ import {
103
100
  MEETING_STATE_MACHINE,
104
101
  MEETING_STATE,
105
102
  MEETINGS,
106
- MQA_STATS,
107
103
  NETWORK_STATUS,
108
104
  ONLINE,
109
105
  OFFLINE,
@@ -167,6 +163,7 @@ import Member from '../member';
167
163
  import {BrbState, createBrbState} from './brbState';
168
164
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
169
165
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
166
+ import {ReachabilityMetrics} from '../reachability/reachability.types';
170
167
 
171
168
  // default callback so we don't call an undefined function, but in practice it should never be used
172
169
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -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 = {
@@ -263,6 +262,8 @@ type FetchMeetingInfoParams = {
263
262
  sendCAevents?: boolean;
264
263
  };
265
264
 
265
+ type MediaReachabilityMetrics = ReachabilityMetrics & {isSubnetReachable: boolean};
266
+
266
267
  /**
267
268
  * MediaDirection
268
269
  * @typedef {Object} MediaDirection
@@ -648,6 +649,13 @@ export default class Meeting extends StatelessWebexPlugin {
648
649
  allowMediaInLobby: boolean;
649
650
  localShareInstanceId: string;
650
651
  remoteShareInstanceId: string;
652
+ shareCAEventSentStatus: {
653
+ transmitStart: boolean;
654
+ transmitStop: boolean;
655
+ receiveStart: boolean;
656
+ receiveStop: boolean;
657
+ };
658
+
651
659
  turnDiscoverySkippedReason: TurnDiscoverySkipReason;
652
660
  turnServerUsed: boolean;
653
661
  areVoiceaEventsSetup = false;
@@ -716,6 +724,7 @@ export default class Meeting extends StatelessWebexPlugin {
716
724
  private rtcMetrics?: RtcMetrics;
717
725
  private uploadLogsTimer?: ReturnType<typeof setTimeout>;
718
726
  private logUploadIntervalIndex: number;
727
+ private mediaServerIp: string;
719
728
 
720
729
  /**
721
730
  * @param {Object} attrs
@@ -1420,6 +1429,19 @@ export default class Meeting extends StatelessWebexPlugin {
1420
1429
  */
1421
1430
  this.remoteShareInstanceId = null;
1422
1431
 
1432
+ /**
1433
+ * Status used for ensuring we do not oversend metrics
1434
+ * @instance
1435
+ * @private
1436
+ * @memberof Meeting
1437
+ */
1438
+ this.shareCAEventSentStatus = {
1439
+ transmitStart: false,
1440
+ transmitStop: false,
1441
+ receiveStart: false,
1442
+ receiveStop: false,
1443
+ };
1444
+
1423
1445
  /**
1424
1446
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1425
1447
  * @instance
@@ -1579,6 +1601,19 @@ export default class Meeting extends StatelessWebexPlugin {
1579
1601
  * @memberof Meeting
1580
1602
  */
1581
1603
  this.#isoLocalClientMeetingJoinTime = undefined;
1604
+
1605
+ // We clear the error cache of CA events on every new meeting instance
1606
+ // @ts-ignore - Fix type
1607
+ this.webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache();
1608
+
1609
+ /**
1610
+ * IP Address of the remote media server
1611
+ * @instance
1612
+ * @type {string}
1613
+ * @private
1614
+ * @memberof Meeting
1615
+ */
1616
+ this.mediaServerIp = undefined;
1582
1617
  }
1583
1618
 
1584
1619
  /**
@@ -1627,6 +1662,38 @@ export default class Meeting extends StatelessWebexPlugin {
1627
1662
  this.callStateForMetrics.correlationId = correlationId;
1628
1663
  }
1629
1664
 
1665
+ /**
1666
+ * Getter - Returns callStateForMetrics.userNameInput
1667
+ * @returns {string}
1668
+ */
1669
+ get userNameInput() {
1670
+ return this.callStateForMetrics?.userNameInput;
1671
+ }
1672
+
1673
+ /**
1674
+ * Setter - sets callStateForMetrics.userNameInput
1675
+ * @param {string} userNameInput
1676
+ */
1677
+ set userNameInput(userNameInput: string) {
1678
+ this.callStateForMetrics.userNameInput = userNameInput;
1679
+ }
1680
+
1681
+ /**
1682
+ * Getter - Returns callStateForMetrics.emailInput
1683
+ * @returns {string}
1684
+ */
1685
+ get emailInput() {
1686
+ return this.callStateForMetrics?.emailInput;
1687
+ }
1688
+
1689
+ /**
1690
+ * Setter - sets callStateForMetrics.emailInput
1691
+ * @param {string} emailInput
1692
+ */
1693
+ set emailInput(emailInput: string) {
1694
+ this.callStateForMetrics.emailInput = emailInput;
1695
+ }
1696
+
1630
1697
  /**
1631
1698
  * Getter - Returns callStateForMetrics.sessionCorrelationId
1632
1699
  * @returns {string}
@@ -1652,6 +1719,33 @@ export default class Meeting extends StatelessWebexPlugin {
1652
1719
  return this.#isoLocalClientMeetingJoinTime;
1653
1720
  }
1654
1721
 
1722
+ /**
1723
+ * Setter - sets isoLocalClientMeetingJoinTime
1724
+ * This will be set once on meeting join, and not updated again
1725
+ * this will always produce an ISO string
1726
+ * If the iso string is invalid, it will fallback to the current system time
1727
+ * @param {string | undefined} time
1728
+ */
1729
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1730
+ const fallback = new Date().toISOString();
1731
+ if (!time) {
1732
+ this.#isoLocalClientMeetingJoinTime = fallback;
1733
+ } else {
1734
+ const date = new Date(time);
1735
+
1736
+ // Check if the date is valid
1737
+ if (Number.isNaN(date.getTime())) {
1738
+ LoggerProxy.logger.info(
1739
+ // @ts-ignore
1740
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1741
+ );
1742
+ this.#isoLocalClientMeetingJoinTime = fallback;
1743
+ } else {
1744
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1745
+ }
1746
+ }
1747
+ }
1748
+
1655
1749
  /**
1656
1750
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1657
1751
  * @param {any} info
@@ -2793,6 +2887,24 @@ export default class Meeting extends StatelessWebexPlugin {
2793
2887
  {state}
2794
2888
  );
2795
2889
  });
2890
+
2891
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2892
+ Trigger.trigger(
2893
+ this,
2894
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2895
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
2896
+ {state}
2897
+ );
2898
+ });
2899
+
2900
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED, ({state}) => {
2901
+ Trigger.trigger(
2902
+ this,
2903
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2904
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
2905
+ {state}
2906
+ );
2907
+ });
2796
2908
  }
2797
2909
 
2798
2910
  /**
@@ -2965,6 +3077,8 @@ export default class Meeting extends StatelessWebexPlugin {
2965
3077
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
2966
3078
  const sendStartedSharingRemote = () => {
2967
3079
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3080
+ this.shareCAEventSentStatus.receiveStart = false;
3081
+ this.shareCAEventSentStatus.receiveStop = false;
2968
3082
 
2969
3083
  Trigger.trigger(
2970
3084
  this,
@@ -3018,6 +3132,7 @@ export default class Meeting extends StatelessWebexPlugin {
3018
3132
  },
3019
3133
  options: {meetingId: this.id},
3020
3134
  });
3135
+
3021
3136
  break;
3022
3137
 
3023
3138
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3058,6 +3173,8 @@ export default class Meeting extends StatelessWebexPlugin {
3058
3173
  // if we got here, then some remote participant has stolen
3059
3174
  // the presentation from another remote participant
3060
3175
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3176
+ this.shareCAEventSentStatus.receiveStart = false;
3177
+ this.shareCAEventSentStatus.receiveStop = false;
3061
3178
 
3062
3179
  Trigger.trigger(
3063
3180
  this,
@@ -3741,7 +3858,13 @@ export default class Meeting extends StatelessWebexPlugin {
3741
3858
  return Promise.reject(error);
3742
3859
  }
3743
3860
 
3744
- return this.brbState.enable(enabled, this.sendSlotManager);
3861
+ return this.brbState.enable(enabled, this.sendSlotManager).then(() => {
3862
+ if (this.audio && enabled) {
3863
+ // locus mutes the participant with brb enabled request,
3864
+ // so we need to explicitly update remote mute for correct logic flow
3865
+ this.audio.handleServerRemoteMuteUpdate(this, enabled);
3866
+ }
3867
+ });
3745
3868
  }
3746
3869
 
3747
3870
  /**
@@ -3933,7 +4056,10 @@ export default class Meeting extends StatelessWebexPlugin {
3933
4056
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3934
4057
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3935
4058
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3936
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4059
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4060
+ this.userDisplayHints,
4061
+ this.selfUserPolicies
4062
+ ),
3937
4063
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3938
4064
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
3939
4065
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -3982,6 +4108,9 @@ export default class Meeting extends StatelessWebexPlugin {
3982
4108
  this.inMeetingActions.canSendReactions,
3983
4109
  this.userDisplayHints
3984
4110
  ),
4111
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4112
+ this.userDisplayHints
4113
+ ),
3985
4114
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
3986
4115
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
3987
4116
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -3997,6 +4126,7 @@ export default class Meeting extends StatelessWebexPlugin {
3997
4126
  this.userDisplayHints
3998
4127
  ),
3999
4128
  canUserRenameOthers: MeetingUtil.canUserRenameOthers(this.userDisplayHints),
4129
+ canMoveToLobby: MeetingUtil.canMoveToLobby(this.userDisplayHints),
4000
4130
  canMuteAll: ControlsOptionsUtil.hasHints({
4001
4131
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
4002
4132
  displayHints: this.userDisplayHints,
@@ -4131,6 +4261,14 @@ export default class Meeting extends StatelessWebexPlugin {
4131
4261
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
4132
4262
  policies: this.selfUserPolicies,
4133
4263
  }),
4264
+ canRealtimeCloseCaption: ControlsOptionsUtil.hasPolicies({
4265
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
4266
+ policies: this.selfUserPolicies,
4267
+ }),
4268
+ canRealtimeCloseCaptionManual: ControlsOptionsUtil.hasPolicies({
4269
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
4270
+ policies: this.selfUserPolicies,
4271
+ }),
4134
4272
  canChat: ControlsOptionsUtil.hasPolicies({
4135
4273
  requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
4136
4274
  policies: this.selfUserPolicies,
@@ -4177,6 +4315,22 @@ export default class Meeting extends StatelessWebexPlugin {
4177
4315
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4178
4316
  policies: this.selfUserPolicies,
4179
4317
  }),
4318
+ canEnableAnnotation: ControlsOptionsUtil.hasHints({
4319
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
4320
+ displayHints: this.userDisplayHints,
4321
+ }),
4322
+ canDisableAnnotation: ControlsOptionsUtil.hasHints({
4323
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
4324
+ displayHints: this.userDisplayHints,
4325
+ }),
4326
+ canEnableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4327
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
4328
+ displayHints: this.userDisplayHints,
4329
+ }),
4330
+ canDisableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4331
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
4332
+ displayHints: this.userDisplayHints,
4333
+ }),
4180
4334
  }) || changed;
4181
4335
  }
4182
4336
  if (changed) {
@@ -5684,8 +5838,6 @@ export default class Meeting extends StatelessWebexPlugin {
5684
5838
  // @ts-ignore
5685
5839
  this.webex.internal.device.meetingStarted();
5686
5840
 
5687
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5688
-
5689
5841
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5690
5842
 
5691
5843
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6146,10 +6298,7 @@ export default class Meeting extends StatelessWebexPlugin {
6146
6298
  },
6147
6299
  options: {meetingId: this.id, rawError: error},
6148
6300
  });
6149
- } else if (
6150
- error instanceof Errors.SdpOfferHandlingError ||
6151
- error instanceof Errors.SdpAnswerHandlingError
6152
- ) {
6301
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6153
6302
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6154
6303
 
6155
6304
  // @ts-ignore
@@ -6160,6 +6309,24 @@ export default class Meeting extends StatelessWebexPlugin {
6160
6309
  },
6161
6310
  options: {meetingId: this.id, rawError: error},
6162
6311
  });
6312
+ } else if (error instanceof Errors.SdpAnswerHandlingError) {
6313
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6314
+
6315
+ // @ts-ignore
6316
+ this.webex.internal.newMetrics.submitClientEvent({
6317
+ name: 'client.media-engine.remote-sdp-received',
6318
+ payload: {
6319
+ canProceed: false,
6320
+ },
6321
+ options: {meetingId: this.id, rawError: error},
6322
+ });
6323
+
6324
+ if (this.deferSDPAnswer) {
6325
+ clearTimeout(this.sdpResponseTimer);
6326
+ this.sdpResponseTimer = undefined;
6327
+
6328
+ this.deferSDPAnswer.reject();
6329
+ }
6163
6330
  } else if (error instanceof Errors.SdpError) {
6164
6331
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6165
6332
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -6187,6 +6354,11 @@ export default class Meeting extends StatelessWebexPlugin {
6187
6354
  ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6188
6355
  : undefined;
6189
6356
 
6357
+ const mediaServerIp =
6358
+ roapMessage.messageType === 'ANSWER'
6359
+ ? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6360
+ : undefined;
6361
+
6190
6362
  if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6191
6363
  throw new MultistreamNotSupportedError(
6192
6364
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6197,6 +6369,10 @@ export default class Meeting extends StatelessWebexPlugin {
6197
6369
  if (mediaServer) {
6198
6370
  this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
6199
6371
  }
6372
+
6373
+ if (this.isMultistream && mediaServerIp) {
6374
+ this.mediaServerIp = mediaServerIp;
6375
+ }
6200
6376
  };
6201
6377
 
6202
6378
  /**
@@ -6654,20 +6830,20 @@ export default class Meeting extends StatelessWebexPlugin {
6654
6830
  * @memberof Meetings
6655
6831
  */
6656
6832
  setupStatsAnalyzerEventHandlers = () => {
6657
- this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (options) => {
6658
- // TODO: might have to send the same event to the developer
6659
- // Add ip address info if geo hint is present
6660
- // @ts-ignore fix type
6661
- options.data.intervalMetadata.peerReflexiveIP =
6662
- // @ts-ignore
6663
- this.webex.meetings.geoHintInfo?.clientAddress ||
6664
- options.data.intervalMetadata.peerReflexiveIP ||
6665
- MQA_STATS.DEFAULT_IP;
6833
+ this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (event) => {
6834
+ // Add IP address from geoHintInfo if missing.
6835
+ if (event.data.intervalMetadata.maskedPeerReflexiveIP === '0.0.0.0') {
6836
+ // @ts-ignore fix type
6837
+ const clientAddressFromGeoHint = this.webex.meetings.geoHintInfo?.clientAddress;
6838
+ if (clientAddressFromGeoHint) {
6839
+ event.data.intervalMetadata.maskedPeerReflexiveIP =
6840
+ CallDiagnosticUtils.anonymizeIPAddress(clientAddressFromGeoHint);
6841
+ }
6842
+ }
6666
6843
 
6844
+ // Count members that are in the meeting.
6667
6845
  const {members} = this.getMembers().membersCollection;
6668
-
6669
- // Count members that are in the meeting
6670
- options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6846
+ event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6671
6847
  (member: Member) => member.isInMeeting
6672
6848
  ).length;
6673
6849
 
@@ -6676,10 +6852,10 @@ export default class Meeting extends StatelessWebexPlugin {
6676
6852
  name: 'client.mediaquality.event',
6677
6853
  options: {
6678
6854
  meetingId: this.id,
6679
- networkType: options.data.networkType,
6855
+ networkType: this.statsAnalyzer.getNetworkType(),
6680
6856
  },
6681
6857
  payload: {
6682
- intervals: [options.data],
6858
+ intervals: [event.data],
6683
6859
  },
6684
6860
  });
6685
6861
  });
@@ -6694,30 +6870,42 @@ export default class Meeting extends StatelessWebexPlugin {
6694
6870
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6695
6871
  data
6696
6872
  );
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
- });
6873
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStart) {
6874
+ // @ts-ignore
6875
+ this.webex.internal.newMetrics.submitClientEvent({
6876
+ name: 'client.media.tx.start',
6877
+ payload: {
6878
+ mediaType: data.mediaType,
6879
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6880
+ },
6881
+ options: {
6882
+ meetingId: this.id,
6883
+ },
6884
+ });
6885
+
6886
+ if (data.mediaType === 'share') {
6887
+ this.shareCAEventSentStatus.transmitStart = true;
6888
+ }
6889
+ }
6708
6890
  });
6709
6891
  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
- });
6892
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStop) {
6893
+ // @ts-ignore
6894
+ this.webex.internal.newMetrics.submitClientEvent({
6895
+ name: 'client.media.tx.stop',
6896
+ payload: {
6897
+ mediaType: data.mediaType,
6898
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6899
+ },
6900
+ options: {
6901
+ meetingId: this.id,
6902
+ },
6903
+ });
6904
+
6905
+ if (data.mediaType === 'share') {
6906
+ this.shareCAEventSentStatus.transmitStop = true;
6907
+ }
6908
+ }
6721
6909
  });
6722
6910
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6723
6911
  Trigger.trigger(
@@ -6729,57 +6917,65 @@ export default class Meeting extends StatelessWebexPlugin {
6729
6917
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6730
6918
  data
6731
6919
  );
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') {
6920
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6745
6921
  // @ts-ignore
6746
6922
  this.webex.internal.newMetrics.submitClientEvent({
6747
- name: 'client.media.render.start',
6923
+ name: 'client.media.rx.start',
6748
6924
  payload: {
6749
- mediaType: 'share',
6750
- shareInstanceId: this.remoteShareInstanceId,
6925
+ mediaType: data.mediaType,
6926
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6751
6927
  },
6752
6928
  options: {
6753
6929
  meetingId: this.id,
6754
6930
  },
6755
6931
  });
6932
+
6933
+ if (data.mediaType === 'share') {
6934
+ // @ts-ignore
6935
+ this.webex.internal.newMetrics.submitClientEvent({
6936
+ name: 'client.media.render.start',
6937
+ payload: {
6938
+ mediaType: 'share',
6939
+ shareInstanceId: this.remoteShareInstanceId,
6940
+ },
6941
+ options: {
6942
+ meetingId: this.id,
6943
+ },
6944
+ });
6945
+
6946
+ this.shareCAEventSentStatus.receiveStart = true;
6947
+ }
6756
6948
  }
6757
6949
  });
6758
6950
  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') {
6951
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6772
6952
  // @ts-ignore
6773
6953
  this.webex.internal.newMetrics.submitClientEvent({
6774
- name: 'client.media.render.stop',
6954
+ name: 'client.media.rx.stop',
6775
6955
  payload: {
6776
- mediaType: 'share',
6777
- shareInstanceId: this.remoteShareInstanceId,
6956
+ mediaType: data.mediaType,
6957
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6778
6958
  },
6779
6959
  options: {
6780
6960
  meetingId: this.id,
6781
6961
  },
6782
6962
  });
6963
+
6964
+ if (data.mediaType === 'share') {
6965
+ // @ts-ignore
6966
+ this.webex.internal.newMetrics.submitClientEvent({
6967
+ name: 'client.media.render.stop',
6968
+ payload: {
6969
+ mediaType: 'share',
6970
+ shareInstanceId: this.remoteShareInstanceId,
6971
+ },
6972
+ options: {
6973
+ meetingId: this.id,
6974
+ },
6975
+ });
6976
+
6977
+ this.shareCAEventSentStatus.receiveStop = true;
6978
+ }
6783
6979
  }
6784
6980
  });
6785
6981
  };
@@ -6796,7 +6992,10 @@ export default class Meeting extends StatelessWebexPlugin {
6796
6992
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6797
6993
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6798
6994
  */
6799
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6995
+ private async createMediaConnection(
6996
+ turnServerInfo?: TurnServerInfo,
6997
+ bundlePolicy?: BundlePolicy
6998
+ ) {
6800
6999
  this.rtcMetrics = this.isMultistream
6801
7000
  ? // @ts-ignore
6802
7001
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6821,6 +7020,8 @@ export default class Meeting extends StatelessWebexPlugin {
6821
7020
  bundlePolicy,
6822
7021
  // @ts-ignore - config coming from registerPlugin
6823
7022
  iceCandidatesTimeout: this.config.iceCandidatesGatheringTimeout,
7023
+ // @ts-ignore - config coming from registerPlugin
7024
+ disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
6824
7025
  }
6825
7026
  );
6826
7027
 
@@ -6971,12 +7172,18 @@ export default class Meeting extends StatelessWebexPlugin {
6971
7172
  },
6972
7173
  options: {
6973
7174
  meetingId: this.id,
7175
+ rawError: error,
6974
7176
  },
6975
7177
  });
6976
7178
  }
6977
- throw new Error(
7179
+
7180
+ const timedOutError = new Error(
6978
7181
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
6979
7182
  );
7183
+
7184
+ timedOutError.cause = error;
7185
+
7186
+ throw timedOutError;
6980
7187
  }
6981
7188
  }
6982
7189
 
@@ -6997,6 +7204,12 @@ export default class Meeting extends StatelessWebexPlugin {
6997
7204
  networkQualityMonitor: this.networkQualityMonitor,
6998
7205
  isMultistream: this.isMultistream,
6999
7206
  });
7207
+ this.shareCAEventSentStatus = {
7208
+ transmitStart: false,
7209
+ transmitStop: false,
7210
+ receiveStart: false,
7211
+ receiveStop: false,
7212
+ };
7000
7213
  this.setupStatsAnalyzerEventHandlers();
7001
7214
  this.networkQualityMonitor.on(
7002
7215
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -7031,6 +7244,9 @@ export default class Meeting extends StatelessWebexPlugin {
7031
7244
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7032
7245
  } seconds`
7033
7246
  );
7247
+
7248
+ const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7249
+
7034
7250
  // @ts-ignore
7035
7251
  this.webex.internal.newMetrics.submitClientEvent({
7036
7252
  name: 'client.media-engine.remote-sdp-received',
@@ -7043,10 +7259,10 @@ export default class Meeting extends StatelessWebexPlugin {
7043
7259
  }),
7044
7260
  ],
7045
7261
  },
7046
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7262
+ options: {meetingId: this.id, rawError: error},
7047
7263
  });
7048
7264
 
7049
- deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
7265
+ deferSDPAnswer.reject(error);
7050
7266
  }, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
7051
7267
 
7052
7268
  LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
@@ -7151,7 +7367,7 @@ export default class Meeting extends StatelessWebexPlugin {
7151
7367
  error
7152
7368
  );
7153
7369
 
7154
- throw new AddMediaFailed();
7370
+ throw new AddMediaFailed(error);
7155
7371
  }
7156
7372
  }
7157
7373
 
@@ -7566,10 +7782,11 @@ export default class Meeting extends StatelessWebexPlugin {
7566
7782
 
7567
7783
  const {connectionType, selectedCandidatePairChanges, numTransports} =
7568
7784
  await this.mediaProperties.getCurrentConnectionInfo();
7569
- // @ts-ignore
7570
- const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7785
+
7571
7786
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7572
7787
 
7788
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7789
+
7573
7790
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7574
7791
  correlation_id: this.correlationId,
7575
7792
  locus_id: this.locusUrl.split('/').pop(),
@@ -7579,7 +7796,7 @@ export default class Meeting extends StatelessWebexPlugin {
7579
7796
  isMultistream: this.isMultistream,
7580
7797
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7581
7798
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7582
- ...reachabilityStats,
7799
+ ...reachabilityMetrics,
7583
7800
  ...iceCandidateErrors,
7584
7801
  iceCandidatesCount: this.iceCandidatesCount,
7585
7802
  });
@@ -7601,7 +7818,7 @@ export default class Meeting extends StatelessWebexPlugin {
7601
7818
  LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
7602
7819
 
7603
7820
  // @ts-ignore
7604
- const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
7821
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7605
7822
 
7606
7823
  const {selectedCandidatePairChanges, numTransports} =
7607
7824
  await this.mediaProperties.getCurrentConnectionInfo();
@@ -8681,6 +8898,9 @@ export default class Meeting extends StatelessWebexPlugin {
8681
8898
  LoggerProxy.logger.log(
8682
8899
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8683
8900
  );
8901
+
8902
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8903
+
8684
8904
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8685
8905
  correlationId: this.correlationId,
8686
8906
  muted,
@@ -8689,8 +8909,9 @@ export default class Meeting extends StatelessWebexPlugin {
8689
8909
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8690
8910
  // all we're doing here is adding metrics.
8691
8911
  // eslint-disable-next-line dot-notation
8692
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8912
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8693
8913
  isMultistream: this.isMultistream,
8914
+ frameRate: shareVideoStreamSettings?.frameRate,
8694
8915
  });
8695
8916
  };
8696
8917
 
@@ -9017,6 +9238,23 @@ export default class Meeting extends StatelessWebexPlugin {
9017
9238
  });
9018
9239
  }
9019
9240
 
9241
+ /**
9242
+ * Method to set post meeting data consent.
9243
+ *
9244
+ * @param {boolean} accept - whether consent accepted or declined
9245
+ * @returns {Promise}
9246
+ * @public
9247
+ * @memberof Meeting
9248
+ */
9249
+ public setPostMeetingDataConsent(accept: boolean) {
9250
+ return this.meetingRequest.setPostMeetingDataConsent({
9251
+ postMeetingDataConsent: accept,
9252
+ locusUrl: this.locusUrl,
9253
+ deviceUrl: this.deviceUrl,
9254
+ selfId: this.members.selfId,
9255
+ });
9256
+ }
9257
+
9020
9258
  /**
9021
9259
  * Throws if we don't have a media connection created
9022
9260
  *
@@ -9257,6 +9495,8 @@ export default class Meeting extends StatelessWebexPlugin {
9257
9495
 
9258
9496
  if (floorRequestNeeded) {
9259
9497
  this.localShareInstanceId = uuid.v4();
9498
+ this.shareCAEventSentStatus.transmitStart = false;
9499
+ this.shareCAEventSentStatus.transmitStop = false;
9260
9500
 
9261
9501
  // @ts-ignore
9262
9502
  this.webex.internal.newMetrics.submitClientEvent({
@@ -9384,4 +9624,44 @@ export default class Meeting extends StatelessWebexPlugin {
9384
9624
 
9385
9625
  return Promise.resolve();
9386
9626
  }
9627
+
9628
+ /**
9629
+ * Gets the media reachability metrics
9630
+ *
9631
+ * @returns {Promise<MediaReachabilityMetrics>}
9632
+ */
9633
+ private async getMediaReachabilityMetricFields(): Promise<MediaReachabilityMetrics> {
9634
+ const reachabilityMetrics: ReachabilityMetrics =
9635
+ // @ts-ignore
9636
+ await this.webex.meetings.reachability.getReachabilityMetrics();
9637
+
9638
+ const successKeys: Array<keyof ReachabilityMetrics> = [
9639
+ 'reachability_public_udp_success',
9640
+ 'reachability_public_tcp_success',
9641
+ 'reachability_public_xtls_success',
9642
+ 'reachability_vmn_udp_success',
9643
+ 'reachability_vmn_tcp_success',
9644
+ 'reachability_vmn_xtls_success',
9645
+ ];
9646
+
9647
+ const totalSuccessCases = successKeys.reduce((total, key) => {
9648
+ const value = reachabilityMetrics[key];
9649
+ if (typeof value === 'number') {
9650
+ return total + value;
9651
+ }
9652
+
9653
+ return total;
9654
+ }, 0);
9655
+
9656
+ let isSubnetReachable = null;
9657
+ if (totalSuccessCases > 0) {
9658
+ // @ts-ignore
9659
+ isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9660
+ }
9661
+
9662
+ return {
9663
+ ...reachabilityMetrics,
9664
+ isSubnetReachable,
9665
+ };
9666
+ }
9387
9667
  }