@webex/plugin-meetings 3.8.0-next.8 → 3.8.0-next.80

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 (171) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +70 -6
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/common/errors/webex-errors.js +12 -2
  5. package/dist/common/errors/webex-errors.js.map +1 -1
  6. package/dist/config.js +5 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +20 -123
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +2 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/types.js.map +1 -1
  13. package/dist/controls-options-manager/util.js +52 -0
  14. package/dist/controls-options-manager/util.js.map +1 -1
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/controlsUtils.js +28 -10
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/index.js +62 -12
  20. package/dist/locus-info/index.js.map +1 -1
  21. package/dist/locus-info/selfUtils.js +432 -418
  22. package/dist/locus-info/selfUtils.js.map +1 -1
  23. package/dist/media/index.js +17 -17
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/media/properties.js +94 -6
  26. package/dist/media/properties.js.map +1 -1
  27. package/dist/meeting/brbState.js +6 -0
  28. package/dist/meeting/brbState.js.map +1 -1
  29. package/dist/meeting/in-meeting-actions.js +17 -1
  30. package/dist/meeting/in-meeting-actions.js.map +1 -1
  31. package/dist/meeting/index.js +570 -302
  32. package/dist/meeting/index.js.map +1 -1
  33. package/dist/meeting/locusMediaRequest.js +0 -17
  34. package/dist/meeting/locusMediaRequest.js.map +1 -1
  35. package/dist/meeting/muteState.js +0 -2
  36. package/dist/meeting/muteState.js.map +1 -1
  37. package/dist/meeting/request.js +30 -0
  38. package/dist/meeting/request.js.map +1 -1
  39. package/dist/meeting/request.type.js.map +1 -1
  40. package/dist/meeting/util.js +13 -2
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meeting-info/meeting-info-v2.js +373 -68
  43. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  44. package/dist/meeting-info/utilv2.js +5 -1
  45. package/dist/meeting-info/utilv2.js.map +1 -1
  46. package/dist/meetings/index.js +136 -1
  47. package/dist/meetings/index.js.map +1 -1
  48. package/dist/meetings/util.js +14 -0
  49. package/dist/meetings/util.js.map +1 -1
  50. package/dist/member/index.js +10 -0
  51. package/dist/member/index.js.map +1 -1
  52. package/dist/member/util.js +330 -353
  53. package/dist/member/util.js.map +1 -1
  54. package/dist/members/index.js +42 -0
  55. package/dist/members/index.js.map +1 -1
  56. package/dist/members/request.js +38 -0
  57. package/dist/members/request.js.map +1 -1
  58. package/dist/members/util.js +36 -1
  59. package/dist/members/util.js.map +1 -1
  60. package/dist/metrics/constants.js +9 -0
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/reachability/clusterReachability.js +63 -27
  63. package/dist/reachability/clusterReachability.js.map +1 -1
  64. package/dist/reachability/index.js +112 -47
  65. package/dist/reachability/index.js.map +1 -1
  66. package/dist/reachability/reachability.types.js +14 -0
  67. package/dist/reachability/reachability.types.js.map +1 -1
  68. package/dist/reachability/request.js +19 -3
  69. package/dist/reachability/request.js.map +1 -1
  70. package/dist/reconnection-manager/index.js +2 -2
  71. package/dist/reconnection-manager/index.js.map +1 -1
  72. package/dist/roap/index.js.map +1 -1
  73. package/dist/roap/turnDiscovery.js +45 -27
  74. package/dist/roap/turnDiscovery.js.map +1 -1
  75. package/dist/roap/types.js +17 -0
  76. package/dist/roap/types.js.map +1 -0
  77. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  78. package/dist/types/config.d.ts +3 -0
  79. package/dist/types/constants.d.ts +13 -85
  80. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  81. package/dist/types/controls-options-manager/types.d.ts +7 -1
  82. package/dist/types/locus-info/index.d.ts +3 -3
  83. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  84. package/dist/types/media/properties.d.ts +15 -0
  85. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  86. package/dist/types/meeting/index.d.ts +43 -1
  87. package/dist/types/meeting/muteState.d.ts +0 -1
  88. package/dist/types/meeting/request.d.ts +12 -1
  89. package/dist/types/meeting/request.type.d.ts +6 -0
  90. package/dist/types/meeting/util.d.ts +3 -1
  91. package/dist/types/meeting-info/meeting-info-v2.d.ts +82 -1
  92. package/dist/types/meetings/index.d.ts +57 -0
  93. package/dist/types/member/index.d.ts +1 -0
  94. package/dist/types/member/util.d.ts +159 -1
  95. package/dist/types/members/index.d.ts +15 -0
  96. package/dist/types/members/request.d.ts +26 -0
  97. package/dist/types/members/util.d.ts +27 -0
  98. package/dist/types/metrics/constants.d.ts +9 -0
  99. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  100. package/dist/types/reachability/index.d.ts +10 -1
  101. package/dist/types/reachability/reachability.types.d.ts +5 -0
  102. package/dist/types/roap/index.d.ts +3 -2
  103. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  104. package/dist/types/roap/types.d.ts +16 -0
  105. package/dist/webinar/index.js +1 -1
  106. package/package.json +24 -23
  107. package/src/breakouts/index.ts +69 -0
  108. package/src/common/errors/webex-errors.ts +8 -1
  109. package/src/config.ts +3 -0
  110. package/src/constants.ts +20 -90
  111. package/src/controls-options-manager/enums.ts +2 -0
  112. package/src/controls-options-manager/types.ts +11 -1
  113. package/src/controls-options-manager/util.ts +62 -0
  114. package/src/locus-info/controlsUtils.ts +44 -14
  115. package/src/locus-info/index.ts +56 -13
  116. package/src/locus-info/selfUtils.ts +496 -442
  117. package/src/media/index.ts +23 -21
  118. package/src/media/properties.ts +96 -0
  119. package/src/meeting/brbState.ts +7 -0
  120. package/src/meeting/in-meeting-actions.ts +32 -0
  121. package/src/meeting/index.ts +382 -93
  122. package/src/meeting/locusMediaRequest.ts +0 -18
  123. package/src/meeting/muteState.ts +0 -2
  124. package/src/meeting/request.ts +36 -1
  125. package/src/meeting/request.type.ts +7 -0
  126. package/src/meeting/util.ts +11 -2
  127. package/src/meeting-info/meeting-info-v2.ts +254 -8
  128. package/src/meeting-info/utilv2.ts +5 -0
  129. package/src/meetings/index.ts +148 -1
  130. package/src/meetings/util.ts +18 -0
  131. package/src/member/index.ts +13 -2
  132. package/src/member/util.ts +351 -348
  133. package/src/members/index.ts +47 -0
  134. package/src/members/request.ts +44 -0
  135. package/src/members/util.ts +43 -1
  136. package/src/metrics/constants.ts +9 -0
  137. package/src/reachability/clusterReachability.ts +73 -26
  138. package/src/reachability/index.ts +70 -1
  139. package/src/reachability/reachability.types.ts +6 -0
  140. package/src/reachability/request.ts +7 -0
  141. package/src/reconnection-manager/index.ts +2 -2
  142. package/src/roap/index.ts +3 -7
  143. package/src/roap/turnDiscovery.ts +34 -39
  144. package/src/roap/types.ts +23 -0
  145. package/test/unit/spec/breakouts/index.ts +167 -95
  146. package/test/unit/spec/controls-options-manager/util.js +120 -0
  147. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  148. package/test/unit/spec/locus-info/index.js +167 -73
  149. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  150. package/test/unit/spec/media/index.ts +150 -18
  151. package/test/unit/spec/media/properties.ts +130 -0
  152. package/test/unit/spec/meeting/brbState.ts +19 -0
  153. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  154. package/test/unit/spec/meeting/index.js +557 -35
  155. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  156. package/test/unit/spec/meeting/muteState.js +0 -2
  157. package/test/unit/spec/meeting/request.js +32 -1
  158. package/test/unit/spec/meeting/utils.js +119 -18
  159. package/test/unit/spec/meeting-info/meetinginfov2.js +484 -114
  160. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  161. package/test/unit/spec/meetings/index.js +146 -2
  162. package/test/unit/spec/member/index.js +7 -0
  163. package/test/unit/spec/member/util.js +24 -0
  164. package/test/unit/spec/members/index.js +140 -26
  165. package/test/unit/spec/members/request.js +68 -22
  166. package/test/unit/spec/members/utils.js +75 -0
  167. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  168. package/test/unit/spec/reachability/index.ts +101 -0
  169. package/test/unit/spec/reachability/request.js +47 -2
  170. package/test/unit/spec/reconnection-manager/index.js +4 -4
  171. 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';
