@webex/plugin-meetings 3.8.0-next.7 → 3.8.0-next.71

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 (167) 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 +4 -1
  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 +32 -12
  19. package/dist/locus-info/index.js.map +1 -1
  20. package/dist/locus-info/selfUtils.js +432 -418
  21. package/dist/locus-info/selfUtils.js.map +1 -1
  22. package/dist/media/index.js +14 -16
  23. package/dist/media/index.js.map +1 -1
  24. package/dist/media/properties.js +94 -6
  25. package/dist/media/properties.js.map +1 -1
  26. package/dist/meeting/brbState.js +6 -0
  27. package/dist/meeting/brbState.js.map +1 -1
  28. package/dist/meeting/in-meeting-actions.js +17 -1
  29. package/dist/meeting/in-meeting-actions.js.map +1 -1
  30. package/dist/meeting/index.js +541 -302
  31. package/dist/meeting/index.js.map +1 -1
  32. package/dist/meeting/locusMediaRequest.js +0 -17
  33. package/dist/meeting/locusMediaRequest.js.map +1 -1
  34. package/dist/meeting/muteState.js +0 -2
  35. package/dist/meeting/muteState.js.map +1 -1
  36. package/dist/meeting/request.js +30 -0
  37. package/dist/meeting/request.js.map +1 -1
  38. package/dist/meeting/request.type.js.map +1 -1
  39. package/dist/meeting/util.js +13 -2
  40. package/dist/meeting/util.js.map +1 -1
  41. package/dist/meeting-info/meeting-info-v2.js +359 -60
  42. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  43. package/dist/meetings/index.js +114 -1
  44. package/dist/meetings/index.js.map +1 -1
  45. package/dist/meetings/util.js +14 -0
  46. package/dist/meetings/util.js.map +1 -1
  47. package/dist/member/index.js +10 -0
  48. package/dist/member/index.js.map +1 -1
  49. package/dist/member/util.js +330 -353
  50. package/dist/member/util.js.map +1 -1
  51. package/dist/members/index.js +23 -0
  52. package/dist/members/index.js.map +1 -1
  53. package/dist/members/request.js +21 -0
  54. package/dist/members/request.js.map +1 -1
  55. package/dist/members/util.js +15 -0
  56. package/dist/members/util.js.map +1 -1
  57. package/dist/metrics/constants.js +9 -0
  58. package/dist/metrics/constants.js.map +1 -1
  59. package/dist/reachability/clusterReachability.js +63 -27
  60. package/dist/reachability/clusterReachability.js.map +1 -1
  61. package/dist/reachability/index.js +112 -47
  62. package/dist/reachability/index.js.map +1 -1
  63. package/dist/reachability/reachability.types.js +14 -0
  64. package/dist/reachability/reachability.types.js.map +1 -1
  65. package/dist/reachability/request.js +19 -3
  66. package/dist/reachability/request.js.map +1 -1
  67. package/dist/reconnection-manager/index.js +2 -2
  68. package/dist/reconnection-manager/index.js.map +1 -1
  69. package/dist/recording-controller/util.js +5 -5
  70. package/dist/recording-controller/util.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 +12 -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 +32 -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 +80 -0
  91. package/dist/types/meetings/index.d.ts +48 -0
  92. package/dist/types/member/index.d.ts +1 -0
  93. package/dist/types/member/util.d.ts +159 -1
  94. package/dist/types/members/index.d.ts +8 -0
  95. package/dist/types/members/request.d.ts +19 -0
  96. package/dist/types/members/util.d.ts +13 -0
  97. package/dist/types/metrics/constants.d.ts +9 -0
  98. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  99. package/dist/types/reachability/index.d.ts +10 -1
  100. package/dist/types/reachability/reachability.types.d.ts +5 -0
  101. package/dist/types/roap/index.d.ts +3 -2
  102. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  103. package/dist/types/roap/types.d.ts +16 -0
  104. package/dist/webinar/index.js +1 -1
  105. package/package.json +24 -23
  106. package/src/common/errors/webex-errors.ts +8 -1
  107. package/src/config.ts +2 -0
  108. package/src/constants.ts +19 -90
  109. package/src/controls-options-manager/enums.ts +2 -0
  110. package/src/controls-options-manager/types.ts +11 -1
  111. package/src/controls-options-manager/util.ts +62 -0
  112. package/src/locus-info/controlsUtils.ts +44 -14
  113. package/src/locus-info/index.ts +38 -12
  114. package/src/locus-info/selfUtils.ts +496 -442
  115. package/src/media/index.ts +20 -21
  116. package/src/media/properties.ts +96 -0
  117. package/src/meeting/brbState.ts +7 -0
  118. package/src/meeting/in-meeting-actions.ts +32 -0
  119. package/src/meeting/index.ts +346 -93
  120. package/src/meeting/locusMediaRequest.ts +0 -18
  121. package/src/meeting/muteState.ts +0 -2
  122. package/src/meeting/request.ts +36 -1
  123. package/src/meeting/request.type.ts +7 -0
  124. package/src/meeting/util.ts +11 -2
  125. package/src/meeting-info/meeting-info-v2.ts +247 -6
  126. package/src/meetings/index.ts +128 -1
  127. package/src/meetings/util.ts +18 -0
  128. package/src/member/index.ts +13 -2
  129. package/src/member/util.ts +351 -348
  130. package/src/members/index.ts +25 -0
  131. package/src/members/request.ts +26 -0
  132. package/src/members/util.ts +16 -0
  133. package/src/metrics/constants.ts +9 -0
  134. package/src/reachability/clusterReachability.ts +73 -26
  135. package/src/reachability/index.ts +70 -1
  136. package/src/reachability/reachability.types.ts +6 -0
  137. package/src/reachability/request.ts +7 -0
  138. package/src/reconnection-manager/index.ts +2 -2
  139. package/src/recording-controller/util.ts +17 -13
  140. package/src/roap/index.ts +3 -7
  141. package/src/roap/turnDiscovery.ts +34 -39
  142. package/src/roap/types.ts +23 -0
  143. package/test/unit/spec/controls-options-manager/util.js +120 -0
  144. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  145. package/test/unit/spec/locus-info/index.js +141 -73
  146. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  147. package/test/unit/spec/media/index.ts +98 -16
  148. package/test/unit/spec/media/properties.ts +130 -0
  149. package/test/unit/spec/meeting/brbState.ts +19 -0
  150. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  151. package/test/unit/spec/meeting/index.js +524 -35
  152. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  153. package/test/unit/spec/meeting/muteState.js +0 -2
  154. package/test/unit/spec/meeting/request.js +32 -1
  155. package/test/unit/spec/meeting/utils.js +119 -18
  156. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  157. package/test/unit/spec/meetings/index.js +133 -2
  158. package/test/unit/spec/member/index.js +7 -0
  159. package/test/unit/spec/member/util.js +24 -0
  160. package/test/unit/spec/members/index.js +103 -26
  161. package/test/unit/spec/members/request.js +45 -22
  162. package/test/unit/spec/members/utils.js +33 -0
  163. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  164. package/test/unit/spec/reachability/index.ts +101 -0
  165. package/test/unit/spec/reachability/request.js +47 -2
  166. package/test/unit/spec/reconnection-manager/index.js +4 -4
  167. 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,8 @@ type FetchMeetingInfoParams = {
265
262
  sendCAevents?: boolean;
266
263
  };
