@webex/plugin-meetings 3.8.0-web-workers-keepalive.1 → 3.8.1-next.1

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 (168) 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 +4 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +22 -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 +30 -10
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/index.js +83 -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 +9 -2
  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 +568 -328
  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 +4 -4
  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 +9 -1
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meeting-info/meeting-info-v2.js +19 -13
  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 +76 -0
  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 +45 -9
  51. package/dist/member/index.js.map +1 -1
  52. package/dist/member/types.js +3 -0
  53. package/dist/member/types.js.map +1 -1
  54. package/dist/member/util.js +335 -356
  55. package/dist/member/util.js.map +1 -1
  56. package/dist/members/collection.js.map +1 -1
  57. package/dist/members/index.js +137 -29
  58. package/dist/members/index.js.map +1 -1
  59. package/dist/members/request.js +38 -0
  60. package/dist/members/request.js.map +1 -1
  61. package/dist/members/util.js +36 -1
  62. package/dist/members/util.js.map +1 -1
  63. package/dist/metrics/constants.js +1 -0
  64. package/dist/metrics/constants.js.map +1 -1
  65. package/dist/reachability/clusterReachability.js +23 -31
  66. package/dist/reachability/clusterReachability.js.map +1 -1
  67. package/dist/reachability/index.js +42 -2
  68. package/dist/reachability/index.js.map +1 -1
  69. package/dist/reconnection-manager/index.js +2 -2
  70. package/dist/reconnection-manager/index.js.map +1 -1
  71. package/dist/roap/index.js.map +1 -1
  72. package/dist/roap/turnDiscovery.js +45 -27
  73. package/dist/roap/turnDiscovery.js.map +1 -1
  74. package/dist/roap/types.js +17 -0
  75. package/dist/roap/types.js.map +1 -0
  76. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  77. package/dist/types/config.d.ts +2 -0
  78. package/dist/types/constants.d.ts +15 -85
  79. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  80. package/dist/types/controls-options-manager/types.d.ts +7 -1
  81. package/dist/types/locus-info/index.d.ts +3 -3
  82. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  83. package/dist/types/media/properties.d.ts +15 -0
  84. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  85. package/dist/types/meeting/index.d.ts +35 -1
  86. package/dist/types/meeting/muteState.d.ts +0 -1
  87. package/dist/types/meeting/request.d.ts +12 -1
  88. package/dist/types/meeting/request.type.d.ts +6 -0
  89. package/dist/types/meeting/util.d.ts +3 -1
  90. package/dist/types/meeting-info/meeting-info-v2.d.ts +2 -1
  91. package/dist/types/meetings/index.d.ts +28 -0
  92. package/dist/types/member/index.d.ts +20 -6
  93. package/dist/types/member/types.d.ts +73 -14
  94. package/dist/types/member/util.d.ts +156 -1
  95. package/dist/types/members/collection.d.ts +6 -5
  96. package/dist/types/members/index.d.ts +32 -43
  97. package/dist/types/members/request.d.ts +26 -0
  98. package/dist/types/members/util.d.ts +27 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/reachability/clusterReachability.d.ts +2 -6
  101. package/dist/types/reachability/index.d.ts +8 -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 +2 -0
  110. package/src/constants.ts +23 -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 +48 -12
  115. package/src/locus-info/index.ts +88 -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 +11 -2
  120. package/src/meeting/in-meeting-actions.ts +32 -0
  121. package/src/meeting/index.ts +356 -87
  122. package/src/meeting/locusMediaRequest.ts +0 -18
  123. package/src/meeting/muteState.ts +4 -4
  124. package/src/meeting/request.ts +36 -1
  125. package/src/meeting/request.type.ts +7 -0
  126. package/src/meeting/util.ts +9 -1
  127. package/src/meeting-info/meeting-info-v2.ts +7 -2
  128. package/src/meeting-info/utilv2.ts +5 -0
  129. package/src/meetings/index.ts +76 -0
  130. package/src/meetings/util.ts +18 -0
  131. package/src/member/index.ts +57 -22
  132. package/src/member/types.ts +82 -16
  133. package/src/member/util.ts +357 -353
  134. package/src/members/collection.ts +4 -3
  135. package/src/members/index.ts +137 -18
  136. package/src/members/request.ts +44 -0
  137. package/src/members/util.ts +43 -1
  138. package/src/metrics/constants.ts +1 -0
  139. package/src/reachability/clusterReachability.ts +26 -25
  140. package/src/reachability/index.ts +55 -1
  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 +131 -9
  148. package/test/unit/spec/locus-info/index.js +195 -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 +40 -2
  153. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  154. package/test/unit/spec/meeting/index.js +553 -36
  155. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  156. package/test/unit/spec/meeting/muteState.js +73 -2
  157. package/test/unit/spec/meeting/request.js +32 -1
  158. package/test/unit/spec/meeting/utils.js +79 -33
  159. package/test/unit/spec/meeting-info/meetinginfov2.js +41 -0
  160. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  161. package/test/unit/spec/meetings/index.js +68 -1
  162. package/test/unit/spec/members/index.js +304 -78
  163. package/test/unit/spec/members/request.js +68 -22
  164. package/test/unit/spec/members/utils.js +75 -0
  165. package/test/unit/spec/reachability/clusterReachability.ts +41 -55
  166. package/test/unit/spec/reachability/index.ts +89 -0
  167. package/test/unit/spec/reconnection-manager/index.js +4 -4
  168. 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