@@ -265,6 +262,11 @@ type FetchMeetingInfoParams = {
265
262
  sendCAevents?: boolean;
266
263
  };
267
264
 
265
+ type MediaReachabilityMetrics = ReachabilityMetrics & {
266
+ isSubnetReachable: boolean;
267
+ selectedCluster: string | null;
268
+ };
269
+
268
270
  /**
269
271
  * MediaDirection
270
272
  * @typedef {Object} MediaDirection
@@ -650,6 +652,13 @@ export default class Meeting extends StatelessWebexPlugin {
650
652
  allowMediaInLobby: boolean;
651
653
  localShareInstanceId: string;
652
654
  remoteShareInstanceId: string;
655
+ shareCAEventSentStatus: {
656
+ transmitStart: boolean;
657
+ transmitStop: boolean;
658
+ receiveStart: boolean;
659
+ receiveStop: boolean;
660
+ };
661
+
653
662
  turnDiscoverySkippedReason: TurnDiscoverySkipReason;
654
663
  turnServerUsed: boolean;
655
664
  areVoiceaEventsSetup = false;
@@ -718,6 +727,7 @@ export default class Meeting extends StatelessWebexPlugin {
718
727
  private rtcMetrics?: RtcMetrics;
719
728
  private uploadLogsTimer?: ReturnType<typeof setTimeout>;
720
729
  private logUploadIntervalIndex: number;
730
+ private mediaServerIp: string;
721
731
 
722
732
  /**
723
733
  * @param {Object} attrs
@@ -1422,6 +1432,19 @@ export default class Meeting extends StatelessWebexPlugin {
1422
1432
  */