267
264
 
265
+ type MediaReachabilityMetrics = ReachabilityMetrics & {isSubnetReachable: boolean};
266
+
268
267
  /**
269
268
  * MediaDirection
270
269
  * @typedef {Object} MediaDirection
@@ -650,6 +649,13 @@ export default class Meeting extends StatelessWebexPlugin {
650
649
  allowMediaInLobby: boolean;
651
650
  localShareInstanceId: string;
652
651
  remoteShareInstanceId: string;
652
+ shareCAEventSentStatus: {
653
+ transmitStart: boolean;
654
+ transmitStop: boolean;
655
+ receiveStart: boolean;
656
+ receiveStop: boolean;
657
+ };
658
+
653
659
  turnDiscoverySkippedReason: TurnDiscoverySkipReason;
654
660
  turnServerUsed: boolean;
655
661
  areVoiceaEventsSetup = false;
@@ -718,6 +724,7 @@ export default class Meeting extends StatelessWebexPlugin {
718
724
  private rtcMetrics?: RtcMetrics;
719
725
  private uploadLogsTimer?: ReturnType<typeof setTimeout>;
720
726
  private logUploadIntervalIndex: number;
727
+ private mediaServerIp: string;
721
728
 
722
729
  /**
723
730
  * @param {Object} attrs
@@ -1422,6 +1429,19 @@ export default class Meeting extends StatelessWebexPlugin {
1422
1429
  */