@@ -1334,7 +1344,7 @@ export default class Meeting extends StatelessWebexPlugin {
1334
1344
  captions: [],
1335
1345
  isListening: false,
1336
1346
  commandText: '',
1337
- languageOptions: {},
1347
+ languageOptions: {currentSpokenLanguage: 'en'},
1338
1348
  showCaptionBox: false,
1339
1349
  transcribingRequestStatus: 'INACTIVE',
1340
1350
  isCaptioning: false,
@@ -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
  /**
@@ -2604,6 +2640,19 @@ export default class Meeting extends StatelessWebexPlugin {
2604
2640
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS, (payload) => {
2605
2641
  this.members.locusParticipantsUpdate(payload);
2606
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
+ });
2607
2656
  }
2608
2657
 
2609
2658
  /**
@@ -2704,6 +2753,27 @@ export default class Meeting extends StatelessWebexPlugin {
2704
2753
  }
2705
2754
  );
2706
2755
 
2756
+ this.locusInfo.on(
2757
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2758
+ ({spokenLanguage}) => {
2759
+ if (spokenLanguage) {
2760
+ this.transcription.languageOptions.currentSpokenLanguage = spokenLanguage;
2761
+ // @ts-ignore
2762
+ this.webex.internal.voicea.onSpokenLanguageUpdate(spokenLanguage);
2763
+
2764
+ Trigger.trigger(
2765
+ this,
2766
+ {
2767
+ file: 'meeting/index',
2768
+ function: 'setupLocusControlsListener',
2769
+ },
2770
+ EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
2771
+ {spokenLanguage}
2772
+ );
2773
+ }
2774
+ }
2775
+ );
2776
+
2707
2777
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_MANUAL_CAPTION_UPDATED, ({enable}) => {
2708
2778
  Trigger.trigger(
2709
2779
  this,
@@ -2854,6 +2924,24 @@ export default class Meeting extends StatelessWebexPlugin {
2854
2924
  {state}
2855
2925
  );
2856
2926
  });
2927
+
2928
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2929
+ Trigger.trigger(
2930
+ this,
2931
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2932
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
2933
+ {state}
2934
+ );
2935
+ });
2936
+
2937
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED, ({state}) => {
2938
+ Trigger.trigger(
2939
+ this,
2940
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2941
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
2942
+ {state}
2943
+ );
2944
+ });
2857
2945
  }
2858
2946
 
2859
2947
  /**
@@ -3026,6 +3114,8 @@ export default class Meeting extends StatelessWebexPlugin {
3026
3114
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
3027
3115
  const sendStartedSharingRemote = () => {
3028
3116
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3117
+ this.shareCAEventSentStatus.receiveStart = false;
3118
+ this.shareCAEventSentStatus.receiveStop = false;
3029
3119
 
3030
3120
  Trigger.trigger(
3031
3121
  this,
@@ -3079,6 +3169,7 @@ export default class Meeting extends StatelessWebexPlugin {
3079
3169
  },
3080
3170
  options: {meetingId: this.id},
3081
3171
  });
3172
+
3082
3173
  break;
3083
3174
 
3084
3175
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3119,6 +3210,8 @@ export default class Meeting extends StatelessWebexPlugin {
3119
3210
  // if we got here, then some remote participant has stolen
3120
3211
  // the presentation from another remote participant
3121
3212
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3213
+ this.shareCAEventSentStatus.receiveStart = false;
3214
+ this.shareCAEventSentStatus.receiveStop = false;
3122
3215
 
3123
3216
  Trigger.trigger(
3124
3217
  this,
@@ -3747,6 +3840,18 @@ export default class Meeting extends StatelessWebexPlugin {
3747
3840
  return this.members.cancelPhoneInvite(invitee);
3748
3841
  }
3749
3842
 
3843
+ /**
3844
+ * Cancel an SIP call invitation made during a meeting
3845
+ * @param {Object} invitee
3846
+ * @param {String} invitee.memberId
3847
+ * @returns {Promise} see #members.cancelSIPInvite
3848
+ * @public
3849
+ * @memberof Meeting
3850
+ */
3851
+ public cancelSIPInvite(invitee: {memberId: string}) {
3852
+ return this.members.cancelSIPInvite(invitee);
3853
+ }
3854
+
3750
3855
  /**
3751
3856
  * Admit the guest(s) to the call once they are waiting.
3752
3857
  * If the host/cohost is in a breakout session, the locus url
@@ -3802,7 +3907,16 @@ export default class Meeting extends StatelessWebexPlugin {
3802
3907
  return Promise.reject(error);
3803
3908
  }
3804
3909
 
3805
- return this.brbState.enable(enabled, this.sendSlotManager);
3910
+ return this.brbState
3911
+ .enable(enabled, this.sendSlotManager)
3912
+ .then(() => {
3913
+ if (this.audio && enabled) {
3914
+ // locus mutes the participant with brb enabled request,
3915
+ // so we need to explicitly update remote mute for correct logic flow
3916
+ this.audio.handleServerRemoteMuteUpdate(this, enabled);
3917
+ }
3918
+ })
3919
+ .catch((error) => Promise.reject(error));
3806
3920
  }
3807
3921
 
3808
3922
  /**
@@ -3994,7 +4108,10 @@ export default class Meeting extends StatelessWebexPlugin {
3994
4108
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3995
4109
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3996
4110
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3997
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4111
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4112
+ this.userDisplayHints,
4113
+ this.selfUserPolicies
4114
+ ),
3998
4115
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3999
4116
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
4000
4117
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -4043,6 +4160,9 @@ export default class Meeting extends StatelessWebexPlugin {
4043
4160
  this.inMeetingActions.canSendReactions,
4044
4161
  this.userDisplayHints
4045
4162
  ),
4163
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4164
+ this.userDisplayHints
4165
+ ),
4046
4166
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
4047
4167
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
4048
4168
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -4058,6 +4178,7 @@ export default class Meeting extends StatelessWebexPlugin {
4058
4178
  this.userDisplayHints
4059
4179
  ),
4060
4180
  canUserRenameOthers: MeetingUtil.canUserRenameOthers(this.userDisplayHints),
4181
+ canMoveToLobby: MeetingUtil.canMoveToLobby(this.userDisplayHints),
4061
4182
  canMuteAll: ControlsOptionsUtil.hasHints({
4062
4183
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
4063
4184
  displayHints: this.userDisplayHints,
@@ -4192,6 +4313,14 @@ export default class Meeting extends StatelessWebexPlugin {
4192
4313
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
4193
4314
  policies: this.selfUserPolicies,
4194
4315
  }),
4316
+ canRealtimeCloseCaption: ControlsOptionsUtil.hasPolicies({
4317
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
4318
+ policies: this.selfUserPolicies,
4319
+ }),
4320
+ canRealtimeCloseCaptionManual: ControlsOptionsUtil.hasPolicies({
4321
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
4322
+ policies: this.selfUserPolicies,
4323
+ }),
4195
4324
  canChat: ControlsOptionsUtil.hasPolicies({
4196
4325
  requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
4197
4326
  policies: this.selfUserPolicies,
@@ -4238,6 +4367,22 @@ export default class Meeting extends StatelessWebexPlugin {
4238
4367
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4239
4368
  policies: this.selfUserPolicies,
4240
4369
  }),
4370
+ canEnableAnnotation: ControlsOptionsUtil.hasHints({
4371
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
4372
+ displayHints: this.userDisplayHints,
4373
+ }),
4374
+ canDisableAnnotation: ControlsOptionsUtil.hasHints({
4375
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
4376
+ displayHints: this.userDisplayHints,
4377
+ }),
4378
+ canEnableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4379
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
4380
+ displayHints: this.userDisplayHints,
4381
+ }),
4382
+ canDisableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4383
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
4384
+ displayHints: this.userDisplayHints,
4385
+ }),
4241
4386
  }) || changed;
4242
4387
  }
4243
4388
  if (changed) {
@@ -6261,6 +6406,11 @@ export default class Meeting extends StatelessWebexPlugin {
6261
6406
  ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6262
6407
  : undefined;
6263
6408
 
6409
+ const mediaServerIp =
6410
+ roapMessage.messageType === 'ANSWER'
6411
+ ? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6412
+ : undefined;
6413
+
6264
6414
  if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6265
6415
  throw new MultistreamNotSupportedError(
6266
6416
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6271,6 +6421,10 @@ export default class Meeting extends StatelessWebexPlugin {
6271
6421
  if (mediaServer) {
6272
6422
  this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
6273
6423
  }
6424
+
6425
+ if (this.isMultistream && mediaServerIp) {
6426
+ this.mediaServerIp = mediaServerIp;
6427
+ }
6274
6428
  };
6275
6429
 
6276
6430
  /**
@@ -6728,20 +6882,20 @@ export default class Meeting extends StatelessWebexPlugin {
6728
6882
  * @memberof Meetings
6729
6883
  */
6730
6884
  setupStatsAnalyzerEventHandlers = () => {
6731
- this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (options) => {
6732
- // TODO: might have to send the same event to the developer
6733
- // Add ip address info if geo hint is present
6734
- // @ts-ignore fix type
6735
- options.data.intervalMetadata.peerReflexiveIP =
6736
- // @ts-ignore
6737
- this.webex.meetings.geoHintInfo?.clientAddress ||
6738
- options.data.intervalMetadata.peerReflexiveIP ||
6739
- MQA_STATS.DEFAULT_IP;
6885
+ this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (event) => {
6886
+ // Add IP address from geoHintInfo if missing.
6887
+ if (event.data.intervalMetadata.maskedPeerReflexiveIP === '0.0.0.0') {
6888
+ // @ts-ignore fix type
6889
+ const clientAddressFromGeoHint = this.webex.meetings.geoHintInfo?.clientAddress;
6890
+ if (clientAddressFromGeoHint) {
6891
+ event.data.intervalMetadata.maskedPeerReflexiveIP =
6892
+ CallDiagnosticUtils.anonymizeIPAddress(clientAddressFromGeoHint);
6893
+ }
6894
+ }
6740
6895
 
6896
+ // Count members that are in the meeting.
6741
6897
  const {members} = this.getMembers().membersCollection;
6742
-
6743
- // Count members that are in the meeting
6744
- options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6898
+ event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6745
6899
  (member: Member) => member.isInMeeting
6746
6900
  ).length;
6747
6901
 
@@ -6750,10 +6904,10 @@ export default class Meeting extends StatelessWebexPlugin {
6750
6904
  name: 'client.mediaquality.event',
6751
6905
  options: {
6752
6906
  meetingId: this.id,
6753
- networkType: options.data.networkType,
6907
+ networkType: this.statsAnalyzer.getNetworkType(),
6754
6908
  },
6755
6909
  payload: {
6756
- intervals: [options.data],
6910
+ intervals: [event.data],
6757
6911
  },
6758
6912
  });
6759
6913
  });