1423
1433
  this.remoteShareInstanceId = null;
1424
1434
 
1435
+ /**
1436
+ * Status used for ensuring we do not oversend metrics
1437
+ * @instance
1438
+ * @private
1439
+ * @memberof Meeting
1440
+ */
1441
+ this.shareCAEventSentStatus = {
1442
+ transmitStart: false,
1443
+ transmitStop: false,
1444
+ receiveStart: false,
1445
+ receiveStop: false,
1446
+ };
1447
+
1425
1448
  /**
1426
1449
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1427
1450
  * @instance
@@ -1581,6 +1604,19 @@ export default class Meeting extends StatelessWebexPlugin {
1581
1604
  * @memberof Meeting
1582
1605
  */
1583
1606
  this.#isoLocalClientMeetingJoinTime = undefined;
1607
+
1608
+ // We clear the error cache of CA events on every new meeting instance
1609
+ // @ts-ignore - Fix type
1610
+ this.webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache();
1611
+
1612
+ /**
1613
+ * IP Address of the remote media server
1614
+ * @instance
1615
+ * @type {string}
1616
+ * @private
1617
+ * @memberof Meeting
1618
+ */
1619
+ this.mediaServerIp = undefined;
1584
1620
  }
1585
1621
 
1586
1622
  /**
@@ -1686,6 +1722,33 @@ export default class Meeting extends StatelessWebexPlugin {
1686
1722
  return this.#isoLocalClientMeetingJoinTime;
1687
1723
  }
1688
1724
 
1725
+ /**
1726
+ * Setter - sets isoLocalClientMeetingJoinTime
1727
+ * This will be set once on meeting join, and not updated again
1728
+ * this will always produce an ISO string
1729
+ * If the iso string is invalid, it will fallback to the current system time
1730
+ * @param {string | undefined} time
1731
+ */
1732
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1733
+ const fallback = new Date().toISOString();
1734
+ if (!time) {
1735
+ this.#isoLocalClientMeetingJoinTime = fallback;
1736
+ } else {
1737
+ const date = new Date(time);
1738
+
1739
+ // Check if the date is valid
1740
+ if (Number.isNaN(date.getTime())) {
1741
+ LoggerProxy.logger.info(
1742
+ // @ts-ignore
1743
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1744
+ );
1745
+ this.#isoLocalClientMeetingJoinTime = fallback;
1746
+ } else {
1747
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1748
+ }
1749
+ }
1750
+ }
1751
+
1689
1752
  /**
1690
1753
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1691
1754
  * @param {any} info
@@ -2577,6 +2640,19 @@ export default class Meeting extends StatelessWebexPlugin {
2577
2640
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS, (payload) => {
2578
2641
  this.members.locusParticipantsUpdate(payload);
2579
2642
  });
2643
+ this.locusInfo.on(LOCUSINFO.EVENTS.PARTICIPANT_REASON_CHANGED, (payload) => {
2644
+ Trigger.trigger(
2645
+ this,
2646
+ {
2647
+ file: 'meeting/index',
2648
+ function: 'setUpLocusParticipantsListener',
2649
+ },
2650
+ EVENT_TRIGGERS.MEETING_PARTICIPANT_REASON_CHANGED,
2651
+ {
2652
+ payload,
2653
+ }
2654
+ );
2655
+ });
2580
2656
  }
2581
2657
 
2582
2658
  /**
@@ -2827,6 +2903,24 @@ export default class Meeting extends StatelessWebexPlugin {
2827
2903
  {state}
2828
2904
  );
2829
2905
  });
2906
+
2907
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2908
+ Trigger.trigger(
2909
+ this,
2910
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2911
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
2912
+ {state}
2913
+ );
2914
+ });
2915
+
2916
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED, ({state}) => {
2917
+ Trigger.trigger(
2918
+ this,
2919
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2920
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
2921
+ {state}
2922
+ );
2923
+ });
2830
2924
  }
2831
2925
 
2832
2926
  /**
@@ -2999,6 +3093,8 @@ export default class Meeting extends StatelessWebexPlugin {
2999
3093
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
3000
3094
  const sendStartedSharingRemote = () => {
3001
3095
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3096
+ this.shareCAEventSentStatus.receiveStart = false;
3097
+ this.shareCAEventSentStatus.receiveStop = false;
3002
3098
 
3003
3099
  Trigger.trigger(
3004
3100
  this,
@@ -3052,6 +3148,7 @@ export default class Meeting extends StatelessWebexPlugin {
3052
3148
  },
3053
3149
  options: {meetingId: this.id},
3054
3150
  });
3151
+
3055
3152
  break;
3056
3153
 
3057
3154
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3092,6 +3189,8 @@ export default class Meeting extends StatelessWebexPlugin {
3092
3189
  // if we got here, then some remote participant has stolen
3093
3190
  // the presentation from another remote participant
3094
3191
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3192
+ this.shareCAEventSentStatus.receiveStart = false;
3193
+ this.shareCAEventSentStatus.receiveStop = false;
3095
3194
 
3096
3195
  Trigger.trigger(
3097
3196
  this,
@@ -3720,6 +3819,18 @@ export default class Meeting extends StatelessWebexPlugin {
3720
3819
  return this.members.cancelPhoneInvite(invitee);
3721
3820
  }
3722
3821
 
3822
+ /**
3823
+ * Cancel an SIP call invitation made during a meeting
3824
+ * @param {Object} invitee
3825
+ * @param {String} invitee.memberId
3826
+ * @returns {Promise} see #members.cancelSIPInvite
3827
+ * @public
3828
+ * @memberof Meeting
3829
+ */
3830
+ public cancelSIPInvite(invitee: {memberId: string}) {
3831
+ return this.members.cancelSIPInvite(invitee);
3832
+ }
3833
+
3723
3834
  /**
3724
3835
  * Admit the guest(s) to the call once they are waiting.
3725
3836
  * If the host/cohost is in a breakout session, the locus url
@@ -3775,7 +3886,13 @@ export default class Meeting extends StatelessWebexPlugin {
3775
3886
  return Promise.reject(error);
3776
3887
  }
3777
3888
 
3778
- return this.brbState.enable(enabled, this.sendSlotManager);
3889
+ return this.brbState.enable(enabled, this.sendSlotManager).then(() => {
3890
+ if (this.audio && enabled) {
3891
+ // locus mutes the participant with brb enabled request,
3892
+ // so we need to explicitly update remote mute for correct logic flow
3893
+ this.audio.handleServerRemoteMuteUpdate(this, enabled);
3894
+ }
3895
+ });
3779
3896
  }
3780
3897
 
3781
3898
  /**
@@ -3967,7 +4084,10 @@ export default class Meeting extends StatelessWebexPlugin {
3967
4084
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3968
4085
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3969
4086
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3970
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4087
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4088
+ this.userDisplayHints,
4089
+ this.selfUserPolicies
4090
+ ),
3971
4091
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3972
4092
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
3973
4093
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -4016,6 +4136,9 @@ export default class Meeting extends StatelessWebexPlugin {
4016
4136
  this.inMeetingActions.canSendReactions,
4017
4137
  this.userDisplayHints
4018
4138
  ),
4139
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4140
+ this.userDisplayHints
4141
+ ),
4019
4142
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
4020
4143
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
4021
4144
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -4031,6 +4154,7 @@ export default class Meeting extends StatelessWebexPlugin {
4031
4154
  this.userDisplayHints
4032
4155
  ),
4033
4156
  canUserRenameOthers: MeetingUtil.canUserRenameOthers(this.userDisplayHints),
4157
+ canMoveToLobby: MeetingUtil.canMoveToLobby(this.userDisplayHints),
4034
4158
  canMuteAll: ControlsOptionsUtil.hasHints({
4035
4159
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
4036
4160
  displayHints: this.userDisplayHints,
@@ -4165,6 +4289,14 @@ export default class Meeting extends StatelessWebexPlugin {
4165
4289
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
4166
4290
  policies: this.selfUserPolicies,
4167
4291
  }),
4292
+ canRealtimeCloseCaption: ControlsOptionsUtil.hasPolicies({
4293
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
4294
+ policies: this.selfUserPolicies,
4295
+ }),
4296
+ canRealtimeCloseCaptionManual: ControlsOptionsUtil.hasPolicies({
4297
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
4298
+ policies: this.selfUserPolicies,
4299
+ }),
4168
4300
  canChat: ControlsOptionsUtil.hasPolicies({
4169
4301
  requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
4170
4302
  policies: this.selfUserPolicies,
@@ -4211,6 +4343,22 @@ export default class Meeting extends StatelessWebexPlugin {
4211
4343
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4212
4344
  policies: this.selfUserPolicies,
4213
4345
  }),
4346
+ canEnableAnnotation: ControlsOptionsUtil.hasHints({
4347
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
4348
+ displayHints: this.userDisplayHints,
4349
+ }),
4350
+ canDisableAnnotation: ControlsOptionsUtil.hasHints({
4351
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
4352
+ displayHints: this.userDisplayHints,
4353
+ }),
4354
+ canEnableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4355
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
4356
+ displayHints: this.userDisplayHints,
4357
+ }),
4358
+ canDisableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4359
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
4360
+ displayHints: this.userDisplayHints,
4361
+ }),
4214
4362
  }) || changed;
4215
4363
  }
4216
4364
  if (changed) {
@@ -5718,8 +5866,6 @@ export default class Meeting extends StatelessWebexPlugin {
5718
5866
  // @ts-ignore
5719
5867
  this.webex.internal.device.meetingStarted();
5720
5868
 
5721
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5722
-
5723
5869
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5724
5870
 
5725
5871
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6180,10 +6326,18 @@ export default class Meeting extends StatelessWebexPlugin {
6180
6326
  },
6181
6327
  options: {meetingId: this.id, rawError: error},
6182
6328
  });
6183
- } else if (
6184
- error instanceof Errors.SdpOfferHandlingError ||
6185
- error instanceof Errors.SdpAnswerHandlingError
6186
- ) {
6329
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6330
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6331
+
6332
+ // @ts-ignore
6333
+ this.webex.internal.newMetrics.submitClientEvent({
6334
+ name: 'client.media-engine.remote-sdp-received',
6335
+ payload: {
6336
+ canProceed: false,
6337
+ },
6338
+ options: {meetingId: this.id, rawError: error},
6339
+ });
6340
+ } else if (error instanceof Errors.SdpAnswerHandlingError) {
6187
6341
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6188
6342
 
6189
6343
  // @ts-ignore
@@ -6194,6 +6348,13 @@ export default class Meeting extends StatelessWebexPlugin {
6194
6348
  },
6195
6349
  options: {meetingId: this.id, rawError: error},
6196
6350
  });
6351
+
6352
+ if (this.deferSDPAnswer) {
6353
+ clearTimeout(this.sdpResponseTimer);
6354
+ this.sdpResponseTimer = undefined;
6355
+
6356
+ this.deferSDPAnswer.reject();
6357
+ }
6197
6358
  } else if (error instanceof Errors.SdpError) {
6198
6359
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6199
6360
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -6221,6 +6382,11 @@ export default class Meeting extends StatelessWebexPlugin {
6221
6382
  ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6222
6383
  : undefined;
6223
6384
 
6385
+ const mediaServerIp =
6386
+ roapMessage.messageType === 'ANSWER'
6387
+ ? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6388
+ : undefined;
6389
+
6224
6390
  if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6225
6391
  throw new MultistreamNotSupportedError(
6226
6392
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6231,6 +6397,10 @@ export default class Meeting extends StatelessWebexPlugin {
6231
6397
  if (mediaServer) {
6232
6398
  this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
6233
6399
  }
6400
+
6401
+ if (this.isMultistream && mediaServerIp) {
6402
+ this.mediaServerIp = mediaServerIp;
6403
+ }
6234
6404
  };
6235
6405
 
6236
6406
  /**
@@ -6688,20 +6858,20 @@ export default class Meeting extends StatelessWebexPlugin {
6688
6858
  * @memberof Meetings
6689
6859
  */