1423
1430
  this.remoteShareInstanceId = null;
1424
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
+
1425
1445
  /**
1426
1446
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1427
1447
  * @instance
@@ -1581,6 +1601,19 @@ export default class Meeting extends StatelessWebexPlugin {
1581
1601
  * @memberof Meeting
1582
1602
  */
1583
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;
1584
1617
  }
1585
1618
 
1586
1619
  /**
@@ -1686,6 +1719,33 @@ export default class Meeting extends StatelessWebexPlugin {
1686
1719
  return this.#isoLocalClientMeetingJoinTime;
1687
1720
  }
1688
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
+
1689
1749
  /**
1690
1750
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1691
1751
  * @param {any} info
@@ -2827,6 +2887,24 @@ export default class Meeting extends StatelessWebexPlugin {
2827
2887
  {state}
2828
2888
  );
2829
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
+ });
2830
2908
  }
2831
2909
 
2832
2910
  /**
@@ -2999,6 +3077,8 @@ export default class Meeting extends StatelessWebexPlugin {
2999
3077
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
3000
3078
  const sendStartedSharingRemote = () => {
3001
3079
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3080
+ this.shareCAEventSentStatus.receiveStart = false;
3081
+ this.shareCAEventSentStatus.receiveStop = false;
3002
3082
 
3003
3083
  Trigger.trigger(
3004
3084
  this,
@@ -3052,6 +3132,7 @@ export default class Meeting extends StatelessWebexPlugin {
3052
3132
  },
3053
3133
  options: {meetingId: this.id},
3054
3134
  });
3135
+
3055
3136
  break;
3056
3137
 
3057
3138
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3092,6 +3173,8 @@ export default class Meeting extends StatelessWebexPlugin {
3092
3173
  // if we got here, then some remote participant has stolen
3093
3174
  // the presentation from another remote participant
3094
3175
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3176
+ this.shareCAEventSentStatus.receiveStart = false;
3177
+ this.shareCAEventSentStatus.receiveStop = false;
3095
3178
 
3096
3179
  Trigger.trigger(
3097
3180
  this,
@@ -3775,7 +3858,13 @@ export default class Meeting extends StatelessWebexPlugin {
3775
3858
  return Promise.reject(error);
3776
3859
  }
3777
3860
 
3778
- 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
+ });
3779
3868
  }
3780
3869
 
3781
3870
  /**
@@ -3967,7 +4056,10 @@ export default class Meeting extends StatelessWebexPlugin {
3967
4056
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3968
4057
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3969
4058
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3970
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4059
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4060
+ this.userDisplayHints,
4061
+ this.selfUserPolicies
4062
+ ),
3971
4063
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3972
4064
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
3973
4065
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -4016,6 +4108,9 @@ export default class Meeting extends StatelessWebexPlugin {
4016
4108
  this.inMeetingActions.canSendReactions,
4017
4109
  this.userDisplayHints
4018
4110
  ),
4111
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4112
+ this.userDisplayHints
4113
+ ),
4019
4114
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
4020
4115
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
4021
4116
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -4031,6 +4126,7 @@ export default class Meeting extends StatelessWebexPlugin {
4031
4126
  this.userDisplayHints
4032
4127
  ),
4033
4128
  canUserRenameOthers: MeetingUtil.canUserRenameOthers(this.userDisplayHints),
4129
+ canMoveToLobby: MeetingUtil.canMoveToLobby(this.userDisplayHints),
4034
4130
  canMuteAll: ControlsOptionsUtil.hasHints({
4035
4131
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
4036
4132
  displayHints: this.userDisplayHints,
@@ -4165,6 +4261,14 @@ export default class Meeting extends StatelessWebexPlugin {
4165
4261
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
4166
4262
  policies: this.selfUserPolicies,
4167
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
+ }),
4168
4272
  canChat: ControlsOptionsUtil.hasPolicies({
4169
4273
  requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
4170
4274
  policies: this.selfUserPolicies,
@@ -4211,6 +4315,22 @@ export default class Meeting extends StatelessWebexPlugin {
4211
4315
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4212
4316
  policies: this.selfUserPolicies,
4213
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
+ }),
4214
4334
  }) || changed;
4215
4335
  }
4216
4336
  if (changed) {
@@ -5718,8 +5838,6 @@ export default class Meeting extends StatelessWebexPlugin {
5718
5838
  // @ts-ignore
5719
5839
  this.webex.internal.device.meetingStarted();
5720
5840
 
5721
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5722
-
5723
5841
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5724
5842
 
5725
5843
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6180,10 +6298,7 @@ export default class Meeting extends StatelessWebexPlugin {
6180
6298
  },
6181
6299
  options: {meetingId: this.id, rawError: error},
6182
6300
  });
6183
- } else if (
6184
- error instanceof Errors.SdpOfferHandlingError ||
6185
- error instanceof Errors.SdpAnswerHandlingError
6186
- ) {
6301
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6187
6302
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6188
6303
 
6189
6304
  // @ts-ignore
@@ -6194,6 +6309,24 @@ export default class Meeting extends StatelessWebexPlugin {
6194
6309
  },
6195
6310
  options: {meetingId: this.id, rawError: error},
6196
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
+ }
6197
6330
  } else if (error instanceof Errors.SdpError) {
6198
6331
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6199
6332
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -6221,6 +6354,11 @@ export default class Meeting extends StatelessWebexPlugin {
6221
6354
  ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6222
6355
  : undefined;
6223
6356
 
6357
+ const mediaServerIp =
6358
+ roapMessage.messageType === 'ANSWER'
6359
+ ? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6360
+ : undefined;
6361
+
6224
6362
  if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6225
6363
  throw new MultistreamNotSupportedError(
6226
6364
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6231,6 +6369,10 @@ export default class Meeting extends StatelessWebexPlugin {
6231
6369
  if (mediaServer) {
6232
6370
  this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
6233
6371
  }
6372
+
6373
+ if (this.isMultistream && mediaServerIp) {
6374
+ this.mediaServerIp = mediaServerIp;
6375
+ }
6234
6376
  };
6235
6377
 
6236
6378
  /**
@@ -6688,20 +6830,20 @@ export default class Meeting extends StatelessWebexPlugin {
6688
6830
  * @memberof Meetings
6689
6831
  */