@@ -6768,30 +6922,42 @@ export default class Meeting extends StatelessWebexPlugin {
6768
6922
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6769
6923
  data
6770
6924
  );
6771
- // @ts-ignore
6772
- this.webex.internal.newMetrics.submitClientEvent({
6773
- name: 'client.media.tx.start',
6774
- payload: {
6775
- mediaType: data.mediaType,
6776
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6777
- },
6778
- options: {
6779
- meetingId: this.id,
6780
- },
6781
- });
6925
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStart) {
6926
+ // @ts-ignore
6927
+ this.webex.internal.newMetrics.submitClientEvent({
6928
+ name: 'client.media.tx.start',
6929
+ payload: {
6930
+ mediaType: data.mediaType,
6931
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6932
+ },
6933
+ options: {
6934
+ meetingId: this.id,
6935
+ },
6936
+ });
6937
+
6938
+ if (data.mediaType === 'share') {
6939
+ this.shareCAEventSentStatus.transmitStart = true;
6940
+ }
6941
+ }
6782
6942
  });
6783
6943
  this.statsAnalyzer.on(StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED, (data) => {
6784
- // @ts-ignore
6785
- this.webex.internal.newMetrics.submitClientEvent({
6786
- name: 'client.media.tx.stop',
6787
- payload: {
6788
- mediaType: data.mediaType,
6789
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6790
- },
6791
- options: {
6792
- meetingId: this.id,
6793
- },
6794
- });
6944
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStop) {
6945
+ // @ts-ignore
6946
+ this.webex.internal.newMetrics.submitClientEvent({
6947
+ name: 'client.media.tx.stop',
6948
+ payload: {
6949
+ mediaType: data.mediaType,
6950
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6951
+ },
6952
+ options: {
6953
+ meetingId: this.id,
6954
+ },
6955
+ });
6956
+
6957
+ if (data.mediaType === 'share') {
6958
+ this.shareCAEventSentStatus.transmitStop = true;
6959
+ }
6960
+ }
6795
6961
  });