6690
6860
  setupStatsAnalyzerEventHandlers = () => {
6691
- this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (options) => {
6692
- // TODO: might have to send the same event to the developer
6693
- // Add ip address info if geo hint is present
6694
- // @ts-ignore fix type
6695
- options.data.intervalMetadata.peerReflexiveIP =
6696
- // @ts-ignore
6697
- this.webex.meetings.geoHintInfo?.clientAddress ||
6698
- options.data.intervalMetadata.peerReflexiveIP ||
6699
- MQA_STATS.DEFAULT_IP;
6861
+ this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (event) => {
6862
+ // Add IP address from geoHintInfo if missing.
6863
+ if (event.data.intervalMetadata.maskedPeerReflexiveIP === '0.0.0.0') {
6864
+ // @ts-ignore fix type
6865
+ const clientAddressFromGeoHint = this.webex.meetings.geoHintInfo?.clientAddress;
6866
+ if (clientAddressFromGeoHint) {
6867
+ event.data.intervalMetadata.maskedPeerReflexiveIP =
6868
+ CallDiagnosticUtils.anonymizeIPAddress(clientAddressFromGeoHint);
6869
+ }
6870
+ }
6700
6871
 
6872
+ // Count members that are in the meeting.
6701
6873
  const {members} = this.getMembers().membersCollection;
6702
-
6703
- // Count members that are in the meeting
6704
- options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6874
+ event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6705
6875
  (member: Member) => member.isInMeeting
6706
6876
  ).length;
6707
6877
 
@@ -6710,10 +6880,10 @@ export default class Meeting extends StatelessWebexPlugin {
6710
6880
  name: 'client.mediaquality.event',
6711
6881
  options: {
6712
6882
  meetingId: this.id,
6713
- networkType: options.data.networkType,
6883
+ networkType: this.statsAnalyzer.getNetworkType(),
6714
6884
  },
6715
6885
  payload: {
6716
- intervals: [options.data],
6886
+ intervals: [event.data],
6717
6887
  },
6718
6888
  });
6719
6889
  });