6690
6832
  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;
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
+ }
6700
6843
 
6844
+ // Count members that are in the meeting.
6701
6845
  const {members} = this.getMembers().membersCollection;
6702
-
6703
- // Count members that are in the meeting
6704
- options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6846
+ event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6705
6847
  (member: Member) => member.isInMeeting
6706
6848
  ).length;
6707
6849
 
@@ -6710,10 +6852,10 @@ export default class Meeting extends StatelessWebexPlugin {
6710
6852
  name: 'client.mediaquality.event',
6711
6853
  options: {
6712
6854
  meetingId: this.id,
6713
- networkType: options.data.networkType,
6855
+ networkType: this.statsAnalyzer.getNetworkType(),
6714
6856
  },
6715
6857
  payload: {
6716
- intervals: [options.data],
6858
+ intervals: [event.data],
6717
6859
  },
6718
6860
  });
6719
6861
  });
@@ -6728,30 +6870,42 @@ export default class Meeting extends StatelessWebexPlugin {
6728
6870
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6729
6871
  data
6730
6872
  );
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
- });
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
+ }
6742
6890
  });
6743
6891
  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
- });
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
+ }
6755
6909
  });
6756
6910
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6757
6911
  Trigger.trigger(
@@ -6763,57 +6917,65 @@ export default class Meeting extends StatelessWebexPlugin {
6763
6917
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6764
6918
  data
6765
6919
  );
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') {
6920
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6779
6921
  // @ts-ignore
6780
6922
  this.webex.internal.newMetrics.submitClientEvent({
6781
- name: 'client.media.render.start',
6923
+ name: 'client.media.rx.start',
6782
6924
  payload: {
6783
- mediaType: 'share',
6784
- shareInstanceId: this.remoteShareInstanceId,
6925
+ mediaType: data.mediaType,
6926
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6785
6927
  },
6786
6928
  options: {
6787
6929
  meetingId: this.id,
6788
6930
  },
6789
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
+ }
6790
6948
  }
6791
6949
  });