6796
6962
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6797
6963
  Trigger.trigger(
@@ -6803,57 +6969,65 @@ export default class Meeting extends StatelessWebexPlugin {
6803
6969
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6804
6970
  data
6805
6971
  );
6806
- // @ts-ignore
6807
- this.webex.internal.newMetrics.submitClientEvent({
6808
- name: 'client.media.rx.start',
6809
- payload: {
6810
- mediaType: data.mediaType,
6811
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6812
- },
6813
- options: {
6814
- meetingId: this.id,
6815
- },
6816
- });
6817
-
6818
- if (data.mediaType === 'share') {
6972
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6819
6973
  // @ts-ignore
6820
6974
  this.webex.internal.newMetrics.submitClientEvent({
6821
- name: 'client.media.render.start',
6975
+ name: 'client.media.rx.start',
6822
6976
  payload: {
6823
- mediaType: 'share',
6824
- shareInstanceId: this.remoteShareInstanceId,
6977
+ mediaType: data.mediaType,
6978
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6825
6979
  },
6826
6980
  options: {
6827
6981
  meetingId: this.id,
6828
6982
  },
6829
6983
  });
6984
+
6985
+ if (data.mediaType === 'share') {
6986
+ // @ts-ignore
6987
+ this.webex.internal.newMetrics.submitClientEvent({
6988
+ name: 'client.media.render.start',
6989
+ payload: {
6990
+ mediaType: 'share',
6991
+ shareInstanceId: this.remoteShareInstanceId,
6992
+ },
6993
+ options: {
6994
+ meetingId: this.id,
6995
+ },
6996
+ });
6997
+
6998
+ this.shareCAEventSentStatus.receiveStart = true;
6999
+ }
6830
7000
  }
6831
7001
  });