@@ -6728,30 +6898,42 @@ export default class Meeting extends StatelessWebexPlugin {
6728
6898
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6729
6899
  data
6730
6900
  );
6731
- // @ts-ignore
6732
- this.webex.internal.newMetrics.submitClientEvent({
6733
- name: 'client.media.tx.start',
6734
- payload: {
6735
- mediaType: data.mediaType,
6736
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6737
- },
6738
- options: {
6739
- meetingId: this.id,
6740
- },
6741
- });
6901
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStart) {
6902
+ // @ts-ignore
6903
+ this.webex.internal.newMetrics.submitClientEvent({
6904
+ name: 'client.media.tx.start',
6905
+ payload: {
6906
+ mediaType: data.mediaType,
6907
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6908
+ },
6909
+ options: {
6910
+ meetingId: this.id,
6911
+ },
6912
+ });
6913
+
6914
+ if (data.mediaType === 'share') {
6915
+ this.shareCAEventSentStatus.transmitStart = true;
6916
+ }
6917
+ }
6742
6918
  });
6743
6919
  this.statsAnalyzer.on(StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED, (data) => {
6744
- // @ts-ignore
6745
- this.webex.internal.newMetrics.submitClientEvent({
6746
- name: 'client.media.tx.stop',
6747
- payload: {
6748
- mediaType: data.mediaType,
6749
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6750
- },
6751
- options: {
6752
- meetingId: this.id,
6753
- },
6754
- });
6920
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStop) {
6921
+ // @ts-ignore
6922
+ this.webex.internal.newMetrics.submitClientEvent({
6923
+ name: 'client.media.tx.stop',
6924
+ payload: {
6925
+ mediaType: data.mediaType,
6926
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6927
+ },
6928
+ options: {
6929
+ meetingId: this.id,
6930
+ },
6931
+ });
6932
+
6933
+ if (data.mediaType === 'share') {
6934
+ this.shareCAEventSentStatus.transmitStop = true;
6935
+ }
6936
+ }
6755
6937
  });