6792
6950
  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') {
6951
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6806
6952
  // @ts-ignore
6807
6953
  this.webex.internal.newMetrics.submitClientEvent({
6808
- name: 'client.media.render.stop',
6954
+ name: 'client.media.rx.stop',
6809
6955
  payload: {
6810
- mediaType: 'share',
6811
- shareInstanceId: this.remoteShareInstanceId,
6956
+ mediaType: data.mediaType,
6957
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6812
6958
  },
6813
6959
  options: {
6814
6960
  meetingId: this.id,
6815
6961
  },
6816
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
+ }
6817
6979
  }
6818
6980
  });
6819
6981
  };
@@ -6830,7 +6992,10 @@ export default class Meeting extends StatelessWebexPlugin {
6830
6992
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6831
6993
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6832
6994
  */
6833
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6995
+ private async createMediaConnection(
6996
+ turnServerInfo?: TurnServerInfo,
6997
+ bundlePolicy?: BundlePolicy
6998
+ ) {
6834
6999
  this.rtcMetrics = this.isMultistream
6835
7000
  ? // @ts-ignore
6836
7001
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6855,6 +7020,11 @@ export default class Meeting extends StatelessWebexPlugin {
6855
7020
  bundlePolicy,
6856
7021
  // @ts-ignore - config coming from registerPlugin
6857
7022
  iceCandidatesTimeout: this.config.iceCandidatesGatheringTimeout,
7023
+ // @ts-ignore - config coming from registerPlugin
7024
+ disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
7025
+ stopIceGatheringAfterFirstRelayCandidate:
7026
+ // @ts-ignore - config coming from registerPlugin
7027
+ this.config.stopIceGatheringAfterFirstRelayCandidate,
6858
7028
  }
6859
7029
  );
6860
7030
 
@@ -7005,12 +7175,18 @@ export default class Meeting extends StatelessWebexPlugin {
7005
7175
  },
7006
7176
  options: {
7007
7177
  meetingId: this.id,
7178
+ rawError: error,
7008
7179
  },
7009
7180
  });
7010
7181
  }
7011
- throw new Error(
7182
+
7183
+ const timedOutError = new Error(
7012
7184
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
7013
7185
  );
7186
+
7187
+ timedOutError.cause = error;
7188
+
7189
+ throw timedOutError;
7014
7190
  }