6832
7002
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED, (data) => {
6833
- // @ts-ignore
6834
- this.webex.internal.newMetrics.submitClientEvent({
6835
- name: 'client.media.rx.stop',
6836
- payload: {
6837
- mediaType: data.mediaType,
6838
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6839
- },
6840
- options: {
6841
- meetingId: this.id,
6842
- },
6843
- });
6844
-
6845
- if (data.mediaType === 'share') {
7003
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6846
7004
  // @ts-ignore
6847
7005
  this.webex.internal.newMetrics.submitClientEvent({
6848
- name: 'client.media.render.stop',
7006
+ name: 'client.media.rx.stop',
6849
7007
  payload: {
6850
- mediaType: 'share',
6851
- shareInstanceId: this.remoteShareInstanceId,
7008
+ mediaType: data.mediaType,
7009
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6852
7010
  },
6853
7011
  options: {
6854
7012
  meetingId: this.id,
6855
7013
  },
6856
7014
  });
7015
+
7016
+ if (data.mediaType === 'share') {
7017
+ // @ts-ignore
7018
+ this.webex.internal.newMetrics.submitClientEvent({
7019
+ name: 'client.media.render.stop',
7020
+ payload: {
7021
+ mediaType: 'share',
7022
+ shareInstanceId: this.remoteShareInstanceId,
7023
+ },
7024
+ options: {
7025
+ meetingId: this.id,
7026
+ },
7027
+ });
7028
+
7029
+ this.shareCAEventSentStatus.receiveStop = true;
7030
+ }
6857
7031
  }
6858
7032
  });
6859
7033
  };