6756
6938
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6757
6939
  Trigger.trigger(
@@ -6763,57 +6945,65 @@ export default class Meeting extends StatelessWebexPlugin {
6763
6945
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6764
6946
  data
6765
6947
  );
6766
- // @ts-ignore
6767
- this.webex.internal.newMetrics.submitClientEvent({
6768
- name: 'client.media.rx.start',
6769
- payload: {
6770
- mediaType: data.mediaType,
6771
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6772
- },
6773
- options: {
6774
- meetingId: this.id,
6775
- },
6776
- });
6777
-
6778
- if (data.mediaType === 'share') {
6948
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6779
6949
  // @ts-ignore
6780
6950
  this.webex.internal.newMetrics.submitClientEvent({
6781
- name: 'client.media.render.start',
6951
+ name: 'client.media.rx.start',
6782
6952
  payload: {
6783
- mediaType: 'share',
6784
- shareInstanceId: this.remoteShareInstanceId,
6953
+ mediaType: data.mediaType,
6954
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6785
6955
  },
6786
6956
  options: {
6787
6957
  meetingId: this.id,
6788
6958
  },
6789
6959
  });
6960
+
6961
+ if (data.mediaType === 'share') {
6962
+ // @ts-ignore
6963
+ this.webex.internal.newMetrics.submitClientEvent({
6964
+ name: 'client.media.render.start',
6965
+ payload: {
6966
+ mediaType: 'share',
6967
+ shareInstanceId: this.remoteShareInstanceId,
6968
+ },
6969
+ options: {
6970
+ meetingId: this.id,
6971
+ },
6972
+ });
6973
+
6974
+ this.shareCAEventSentStatus.receiveStart = true;
6975
+ }
6790
6976
  }
6791
6977
  });
6792
6978
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED, (data) => {
6793
- // @ts-ignore
6794
- this.webex.internal.newMetrics.submitClientEvent({
6795
- name: 'client.media.rx.stop',
6796
- payload: {
6797
- mediaType: data.mediaType,
6798
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6799
- },
6800
- options: {
6801
- meetingId: this.id,
6802
- },
6803
- });
6804
-
6805
- if (data.mediaType === 'share') {
6979
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6806
6980
  // @ts-ignore
6807
6981
  this.webex.internal.newMetrics.submitClientEvent({
6808
- name: 'client.media.render.stop',
6982
+ name: 'client.media.rx.stop',
6809
6983
  payload: {
6810
- mediaType: 'share',
6811
- shareInstanceId: this.remoteShareInstanceId,
6984
+ mediaType: data.mediaType,
6985
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6812
6986
  },
6813
6987
  options: {
6814
6988
  meetingId: this.id,
6815
6989
  },
6816
6990
  });
6991
+
6992
+ if (data.mediaType === 'share') {
6993
+ // @ts-ignore
6994
+ this.webex.internal.newMetrics.submitClientEvent({
6995
+ name: 'client.media.render.stop',
6996
+ payload: {
6997
+ mediaType: 'share',
6998
+ shareInstanceId: this.remoteShareInstanceId,
6999
+ },
7000
+ options: {
7001
+ meetingId: this.id,
7002
+ },
7003
+ });
7004
+
7005
+ this.shareCAEventSentStatus.receiveStop = true;
7006
+ }
6817
7007
  }
6818
7008
  });