7015
7191
  }
7016
7192
 
@@ -7031,6 +7207,12 @@ export default class Meeting extends StatelessWebexPlugin {
7031
7207
  networkQualityMonitor: this.networkQualityMonitor,
7032
7208
  isMultistream: this.isMultistream,
7033
7209
  });
7210
+ this.shareCAEventSentStatus = {
7211
+ transmitStart: false,
7212
+ transmitStop: false,
7213
+ receiveStart: false,
7214
+ receiveStop: false,
7215
+ };
7034
7216
  this.setupStatsAnalyzerEventHandlers();
7035
7217
  this.networkQualityMonitor.on(
7036
7218
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -7065,6 +7247,9 @@ export default class Meeting extends StatelessWebexPlugin {
7065
7247
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7066
7248
  } seconds`
7067
7249
  );
7250
+
7251
+ const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7252
+
7068
7253
  // @ts-ignore
7069
7254
  this.webex.internal.newMetrics.submitClientEvent({
7070
7255
  name: 'client.media-engine.remote-sdp-received',
@@ -7077,10 +7262,10 @@ export default class Meeting extends StatelessWebexPlugin {
7077
7262
  }),
7078
7263
  ],
7079
7264
  },
7080
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7265
+ options: {meetingId: this.id, rawError: error},
7081
7266
  });
7082
7267
 
7083
- deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
7268
+ deferSDPAnswer.reject(error);
7084
7269
  }, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
7085
7270
 
7086
7271
  LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
@@ -7185,7 +7370,7 @@ export default class Meeting extends StatelessWebexPlugin {
7185
7370
  error
7186
7371
  );
7187
7372
 
7188
- throw new AddMediaFailed();
7373
+ throw new AddMediaFailed(error);
7189
7374
  }
7190
7375
  }
7191
7376
 
@@ -7598,28 +7783,33 @@ export default class Meeting extends StatelessWebexPlugin {
7598
7783
  await this.enqueueScreenShareFloorRequest();
7599
7784
  }
7600
7785
 
7601
- const {connectionType, selectedCandidatePairChanges, numTransports} =
7786
+ const {connectionType, ipVersion, selectedCandidatePairChanges, numTransports} =
7602
7787
  await this.mediaProperties.getCurrentConnectionInfo();
7603
- // @ts-ignore
7604
- const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7788
+
7605
7789
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7606
7790
 
7791
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7792
+
7607
7793
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7608
7794
  correlation_id: this.correlationId,
7609
7795
  locus_id: this.locusUrl.split('/').pop(),
7610
7796
  connectionType,
7797
+ ipVersion,
7611
7798
  selectedCandidatePairChanges,
7612
7799
  numTransports,
7613
7800
  isMultistream: this.isMultistream,
7614
7801
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7615
7802
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7616
- ...reachabilityStats,
7803
+ ...reachabilityMetrics,
7617
7804
  ...iceCandidateErrors,
7618
7805
  iceCandidatesCount: this.iceCandidatesCount,
7619
7806
  });
7620
7807
  // @ts-ignore
7621
7808
  this.webex.internal.newMetrics.submitClientEvent({
7622
7809
  name: 'client.media-engine.ready',
7810
+ payload: {
7811
+ ipVersion,
7812
+ },
7623
7813
  options: {
7624
7814
  meetingId: this.id,
7625
7815
  },
@@ -7635,7 +7825,7 @@ export default class Meeting extends StatelessWebexPlugin {
7635
7825
  LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
7636
7826
 
7637
7827
  // @ts-ignore
7638
- const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
7828
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7639
7829
 
7640
7830
  const {selectedCandidatePairChanges, numTransports} =
7641
7831
  await this.mediaProperties.getCurrentConnectionInfo();
@@ -8715,6 +8905,9 @@ export default class Meeting extends StatelessWebexPlugin {
8715
8905
  LoggerProxy.logger.log(
8716
8906
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8717
8907
  );
8908
+
8909
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8910
+
8718
8911
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8719
8912
  correlationId: this.correlationId,
8720
8913
  muted,
@@ -8723,8 +8916,9 @@ export default class Meeting extends StatelessWebexPlugin {
8723
8916
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8724
8917
  // all we're doing here is adding metrics.
8725
8918
  // eslint-disable-next-line dot-notation
8726
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8919
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8727
8920
  isMultistream: this.isMultistream,
8921
+ frameRate: shareVideoStreamSettings?.frameRate,
8728
8922
  });
8729
8923
  };
8730
8924
 
@@ -9051,6 +9245,23 @@ export default class Meeting extends StatelessWebexPlugin {
9051
9245
  });
9052
9246
  }
9053
9247
 
9248
+ /**
9249
+ * Method to set post meeting data consent.
9250
+ *
9251
+ * @param {boolean} accept - whether consent accepted or declined
9252
+ * @returns {Promise}
9253
+ * @public
9254
+ * @memberof Meeting
9255
+ */
9256
+ public setPostMeetingDataConsent(accept: boolean) {
9257
+ return this.meetingRequest.setPostMeetingDataConsent({
9258
+ postMeetingDataConsent: accept,
9259
+ locusUrl: this.locusUrl,
9260
+ deviceUrl: this.deviceUrl,
9261
+ selfId: this.members.selfId,
9262
+ });
9263
+ }
9264
+
9054
9265
  /**
9055
9266
  * Throws if we don't have a media connection created
9056
9267
  *
@@ -9291,6 +9502,8 @@ export default class Meeting extends StatelessWebexPlugin {
9291
9502
 
9292
9503
  if (floorRequestNeeded) {
9293
9504
  this.localShareInstanceId = uuid.v4();
9505
+ this.shareCAEventSentStatus.transmitStart = false;
9506
+ this.shareCAEventSentStatus.transmitStop = false;
9294
9507
 
9295
9508
  // @ts-ignore
9296
9509
  this.webex.internal.newMetrics.submitClientEvent({
@@ -9418,4 +9631,44 @@ export default class Meeting extends StatelessWebexPlugin {
9418
9631
 
9419
9632
  return Promise.resolve();
9420
9633
  }
9634
+
9635
+ /**
9636
+ * Gets the media reachability metrics
9637
+ *
9638
+ * @returns {Promise<MediaReachabilityMetrics>}
9639
+ */
9640
+ private async getMediaReachabilityMetricFields(): Promise<MediaReachabilityMetrics> {
9641
+ const reachabilityMetrics: ReachabilityMetrics =
9642
+ // @ts-ignore
9643
+ await this.webex.meetings.reachability.getReachabilityMetrics();
9644
+
9645
+ const successKeys: Array<keyof ReachabilityMetrics> = [
9646
+ 'reachability_public_udp_success',
9647
+ 'reachability_public_tcp_success',
9648
+ 'reachability_public_xtls_success',
9649
+ 'reachability_vmn_udp_success',
9650
+ 'reachability_vmn_tcp_success',
9651
+ 'reachability_vmn_xtls_success',
9652
+ ];
9653
+
9654
+ const totalSuccessCases = successKeys.reduce((total, key) => {
9655
+ const value = reachabilityMetrics[key];
9656
+ if (typeof value === 'number') {
9657
+ return total + value;
9658
+ }
9659
+
9660
+ return total;
9661
+ }, 0);
9662
+
9663
+ let isSubnetReachable = null;
9664
+ if (totalSuccessCases > 0) {
9665
+ // @ts-ignore
9666
+ isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9667
+ }
9668
+
9669
+ return {
9670
+ ...reachabilityMetrics,
9671
+ isSubnetReachable,
9672
+ };
9673
+ }
9421
9674
  }