@@ -6870,7 +7044,10 @@ export default class Meeting extends StatelessWebexPlugin {
6870
7044
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6871
7045
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6872
7046
  */
6873
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
7047
+ private async createMediaConnection(
7048
+ turnServerInfo?: TurnServerInfo,
7049
+ bundlePolicy?: BundlePolicy
7050
+ ) {
6874
7051
  this.rtcMetrics = this.isMultistream
6875
7052
  ? // @ts-ignore
6876
7053
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6895,6 +7072,13 @@ export default class Meeting extends StatelessWebexPlugin {
6895
7072
  bundlePolicy,
6896
7073
  // @ts-ignore - config coming from registerPlugin
6897
7074
  iceCandidatesTimeout: this.config.iceCandidatesGatheringTimeout,
7075
+ // @ts-ignore - config coming from registerPlugin
7076
+ disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
7077
+ // @ts-ignore - config coming from registerPlugin
7078
+ enableAudioTwcc: this.config.enableAudioTwccForMultistream,
7079
+ stopIceGatheringAfterFirstRelayCandidate:
7080
+ // @ts-ignore - config coming from registerPlugin
7081
+ this.config.stopIceGatheringAfterFirstRelayCandidate,
6898
7082
  }
6899
7083
  );
6900
7084
 
@@ -7045,12 +7229,18 @@ export default class Meeting extends StatelessWebexPlugin {
7045
7229
  },
7046
7230
  options: {
7047
7231
  meetingId: this.id,
7232
+ rawError: error,
7048
7233
  },
7049
7234
  });
7050
7235
  }
7051
- throw new Error(
7236
+
7237
+ const timedOutError = new Error(
7052
7238
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
7053
7239
  );
7240
+
7241
+ timedOutError.cause = error;
7242
+
7243
+ throw timedOutError;
7054
7244
  }
7055
7245
  }
7056
7246
 
@@ -7071,6 +7261,12 @@ export default class Meeting extends StatelessWebexPlugin {
7071
7261
  networkQualityMonitor: this.networkQualityMonitor,
7072
7262
  isMultistream: this.isMultistream,
7073
7263
  });
7264
+ this.shareCAEventSentStatus = {
7265
+ transmitStart: false,
7266
+ transmitStop: false,
7267
+ receiveStart: false,
7268
+ receiveStop: false,
7269
+ };
7074
7270
  this.setupStatsAnalyzerEventHandlers();