6819
7009
  };
@@ -6830,7 +7020,10 @@ export default class Meeting extends StatelessWebexPlugin {
6830
7020
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6831
7021
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6832
7022
  */
6833
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
7023
+ private async createMediaConnection(
7024
+ turnServerInfo?: TurnServerInfo,
7025
+ bundlePolicy?: BundlePolicy
7026
+ ) {
6834
7027
  this.rtcMetrics = this.isMultistream
6835
7028
  ? // @ts-ignore
6836
7029
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6855,6 +7048,13 @@ export default class Meeting extends StatelessWebexPlugin {
6855
7048
  bundlePolicy,
6856
7049
  // @ts-ignore - config coming from registerPlugin
6857
7050
  iceCandidatesTimeout: this.config.iceCandidatesGatheringTimeout,
7051
+ // @ts-ignore - config coming from registerPlugin
7052
+ disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
7053
+ // @ts-ignore - config coming from registerPlugin
7054
+ enableAudioTwcc: this.config.enableAudioTwccForMultistream,
7055
+ stopIceGatheringAfterFirstRelayCandidate:
7056
+ // @ts-ignore - config coming from registerPlugin
7057
+ this.config.stopIceGatheringAfterFirstRelayCandidate,
6858
7058
  }
6859
7059
  );
6860
7060
 
@@ -7005,12 +7205,18 @@ export default class Meeting extends StatelessWebexPlugin {
7005
7205
  },
7006
7206
  options: {
7007
7207
  meetingId: this.id,
7208
+ rawError: error,
7008
7209
  },
7009
7210
  });
7010
7211
  }
7011
- throw new Error(
7212
+
7213
+ const timedOutError = new Error(
7012
7214
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
7013
7215
  );
7216
+
7217
+ timedOutError.cause = error;
7218
+
7219
+ throw timedOutError;
7014
7220
  }
7015
7221
  }
7016
7222
 
@@ -7031,6 +7237,12 @@ export default class Meeting extends StatelessWebexPlugin {
7031
7237
  networkQualityMonitor: this.networkQualityMonitor,
7032
7238
  isMultistream: this.isMultistream,
7033
7239
  });
7240
+ this.shareCAEventSentStatus = {
7241
+ transmitStart: false,
7242
+ transmitStop: false,
7243
+ receiveStart: false,
7244
+ receiveStop: false,
7245
+ };
7034
7246
  this.setupStatsAnalyzerEventHandlers();
7035
7247
  this.networkQualityMonitor.on(
7036
7248
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -7065,6 +7277,9 @@ export default class Meeting extends StatelessWebexPlugin {
7065
7277
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7066
7278
  } seconds`
7067
7279
  );
7280
+
7281
+ const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7282
+
7068
7283
  // @ts-ignore
7069
7284
  this.webex.internal.newMetrics.submitClientEvent({
7070
7285
  name: 'client.media-engine.remote-sdp-received',
@@ -7077,10 +7292,10 @@ export default class Meeting extends StatelessWebexPlugin {
7077
7292
  }),
7078
7293
  ],
7079
7294
  },
7080
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7295
+ options: {meetingId: this.id, rawError: error},
7081
7296
  });
7082
7297
 
7083
- deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
7298
+ deferSDPAnswer.reject(error);
7084
7299
  }, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
7085
7300
 
7086
7301
  LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
@@ -7185,7 +7400,7 @@ export default class Meeting extends StatelessWebexPlugin {
7185
7400
  error
7186
7401
  );
7187
7402
 
7188
- throw new AddMediaFailed();
7403
+ throw new AddMediaFailed(error);
7189
7404
  }
7190
7405
  }
7191
7406
 
@@ -7598,28 +7813,33 @@ export default class Meeting extends StatelessWebexPlugin {
7598
7813
  await this.enqueueScreenShareFloorRequest();
7599
7814
  }
7600
7815
 
7601
- const {connectionType, selectedCandidatePairChanges, numTransports} =
7816
+ const {connectionType, ipVersion, selectedCandidatePairChanges, numTransports} =
7602
7817
  await this.mediaProperties.getCurrentConnectionInfo();
7603
- // @ts-ignore
7604
- const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7818
+
7605
7819
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7606
7820
 
7821
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7822
+
7607
7823
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7608
7824
  correlation_id: this.correlationId,
7609
7825
  locus_id: this.locusUrl.split('/').pop(),
7610
7826
  connectionType,
7827
+ ipVersion,
7611
7828
  selectedCandidatePairChanges,
7612
7829
  numTransports,
7613
7830
  isMultistream: this.isMultistream,
7614
7831
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7615
7832
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7616
- ...reachabilityStats,
7833
+ ...reachabilityMetrics,
7617
7834
  ...iceCandidateErrors,
7618
7835
  iceCandidatesCount: this.iceCandidatesCount,
7619
7836
  });
7620
7837
  // @ts-ignore
7621
7838
  this.webex.internal.newMetrics.submitClientEvent({
7622
7839
  name: 'client.media-engine.ready',
7840
+ payload: {
7841
+ ipVersion,
7842
+ },
7623
7843
  options: {
7624
7844
  meetingId: this.id,
7625
7845
  },
@@ -7635,7 +7855,7 @@ export default class Meeting extends StatelessWebexPlugin {
7635
7855
  LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
7636
7856
 
7637
7857
  // @ts-ignore
7638
- const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
7858
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7639
7859
 
7640
7860
  const {selectedCandidatePairChanges, numTransports} =
7641
7861
  await this.mediaProperties.getCurrentConnectionInfo();
@@ -8715,6 +8935,9 @@ export default class Meeting extends StatelessWebexPlugin {
8715
8935
  LoggerProxy.logger.log(
8716
8936
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8717
8937
  );
8938
+
8939
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8940
+
8718
8941
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8719
8942
  correlationId: this.correlationId,
8720
8943
  muted,
@@ -8723,8 +8946,9 @@ export default class Meeting extends StatelessWebexPlugin {
8723
8946
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8724
8947
  // all we're doing here is adding metrics.
8725
8948
  // eslint-disable-next-line dot-notation
8726
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8949
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8727
8950
  isMultistream: this.isMultistream,
8951
+ frameRate: shareVideoStreamSettings?.frameRate,
8728
8952
  });
8729
8953
  };
8730
8954
 
@@ -9051,6 +9275,23 @@ export default class Meeting extends StatelessWebexPlugin {
9051
9275
  });
9052
9276
  }
9053
9277
 
9278
+ /**
9279
+ * Method to set post meeting data consent.
9280
+ *
9281
+ * @param {boolean} accept - whether consent accepted or declined
9282
+ * @returns {Promise}
9283
+ * @public
9284
+ * @memberof Meeting
9285
+ */
9286
+ public setPostMeetingDataConsent(accept: boolean) {
9287
+ return this.meetingRequest.setPostMeetingDataConsent({
9288
+ postMeetingDataConsent: accept,
9289
+ locusUrl: this.locusUrl,
9290
+ deviceUrl: this.deviceUrl,
9291
+ selfId: this.members.selfId,
9292
+ });
9293
+ }
9294
+
9054
9295
  /**
9055
9296
  * Throws if we don't have a media connection created
9056
9297
  *
@@ -9291,6 +9532,8 @@ export default class Meeting extends StatelessWebexPlugin {
9291
9532
 
9292
9533
  if (floorRequestNeeded) {
9293
9534
  this.localShareInstanceId = uuid.v4();
9535
+ this.shareCAEventSentStatus.transmitStart = false;
9536
+ this.shareCAEventSentStatus.transmitStop = false;
9294
9537
 
9295
9538
  // @ts-ignore
9296
9539
  this.webex.internal.newMetrics.submitClientEvent({
@@ -9418,4 +9661,50 @@ export default class Meeting extends StatelessWebexPlugin {
9418
9661
 
9419
9662
  return Promise.resolve();
9420
9663
  }
9664
+
9665
+ /**
9666
+ * Gets the media reachability metrics
9667
+ *
9668
+ * @returns {Promise<MediaReachabilityMetrics>}
9669
+ */
9670
+ private async getMediaReachabilityMetricFields(): Promise<MediaReachabilityMetrics> {
9671
+ const reachabilityMetrics: ReachabilityMetrics =
9672
+ // @ts-ignore
9673
+ await this.webex.meetings.reachability.getReachabilityMetrics();
9674
+
9675
+ const successKeys: Array<keyof ReachabilityMetrics> = [
9676
+ 'reachability_public_udp_success',
9677
+ 'reachability_public_tcp_success',
9678
+ 'reachability_public_xtls_success',
9679
+ 'reachability_vmn_udp_success',
9680
+ 'reachability_vmn_tcp_success',
9681
+ 'reachability_vmn_xtls_success',
9682
+ ];
9683
+
9684
+ const totalSuccessCases = successKeys.reduce((total, key) => {
9685
+ const value = reachabilityMetrics[key];
9686
+ if (typeof value === 'number') {
9687
+ return total + value;
9688
+ }
9689
+
9690
+ return total;
9691
+ }, 0);
9692
+
9693
+ let isSubnetReachable = null;
9694
+ if (totalSuccessCases > 0) {
9695
+ // @ts-ignore
9696
+ isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9697
+ }
9698
+
9699
+ let selectedCluster = null;
9700
+ if (this.mediaConnections && this.mediaConnections.length > 0) {
9701
+ selectedCluster = this.mediaConnections[0].mediaAgentCluster;
9702
+ }
9703
+
9704
+ return {
9705
+ ...reachabilityMetrics,
9706
+ isSubnetReachable,
9707
+ selectedCluster,
9708
+ };
9709
+ }
9421
9710
  }