7075
7271
  this.networkQualityMonitor.on(
7076
7272
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -7105,6 +7301,9 @@ export default class Meeting extends StatelessWebexPlugin {
7105
7301
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7106
7302
  } seconds`
7107
7303
  );
7304
+
7305
+ const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7306
+
7108
7307
  // @ts-ignore
7109
7308
  this.webex.internal.newMetrics.submitClientEvent({
7110
7309
  name: 'client.media-engine.remote-sdp-received',
@@ -7117,10 +7316,10 @@ export default class Meeting extends StatelessWebexPlugin {
7117
7316
  }),
7118
7317
  ],
7119
7318
  },
7120
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7319
+ options: {meetingId: this.id, rawError: error},
7121
7320
  });
7122
7321
 
7123
- deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
7322
+ deferSDPAnswer.reject(error);
7124
7323
  }, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
7125
7324
 
7126
7325
  LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
@@ -7225,7 +7424,7 @@ export default class Meeting extends StatelessWebexPlugin {
7225
7424
  error
7226
7425
  );
7227
7426
 
7228
- throw new AddMediaFailed();
7427
+ throw new AddMediaFailed(error);
7229
7428
  }
7230
7429
  }
7231
7430
 
@@ -7638,28 +7837,33 @@ export default class Meeting extends StatelessWebexPlugin {
7638
7837
  await this.enqueueScreenShareFloorRequest();
7639
7838
  }
7640
7839
 
7641
- const {connectionType, selectedCandidatePairChanges, numTransports} =
7840
+ const {connectionType, ipVersion, selectedCandidatePairChanges, numTransports} =
7642
7841
  await this.mediaProperties.getCurrentConnectionInfo();
7643
- // @ts-ignore
7644
- const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7842
+
7645
7843
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7646
7844
 
7845
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7846
+
7647
7847
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7648
7848
  correlation_id: this.correlationId,
7649
7849
  locus_id: this.locusUrl.split('/').pop(),
7650
7850
  connectionType,
7851
+ ipVersion,
7651
7852
  selectedCandidatePairChanges,
7652
7853
  numTransports,
7653
7854
  isMultistream: this.isMultistream,
7654
7855
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7655
7856
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7656
- ...reachabilityStats,
7857
+ ...reachabilityMetrics,
7657
7858
  ...iceCandidateErrors,
7658
7859
  iceCandidatesCount: this.iceCandidatesCount,
7659
7860
  });
7660
7861
  // @ts-ignore
7661
7862
  this.webex.internal.newMetrics.submitClientEvent({
7662
7863
  name: 'client.media-engine.ready',
7864
+ payload: {
7865
+ ipVersion,
7866
+ },
7663
7867
  options: {
7664
7868
  meetingId: this.id,
7665
7869
  },
@@ -7675,7 +7879,7 @@ export default class Meeting extends StatelessWebexPlugin {
7675
7879
  LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
7676
7880
 
7677
7881
  // @ts-ignore
7678
- const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
7882
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7679
7883
 
7680
7884
  const {selectedCandidatePairChanges, numTransports} =
7681
7885
  await this.mediaProperties.getCurrentConnectionInfo();
@@ -9095,6 +9299,23 @@ export default class Meeting extends StatelessWebexPlugin {
9095
9299
  });
9096
9300
  }
9097
9301
 
9302
+ /**
9303
+ * Method to set post meeting data consent.
9304
+ *
9305
+ * @param {boolean} accept - whether consent accepted or declined
9306
+ * @returns {Promise}
9307
+ * @public
9308
+ * @memberof Meeting
9309
+ */
9310
+ public setPostMeetingDataConsent(accept: boolean) {
9311
+ return this.meetingRequest.setPostMeetingDataConsent({
9312
+ postMeetingDataConsent: accept,
9313
+ locusUrl: this.locusUrl,
9314
+ deviceUrl: this.deviceUrl,
9315
+ selfId: this.members.selfId,
9316
+ });
9317
+ }
9318
+
9098
9319
  /**
9099
9320
  * Throws if we don't have a media connection created
9100
9321
  *
@@ -9335,6 +9556,8 @@ export default class Meeting extends StatelessWebexPlugin {
9335
9556
 
9336
9557
  if (floorRequestNeeded) {
9337
9558
  this.localShareInstanceId = uuid.v4();
9559
+ this.shareCAEventSentStatus.transmitStart = false;
9560
+ this.shareCAEventSentStatus.transmitStop = false;
9338
9561
 
9339
9562
  // @ts-ignore
9340
9563
  this.webex.internal.newMetrics.submitClientEvent({
@@ -9462,4 +9685,50 @@ export default class Meeting extends StatelessWebexPlugin {
9462
9685
 
9463
9686
  return Promise.resolve();
9464
9687
  }
9688
+
9689
+ /**
9690
+ * Gets the media reachability metrics
9691
+ *
9692
+ * @returns {Promise<MediaReachabilityMetrics>}
9693
+ */
9694
+ private async getMediaReachabilityMetricFields(): Promise<MediaReachabilityMetrics> {
9695
+ const reachabilityMetrics: ReachabilityMetrics =
9696
+ // @ts-ignore
9697
+ await this.webex.meetings.reachability.getReachabilityMetrics();
9698
+
9699
+ const successKeys: Array<keyof ReachabilityMetrics> = [
9700
+ 'reachability_public_udp_success',
9701
+ 'reachability_public_tcp_success',
9702
+ 'reachability_public_xtls_success',
9703
+ 'reachability_vmn_udp_success',
9704
+ 'reachability_vmn_tcp_success',
9705
+ 'reachability_vmn_xtls_success',
9706
+ ];
9707
+
9708
+ const totalSuccessCases = successKeys.reduce((total, key) => {
9709
+ const value = reachabilityMetrics[key];
9710
+ if (typeof value === 'number') {
9711
+ return total + value;
9712
+ }
9713
+
9714
+ return total;
9715
+ }, 0);
9716
+
9717
+ let isSubnetReachable = null;
9718
+ if (totalSuccessCases > 0) {
9719
+ // @ts-ignore
9720
+ isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9721
+ }
9722
+
9723
+ let selectedCluster = null;
9724
+ if (this.mediaConnections && this.mediaConnections.length > 0) {
9725
+ selectedCluster = this.mediaConnections[0].mediaAgentCluster;
9726
+ }
9727
+
9728
+ return {
9729
+ ...reachabilityMetrics,
9730
+ isSubnetReachable,
9731
+ selectedCluster,
9732
+ };
9733
+ }
9465
9734
  }