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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/webex-errors.js +12 -2
  4. package/dist/common/errors/webex-errors.js.map +1 -1
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +16 -121
  8. package/dist/constants.js.map +1 -1
  9. package/dist/controls-options-manager/enums.js +2 -0
  10. package/dist/controls-options-manager/enums.js.map +1 -1
  11. package/dist/controls-options-manager/types.js.map +1 -1
  12. package/dist/controls-options-manager/util.js +52 -0
  13. package/dist/controls-options-manager/util.js.map +1 -1
  14. package/dist/interpretation/index.js +1 -1
  15. package/dist/interpretation/siLanguage.js +1 -1
  16. package/dist/locus-info/controlsUtils.js +28 -10
  17. package/dist/locus-info/controlsUtils.js.map +1 -1
  18. package/dist/locus-info/index.js +20 -1
  19. package/dist/locus-info/index.js.map +1 -1
  20. package/dist/locus-info/selfUtils.js +405 -418
  21. package/dist/locus-info/selfUtils.js.map +1 -1
  22. package/dist/media/index.js +8 -16
  23. package/dist/media/index.js.map +1 -1
  24. package/dist/meeting/in-meeting-actions.js +13 -1
  25. package/dist/meeting/in-meeting-actions.js.map +1 -1
  26. package/dist/meeting/index.js +548 -288
  27. package/dist/meeting/index.js.map +1 -1
  28. package/dist/meeting/locusMediaRequest.js +0 -17
  29. package/dist/meeting/locusMediaRequest.js.map +1 -1
  30. package/dist/meeting/muteState.js +0 -2
  31. package/dist/meeting/muteState.js.map +1 -1
  32. package/dist/meeting/request.js +30 -0
  33. package/dist/meeting/request.js.map +1 -1
  34. package/dist/meeting/request.type.js.map +1 -1
  35. package/dist/meeting/util.js +13 -2
  36. package/dist/meeting/util.js.map +1 -1
  37. package/dist/meeting-info/meeting-info-v2.js +359 -60
  38. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  39. package/dist/meetings/index.js +91 -1
  40. package/dist/meetings/index.js.map +1 -1
  41. package/dist/meetings/util.js +14 -0
  42. package/dist/meetings/util.js.map +1 -1
  43. package/dist/member/index.js +10 -0
  44. package/dist/member/index.js.map +1 -1
  45. package/dist/member/util.js +3 -0
  46. package/dist/member/util.js.map +1 -1
  47. package/dist/members/index.js +23 -0
  48. package/dist/members/index.js.map +1 -1
  49. package/dist/members/request.js +21 -0
  50. package/dist/members/request.js.map +1 -1
  51. package/dist/members/util.js +15 -0
  52. package/dist/members/util.js.map +1 -1
  53. package/dist/metrics/constants.js +9 -0
  54. package/dist/metrics/constants.js.map +1 -1
  55. package/dist/reachability/clusterReachability.js +63 -27
  56. package/dist/reachability/clusterReachability.js.map +1 -1
  57. package/dist/reachability/index.js +112 -47
  58. package/dist/reachability/index.js.map +1 -1
  59. package/dist/reachability/reachability.types.js +14 -0
  60. package/dist/reachability/reachability.types.js.map +1 -1
  61. package/dist/reachability/request.js +19 -3
  62. package/dist/reachability/request.js.map +1 -1
  63. package/dist/reconnection-manager/index.js +2 -2
  64. package/dist/reconnection-manager/index.js.map +1 -1
  65. package/dist/recording-controller/util.js +5 -5
  66. package/dist/recording-controller/util.js.map +1 -1
  67. package/dist/roap/index.js.map +1 -1
  68. package/dist/roap/turnDiscovery.js +45 -27
  69. package/dist/roap/turnDiscovery.js.map +1 -1
  70. package/dist/roap/types.js +17 -0
  71. package/dist/roap/types.js.map +1 -0
  72. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  73. package/dist/types/config.d.ts +1 -0
  74. package/dist/types/constants.d.ts +11 -85
  75. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  76. package/dist/types/controls-options-manager/types.d.ts +7 -1
  77. package/dist/types/locus-info/index.d.ts +1 -0
  78. package/dist/types/locus-info/selfUtils.d.ts +247 -1
  79. package/dist/types/meeting/in-meeting-actions.d.ts +12 -0
  80. package/dist/types/meeting/index.d.ts +54 -1
  81. package/dist/types/meeting/muteState.d.ts +0 -1
  82. package/dist/types/meeting/request.d.ts +12 -1
  83. package/dist/types/meeting/request.type.d.ts +6 -0
  84. package/dist/types/meeting/util.d.ts +3 -1
  85. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  86. package/dist/types/meetings/index.d.ts +38 -0
  87. package/dist/types/member/index.d.ts +1 -0
  88. package/dist/types/members/index.d.ts +8 -0
  89. package/dist/types/members/request.d.ts +19 -0
  90. package/dist/types/members/util.d.ts +13 -0
  91. package/dist/types/metrics/constants.d.ts +9 -0
  92. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  93. package/dist/types/reachability/index.d.ts +10 -1
  94. package/dist/types/reachability/reachability.types.d.ts +5 -0
  95. package/dist/types/roap/index.d.ts +3 -2
  96. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  97. package/dist/types/roap/types.d.ts +16 -0
  98. package/dist/webinar/index.js +1 -1
  99. package/package.json +23 -23
  100. package/src/common/errors/webex-errors.ts +8 -1
  101. package/src/config.ts +1 -0
  102. package/src/constants.ts +18 -90
  103. package/src/controls-options-manager/enums.ts +2 -0
  104. package/src/controls-options-manager/types.ts +11 -1
  105. package/src/controls-options-manager/util.ts +62 -0
  106. package/src/locus-info/controlsUtils.ts +44 -14
  107. package/src/locus-info/index.ts +23 -1
  108. package/src/locus-info/selfUtils.ts +451 -447
  109. package/src/media/index.ts +11 -21
  110. package/src/meeting/in-meeting-actions.ts +24 -0
  111. package/src/meeting/index.ts +364 -92
  112. package/src/meeting/locusMediaRequest.ts +0 -18
  113. package/src/meeting/muteState.ts +0 -2
  114. package/src/meeting/request.ts +36 -1
  115. package/src/meeting/request.type.ts +7 -0
  116. package/src/meeting/util.ts +11 -2
  117. package/src/meeting-info/meeting-info-v2.ts +247 -6
  118. package/src/meetings/index.ts +107 -1
  119. package/src/meetings/util.ts +18 -0
  120. package/src/member/index.ts +11 -0
  121. package/src/member/util.ts +3 -0
  122. package/src/members/index.ts +25 -0
  123. package/src/members/request.ts +26 -0
  124. package/src/members/util.ts +16 -0
  125. package/src/metrics/constants.ts +9 -0
  126. package/src/reachability/clusterReachability.ts +73 -26
  127. package/src/reachability/index.ts +70 -1
  128. package/src/reachability/reachability.types.ts +6 -0
  129. package/src/reachability/request.ts +7 -0
  130. package/src/reconnection-manager/index.ts +2 -2
  131. package/src/recording-controller/util.ts +17 -13
  132. package/src/roap/index.ts +3 -7
  133. package/src/roap/turnDiscovery.ts +34 -39
  134. package/src/roap/types.ts +23 -0
  135. package/test/unit/spec/controls-options-manager/util.js +120 -0
  136. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  137. package/test/unit/spec/locus-info/index.js +28 -0
  138. package/test/unit/spec/media/index.ts +36 -16
  139. package/test/unit/spec/meeting/in-meeting-actions.ts +15 -4
  140. package/test/unit/spec/meeting/index.js +518 -34
  141. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  142. package/test/unit/spec/meeting/muteState.js +0 -2
  143. package/test/unit/spec/meeting/request.js +32 -1
  144. package/test/unit/spec/meeting/utils.js +119 -18
  145. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  146. package/test/unit/spec/meetings/index.js +120 -2
  147. package/test/unit/spec/member/index.js +7 -0
  148. package/test/unit/spec/member/util.js +24 -0
  149. package/test/unit/spec/members/index.js +103 -26
  150. package/test/unit/spec/members/request.js +45 -22
  151. package/test/unit/spec/members/utils.js +33 -0
  152. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  153. package/test/unit/spec/reachability/index.ts +101 -0
  154. package/test/unit/spec/reachability/request.js +47 -2
  155. package/test/unit/spec/reconnection-manager/index.js +4 -4
  156. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -60,11 +60,8 @@ import {
60
60
  import LoggerProxy from '../common/logs/logger-proxy';
61
61
  import EventsUtil from '../common/events/util';
62
62
  import Trigger from '../common/events/trigger-proxy';
63
- import Roap, {
64
- type TurnDiscoveryResult,
65
- type TurnServerInfo,
66
- type TurnDiscoverySkipReason,
67
- } from '../roap/index';
63
+ import Roap, {type TurnDiscoveryResult, type TurnDiscoverySkipReason} from '../roap/index';
64
+ import {type TurnServerInfo} from '../roap/types';
68
65
  import Media, {type BundlePolicy} from '../media';
69
66
  import MediaProperties from '../media/properties';
70
67
  import MeetingStateMachine from './state';
@@ -103,7 +100,6 @@ import {
103
100
  MEETING_STATE_MACHINE,
104
101
  MEETING_STATE,
105
102
  MEETINGS,
106
- MQA_STATS,
107
103
  NETWORK_STATUS,
108
104
  ONLINE,
109
105
  OFFLINE,
@@ -167,6 +163,7 @@ import Member from '../member';
167
163
  import {BrbState, createBrbState} from './brbState';
168
164
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
169
165
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
166
+ import {ReachabilityMetrics} from '../reachability/reachability.types';
170
167
 
171
168
  // default callback so we don't call an undefined function, but in practice it should never be used
172
169
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -241,6 +238,8 @@ export type CallStateForMetrics = {
241
238
  sessionCorrelationId?: string;
242
239
  joinTrigger?: string;
243
240
  loginType?: string;
241
+ userNameInput?: string;
242
+ emailInput?: string;
244
243
  };
245
244
 
246
245
  export const MEDIA_UPDATE_TYPE = {
@@ -263,6 +262,8 @@ type FetchMeetingInfoParams = {
263
262
  sendCAevents?: boolean;
264
263
  };
265
264
 
265
+ type MediaReachabilityMetrics = ReachabilityMetrics & {isSubnetReachable: boolean};
266
+
266
267
  /**
267
268
  * MediaDirection
268
269
  * @typedef {Object} MediaDirection
@@ -648,6 +649,13 @@ export default class Meeting extends StatelessWebexPlugin {
648
649
  allowMediaInLobby: boolean;
649
650
  localShareInstanceId: string;
650
651
  remoteShareInstanceId: string;
652
+ shareCAEventSentStatus: {
653
+ transmitStart: boolean;
654
+ transmitStop: boolean;
655
+ receiveStart: boolean;
656
+ receiveStop: boolean;
657
+ };
658
+
651
659
  turnDiscoverySkippedReason: TurnDiscoverySkipReason;
652
660
  turnServerUsed: boolean;
653
661
  areVoiceaEventsSetup = false;
@@ -716,6 +724,7 @@ export default class Meeting extends StatelessWebexPlugin {
716
724
  private rtcMetrics?: RtcMetrics;
717
725
  private uploadLogsTimer?: ReturnType<typeof setTimeout>;
718
726
  private logUploadIntervalIndex: number;
727
+ private mediaServerIp: string;
719
728
 
720
729
  /**
721
730
  * @param {Object} attrs
@@ -1420,6 +1429,19 @@ export default class Meeting extends StatelessWebexPlugin {
1420
1429
  */
1421
1430
  this.remoteShareInstanceId = null;
1422
1431
 
1432
+ /**
1433
+ * Status used for ensuring we do not oversend metrics
1434
+ * @instance
1435
+ * @private
1436
+ * @memberof Meeting
1437
+ */
1438
+ this.shareCAEventSentStatus = {
1439
+ transmitStart: false,
1440
+ transmitStop: false,
1441
+ receiveStart: false,
1442
+ receiveStop: false,
1443
+ };
1444
+
1423
1445
  /**
1424
1446
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1425
1447
  * @instance
@@ -1579,6 +1601,19 @@ export default class Meeting extends StatelessWebexPlugin {
1579
1601
  * @memberof Meeting
1580
1602
  */
1581
1603
  this.#isoLocalClientMeetingJoinTime = undefined;
1604
+
1605
+ // We clear the error cache of CA events on every new meeting instance
1606
+ // @ts-ignore - Fix type
1607
+ this.webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache();
1608
+
1609
+ /**
1610
+ * IP Address of the remote media server
1611
+ * @instance
1612
+ * @type {string}
1613
+ * @private
1614
+ * @memberof Meeting
1615
+ */
1616
+ this.mediaServerIp = undefined;
1582
1617
  }
1583
1618
 
1584
1619
  /**
@@ -1627,6 +1662,38 @@ export default class Meeting extends StatelessWebexPlugin {
1627
1662
  this.callStateForMetrics.correlationId = correlationId;
1628
1663
  }
1629
1664
 
1665
+ /**
1666
+ * Getter - Returns callStateForMetrics.userNameInput
1667
+ * @returns {string}
1668
+ */
1669
+ get userNameInput() {
1670
+ return this.callStateForMetrics?.userNameInput;
1671
+ }
1672
+
1673
+ /**
1674
+ * Setter - sets callStateForMetrics.userNameInput
1675
+ * @param {string} userNameInput
1676
+ */
1677
+ set userNameInput(userNameInput: string) {
1678
+ this.callStateForMetrics.userNameInput = userNameInput;
1679
+ }
1680
+
1681
+ /**
1682
+ * Getter - Returns callStateForMetrics.emailInput
1683
+ * @returns {string}
1684
+ */
1685
+ get emailInput() {
1686
+ return this.callStateForMetrics?.emailInput;
1687
+ }
1688
+
1689
+ /**
1690
+ * Setter - sets callStateForMetrics.emailInput
1691
+ * @param {string} emailInput
1692
+ */
1693
+ set emailInput(emailInput: string) {
1694
+ this.callStateForMetrics.emailInput = emailInput;
1695
+ }
1696
+
1630
1697
  /**
1631
1698
  * Getter - Returns callStateForMetrics.sessionCorrelationId
1632
1699
  * @returns {string}
@@ -1652,6 +1719,33 @@ export default class Meeting extends StatelessWebexPlugin {
1652
1719
  return this.#isoLocalClientMeetingJoinTime;
1653
1720
  }
1654
1721
 
1722
+ /**
1723
+ * Setter - sets isoLocalClientMeetingJoinTime
1724
+ * This will be set once on meeting join, and not updated again
1725
+ * this will always produce an ISO string
1726
+ * If the iso string is invalid, it will fallback to the current system time
1727
+ * @param {string | undefined} time
1728
+ */
1729
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1730
+ const fallback = new Date().toISOString();
1731
+ if (!time) {
1732
+ this.#isoLocalClientMeetingJoinTime = fallback;
1733
+ } else {
1734
+ const date = new Date(time);
1735
+
1736
+ // Check if the date is valid
1737
+ if (Number.isNaN(date.getTime())) {
1738
+ LoggerProxy.logger.info(
1739
+ // @ts-ignore
1740
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1741
+ );
1742
+ this.#isoLocalClientMeetingJoinTime = fallback;
1743
+ } else {
1744
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1745
+ }
1746
+ }
1747
+ }
1748
+
1655
1749
  /**
1656
1750
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1657
1751
  * @param {any} info
@@ -2793,6 +2887,24 @@ export default class Meeting extends StatelessWebexPlugin {
2793
2887
  {state}
2794
2888
  );
2795
2889
  });
2890
+
2891
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2892
+ Trigger.trigger(
2893
+ this,
2894
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2895
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
2896
+ {state}
2897
+ );
2898
+ });
2899
+
2900
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED, ({state}) => {
2901
+ Trigger.trigger(
2902
+ this,
2903
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2904
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
2905
+ {state}
2906
+ );
2907
+ });
2796
2908
  }
2797
2909
 
2798
2910
  /**
@@ -2965,6 +3077,8 @@ export default class Meeting extends StatelessWebexPlugin {
2965
3077
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
2966
3078
  const sendStartedSharingRemote = () => {
2967
3079
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3080
+ this.shareCAEventSentStatus.receiveStart = false;
3081
+ this.shareCAEventSentStatus.receiveStop = false;
2968
3082
 
2969
3083
  Trigger.trigger(
2970
3084
  this,
@@ -3018,6 +3132,7 @@ export default class Meeting extends StatelessWebexPlugin {
3018
3132
  },
3019
3133
  options: {meetingId: this.id},
3020
3134
  });
3135
+
3021
3136
  break;
3022
3137
 
3023
3138
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3058,6 +3173,8 @@ export default class Meeting extends StatelessWebexPlugin {
3058
3173
  // if we got here, then some remote participant has stolen
3059
3174
  // the presentation from another remote participant
3060
3175
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3176
+ this.shareCAEventSentStatus.receiveStart = false;
3177
+ this.shareCAEventSentStatus.receiveStop = false;
3061
3178
 
3062
3179
  Trigger.trigger(
3063
3180
  this,
@@ -3741,7 +3858,13 @@ export default class Meeting extends StatelessWebexPlugin {
3741
3858
  return Promise.reject(error);
3742
3859
  }
3743
3860
 
3744
- return this.brbState.enable(enabled, this.sendSlotManager);
3861
+ return this.brbState.enable(enabled, this.sendSlotManager).then(() => {
3862
+ if (this.audio && enabled) {
3863
+ // locus mutes the participant with brb enabled request,
3864
+ // so we need to explicitly update remote mute for correct logic flow
3865
+ this.audio.handleServerRemoteMuteUpdate(this, enabled);
3866
+ }
3867
+ });
3745
3868
  }
3746
3869
 
3747
3870
  /**
@@ -3933,7 +4056,10 @@ export default class Meeting extends StatelessWebexPlugin {
3933
4056
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3934
4057
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3935
4058
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3936
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4059
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4060
+ this.userDisplayHints,
4061
+ this.selfUserPolicies
4062
+ ),
3937
4063
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3938
4064
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
3939
4065
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -3982,6 +4108,9 @@ export default class Meeting extends StatelessWebexPlugin {
3982
4108
  this.inMeetingActions.canSendReactions,
3983
4109
  this.userDisplayHints
3984
4110
  ),
4111
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4112
+ this.userDisplayHints
4113
+ ),
3985
4114
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
3986
4115
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
3987
4116
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -3997,6 +4126,7 @@ export default class Meeting extends StatelessWebexPlugin {
3997
4126
  this.userDisplayHints
3998
4127
  ),
3999
4128
  canUserRenameOthers: MeetingUtil.canUserRenameOthers(this.userDisplayHints),
4129
+ canMoveToLobby: MeetingUtil.canMoveToLobby(this.userDisplayHints),
4000
4130
  canMuteAll: ControlsOptionsUtil.hasHints({
4001
4131
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
4002
4132
  displayHints: this.userDisplayHints,
@@ -4177,6 +4307,22 @@ export default class Meeting extends StatelessWebexPlugin {
4177
4307
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4178
4308
  policies: this.selfUserPolicies,
4179
4309
  }),
4310
+ canEnableAnnotation: ControlsOptionsUtil.hasHints({
4311
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
4312
+ displayHints: this.userDisplayHints,
4313
+ }),
4314
+ canDisableAnnotation: ControlsOptionsUtil.hasHints({
4315
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
4316
+ displayHints: this.userDisplayHints,
4317
+ }),
4318
+ canEnableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4319
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
4320
+ displayHints: this.userDisplayHints,
4321
+ }),
4322
+ canDisableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4323
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
4324
+ displayHints: this.userDisplayHints,
4325
+ }),
4180
4326
  }) || changed;
4181
4327
  }
4182
4328
  if (changed) {
@@ -5684,8 +5830,6 @@ export default class Meeting extends StatelessWebexPlugin {
5684
5830
  // @ts-ignore
5685
5831
  this.webex.internal.device.meetingStarted();
5686
5832
 
5687
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5688
-
5689
5833
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5690
5834
 
5691
5835
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6146,10 +6290,18 @@ export default class Meeting extends StatelessWebexPlugin {
6146
6290
  },
6147
6291
  options: {meetingId: this.id, rawError: error},
6148
6292
  });
6149
- } else if (
6150
- error instanceof Errors.SdpOfferHandlingError ||
6151
- error instanceof Errors.SdpAnswerHandlingError
6152
- ) {
6293
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6294
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6295
+
6296
+ // @ts-ignore
6297
+ this.webex.internal.newMetrics.submitClientEvent({
6298
+ name: 'client.media-engine.remote-sdp-received',
6299
+ payload: {
6300
+ canProceed: false,
6301
+ },
6302
+ options: {meetingId: this.id, rawError: error},
6303
+ });
6304
+ } else if (error instanceof Errors.SdpAnswerHandlingError) {
6153
6305
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6154
6306
 
6155
6307
  // @ts-ignore
@@ -6160,6 +6312,13 @@ export default class Meeting extends StatelessWebexPlugin {
6160
6312
  },
6161
6313
  options: {meetingId: this.id, rawError: error},
6162
6314
  });
6315
+
6316
+ if (this.deferSDPAnswer) {
6317
+ clearTimeout(this.sdpResponseTimer);
6318
+ this.sdpResponseTimer = undefined;
6319
+
6320
+ this.deferSDPAnswer.reject();
6321
+ }
6163
6322
  } else if (error instanceof Errors.SdpError) {
6164
6323
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6165
6324
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -6187,6 +6346,11 @@ export default class Meeting extends StatelessWebexPlugin {
6187
6346
  ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6188
6347
  : undefined;
6189
6348
 
6349
+ const mediaServerIp =
6350
+ roapMessage.messageType === 'ANSWER'
6351
+ ? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6352
+ : undefined;
6353
+
6190
6354
  if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6191
6355
  throw new MultistreamNotSupportedError(
6192
6356
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6197,6 +6361,10 @@ export default class Meeting extends StatelessWebexPlugin {
6197
6361
  if (mediaServer) {
6198
6362
  this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
6199
6363
  }
6364
+
6365
+ if (this.isMultistream && mediaServerIp) {
6366
+ this.mediaServerIp = mediaServerIp;
6367
+ }
6200
6368
  };
6201
6369
 
6202
6370
  /**
@@ -6654,20 +6822,20 @@ export default class Meeting extends StatelessWebexPlugin {
6654
6822
  * @memberof Meetings
6655
6823
  */
6656
6824
  setupStatsAnalyzerEventHandlers = () => {
6657
- this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (options) => {
6658
- // TODO: might have to send the same event to the developer
6659
- // Add ip address info if geo hint is present
6660
- // @ts-ignore fix type
6661
- options.data.intervalMetadata.peerReflexiveIP =
6662
- // @ts-ignore
6663
- this.webex.meetings.geoHintInfo?.clientAddress ||
6664
- options.data.intervalMetadata.peerReflexiveIP ||
6665
- MQA_STATS.DEFAULT_IP;
6825
+ this.statsAnalyzer.on(StatsAnalyzerEventNames.MEDIA_QUALITY, (event) => {
6826
+ // Add IP address from geoHintInfo if missing.
6827
+ if (event.data.intervalMetadata.maskedPeerReflexiveIP === '0.0.0.0') {
6828
+ // @ts-ignore fix type
6829
+ const clientAddressFromGeoHint = this.webex.meetings.geoHintInfo?.clientAddress;
6830
+ if (clientAddressFromGeoHint) {
6831
+ event.data.intervalMetadata.maskedPeerReflexiveIP =
6832
+ CallDiagnosticUtils.anonymizeIPAddress(clientAddressFromGeoHint);
6833
+ }
6834
+ }
6666
6835
 
6836
+ // Count members that are in the meeting.
6667
6837
  const {members} = this.getMembers().membersCollection;
6668
-
6669
- // Count members that are in the meeting
6670
- options.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6838
+ event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6671
6839
  (member: Member) => member.isInMeeting
6672
6840
  ).length;
6673
6841
 
@@ -6676,10 +6844,10 @@ export default class Meeting extends StatelessWebexPlugin {
6676
6844
  name: 'client.mediaquality.event',
6677
6845
  options: {
6678
6846
  meetingId: this.id,
6679
- networkType: options.data.networkType,
6847
+ networkType: this.statsAnalyzer.getNetworkType(),
6680
6848
  },
6681
6849
  payload: {
6682
- intervals: [options.data],
6850
+ intervals: [event.data],
6683
6851
  },
6684
6852
  });
6685
6853
  });
@@ -6694,30 +6862,42 @@ export default class Meeting extends StatelessWebexPlugin {
6694
6862
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6695
6863
  data
6696
6864
  );
6697
- // @ts-ignore
6698
- this.webex.internal.newMetrics.submitClientEvent({
6699
- name: 'client.media.tx.start',
6700
- payload: {
6701
- mediaType: data.mediaType,
6702
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6703
- },
6704
- options: {
6705
- meetingId: this.id,
6706
- },
6707
- });
6865
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStart) {
6866
+ // @ts-ignore
6867
+ this.webex.internal.newMetrics.submitClientEvent({
6868
+ name: 'client.media.tx.start',
6869
+ payload: {
6870
+ mediaType: data.mediaType,
6871
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6872
+ },
6873
+ options: {
6874
+ meetingId: this.id,
6875
+ },
6876
+ });
6877
+
6878
+ if (data.mediaType === 'share') {
6879
+ this.shareCAEventSentStatus.transmitStart = true;
6880
+ }
6881
+ }
6708
6882
  });
6709
6883
  this.statsAnalyzer.on(StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED, (data) => {
6710
- // @ts-ignore
6711
- this.webex.internal.newMetrics.submitClientEvent({
6712
- name: 'client.media.tx.stop',
6713
- payload: {
6714
- mediaType: data.mediaType,
6715
- shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6716
- },
6717
- options: {
6718
- meetingId: this.id,
6719
- },
6720
- });
6884
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStop) {
6885
+ // @ts-ignore
6886
+ this.webex.internal.newMetrics.submitClientEvent({
6887
+ name: 'client.media.tx.stop',
6888
+ payload: {
6889
+ mediaType: data.mediaType,
6890
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6891
+ },
6892
+ options: {
6893
+ meetingId: this.id,
6894
+ },
6895
+ });
6896
+
6897
+ if (data.mediaType === 'share') {
6898
+ this.shareCAEventSentStatus.transmitStop = true;
6899
+ }
6900
+ }
6721
6901
  });
6722
6902
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6723
6903
  Trigger.trigger(
@@ -6729,57 +6909,65 @@ export default class Meeting extends StatelessWebexPlugin {
6729
6909
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6730
6910
  data
6731
6911
  );
6732
- // @ts-ignore
6733
- this.webex.internal.newMetrics.submitClientEvent({
6734
- name: 'client.media.rx.start',
6735
- payload: {
6736
- mediaType: data.mediaType,
6737
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6738
- },
6739
- options: {
6740
- meetingId: this.id,
6741
- },
6742
- });
6743
-
6744
- if (data.mediaType === 'share') {
6912
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6745
6913
  // @ts-ignore
6746
6914
  this.webex.internal.newMetrics.submitClientEvent({
6747
- name: 'client.media.render.start',
6915
+ name: 'client.media.rx.start',
6748
6916
  payload: {
6749
- mediaType: 'share',
6750
- shareInstanceId: this.remoteShareInstanceId,
6917
+ mediaType: data.mediaType,
6918
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6751
6919
  },
6752
6920
  options: {
6753
6921
  meetingId: this.id,
6754
6922
  },
6755
6923
  });
6924
+
6925
+ if (data.mediaType === 'share') {
6926
+ // @ts-ignore
6927
+ this.webex.internal.newMetrics.submitClientEvent({
6928
+ name: 'client.media.render.start',
6929
+ payload: {
6930
+ mediaType: 'share',
6931
+ shareInstanceId: this.remoteShareInstanceId,
6932
+ },
6933
+ options: {
6934
+ meetingId: this.id,
6935
+ },
6936
+ });
6937
+
6938
+ this.shareCAEventSentStatus.receiveStart = true;
6939
+ }
6756
6940
  }
6757
6941
  });
6758
6942
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED, (data) => {
6759
- // @ts-ignore
6760
- this.webex.internal.newMetrics.submitClientEvent({
6761
- name: 'client.media.rx.stop',
6762
- payload: {
6763
- mediaType: data.mediaType,
6764
- shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6765
- },
6766
- options: {
6767
- meetingId: this.id,
6768
- },
6769
- });
6770
-
6771
- if (data.mediaType === 'share') {
6943
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6772
6944
  // @ts-ignore
6773
6945
  this.webex.internal.newMetrics.submitClientEvent({
6774
- name: 'client.media.render.stop',
6946
+ name: 'client.media.rx.stop',
6775
6947
  payload: {
6776
- mediaType: 'share',
6777
- shareInstanceId: this.remoteShareInstanceId,
6948
+ mediaType: data.mediaType,
6949
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6778
6950
  },
6779
6951
  options: {
6780
6952
  meetingId: this.id,
6781
6953
  },
6782
6954
  });
6955
+
6956
+ if (data.mediaType === 'share') {
6957
+ // @ts-ignore
6958
+ this.webex.internal.newMetrics.submitClientEvent({
6959
+ name: 'client.media.render.stop',
6960
+ payload: {
6961
+ mediaType: 'share',
6962
+ shareInstanceId: this.remoteShareInstanceId,
6963
+ },
6964
+ options: {
6965
+ meetingId: this.id,
6966
+ },
6967
+ });
6968
+
6969
+ this.shareCAEventSentStatus.receiveStop = true;
6970
+ }
6783
6971
  }
6784
6972
  });
6785
6973
  };
@@ -6796,7 +6984,10 @@ export default class Meeting extends StatelessWebexPlugin {
6796
6984
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6797
6985
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6798
6986
  */
6799
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6987
+ private async createMediaConnection(
6988
+ turnServerInfo?: TurnServerInfo,
6989
+ bundlePolicy?: BundlePolicy
6990
+ ) {
6800
6991
  this.rtcMetrics = this.isMultistream
6801
6992
  ? // @ts-ignore
6802
6993
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6821,6 +7012,8 @@ export default class Meeting extends StatelessWebexPlugin {
6821
7012
  bundlePolicy,
6822
7013
  // @ts-ignore - config coming from registerPlugin
6823
7014
  iceCandidatesTimeout: this.config.iceCandidatesGatheringTimeout,
7015
+ // @ts-ignore - config coming from registerPlugin
7016
+ disableAudioMainDtx: this.config.experimental.disableAudioMainDtx,
6824
7017
  }
6825
7018
  );
6826
7019
 
@@ -6971,12 +7164,18 @@ export default class Meeting extends StatelessWebexPlugin {
6971
7164
  },
6972
7165
  options: {
6973
7166
  meetingId: this.id,
7167
+ rawError: error,
6974
7168
  },
6975
7169
  });
6976
7170
  }
6977
- throw new Error(
7171
+
7172
+ const timedOutError = new Error(
6978
7173
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
6979
7174
  );
7175
+
7176
+ timedOutError.cause = error;
7177
+
7178
+ throw timedOutError;
6980
7179
  }
6981
7180
  }
6982
7181
 
@@ -6997,6 +7196,12 @@ export default class Meeting extends StatelessWebexPlugin {
6997
7196
  networkQualityMonitor: this.networkQualityMonitor,
6998
7197
  isMultistream: this.isMultistream,
6999
7198
  });
7199
+ this.shareCAEventSentStatus = {
7200
+ transmitStart: false,
7201
+ transmitStop: false,
7202
+ receiveStart: false,
7203
+ receiveStop: false,
7204
+ };
7000
7205
  this.setupStatsAnalyzerEventHandlers();
7001
7206
  this.networkQualityMonitor.on(
7002
7207
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -7031,6 +7236,9 @@ export default class Meeting extends StatelessWebexPlugin {
7031
7236
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7032
7237
  } seconds`
7033
7238
  );
7239
+
7240
+ const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7241
+
7034
7242
  // @ts-ignore
7035
7243
  this.webex.internal.newMetrics.submitClientEvent({
7036
7244
  name: 'client.media-engine.remote-sdp-received',
@@ -7043,10 +7251,10 @@ export default class Meeting extends StatelessWebexPlugin {
7043
7251
  }),
7044
7252
  ],
7045
7253
  },
7046
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7254
+ options: {meetingId: this.id, rawError: error},
7047
7255
  });
7048
7256
 
7049
- deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
7257
+ deferSDPAnswer.reject(error);
7050
7258
  }, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
7051
7259
 
7052
7260
  LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
@@ -7151,7 +7359,7 @@ export default class Meeting extends StatelessWebexPlugin {
7151
7359
  error
7152
7360
  );
7153
7361
 
7154
- throw new AddMediaFailed();
7362
+ throw new AddMediaFailed(error);
7155
7363
  }
7156
7364
  }
7157
7365
 
@@ -7566,10 +7774,11 @@ export default class Meeting extends StatelessWebexPlugin {
7566
7774
 
7567
7775
  const {connectionType, selectedCandidatePairChanges, numTransports} =
7568
7776
  await this.mediaProperties.getCurrentConnectionInfo();
7569
- // @ts-ignore
7570
- const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7777
+
7571
7778
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7572
7779
 
7780
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7781
+
7573
7782
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7574
7783
  correlation_id: this.correlationId,
7575
7784
  locus_id: this.locusUrl.split('/').pop(),
@@ -7579,7 +7788,7 @@ export default class Meeting extends StatelessWebexPlugin {
7579
7788
  isMultistream: this.isMultistream,
7580
7789
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7581
7790
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7582
- ...reachabilityStats,
7791
+ ...reachabilityMetrics,
7583
7792
  ...iceCandidateErrors,
7584
7793
  iceCandidatesCount: this.iceCandidatesCount,
7585
7794
  });
@@ -7601,7 +7810,7 @@ export default class Meeting extends StatelessWebexPlugin {
7601
7810
  LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
7602
7811
 
7603
7812
  // @ts-ignore
7604
- const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
7813
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7605
7814
 
7606
7815
  const {selectedCandidatePairChanges, numTransports} =
7607
7816
  await this.mediaProperties.getCurrentConnectionInfo();
@@ -8681,6 +8890,9 @@ export default class Meeting extends StatelessWebexPlugin {
8681
8890
  LoggerProxy.logger.log(
8682
8891
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8683
8892
  );
8893
+
8894
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8895
+
8684
8896
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8685
8897
  correlationId: this.correlationId,
8686
8898
  muted,
@@ -8689,8 +8901,9 @@ export default class Meeting extends StatelessWebexPlugin {
8689
8901
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8690
8902
  // all we're doing here is adding metrics.
8691
8903
  // eslint-disable-next-line dot-notation
8692
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8904
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8693
8905
  isMultistream: this.isMultistream,
8906
+ frameRate: shareVideoStreamSettings?.frameRate,
8694
8907
  });
8695
8908
  };
8696
8909
 
@@ -9017,6 +9230,23 @@ export default class Meeting extends StatelessWebexPlugin {
9017
9230
  });
9018
9231
  }
9019
9232
 
9233
+ /**
9234
+ * Method to set post meeting data consent.
9235
+ *
9236
+ * @param {boolean} accept - whether consent accepted or declined
9237
+ * @returns {Promise}
9238
+ * @public
9239
+ * @memberof Meeting
9240
+ */
9241
+ public setPostMeetingDataConsent(accept: boolean) {
9242
+ return this.meetingRequest.setPostMeetingDataConsent({
9243
+ postMeetingDataConsent: accept,
9244
+ locusUrl: this.locusUrl,
9245
+ deviceUrl: this.deviceUrl,
9246
+ selfId: this.members.selfId,
9247
+ });
9248
+ }
9249
+
9020
9250
  /**
9021
9251
  * Throws if we don't have a media connection created
9022
9252
  *
@@ -9257,6 +9487,8 @@ export default class Meeting extends StatelessWebexPlugin {
9257
9487
 
9258
9488
  if (floorRequestNeeded) {
9259
9489
  this.localShareInstanceId = uuid.v4();
9490
+ this.shareCAEventSentStatus.transmitStart = false;
9491
+ this.shareCAEventSentStatus.transmitStop = false;
9260
9492
 
9261
9493
  // @ts-ignore
9262
9494
  this.webex.internal.newMetrics.submitClientEvent({
@@ -9384,4 +9616,44 @@ export default class Meeting extends StatelessWebexPlugin {
9384
9616
 
9385
9617
  return Promise.resolve();
9386
9618
  }
9619
+
9620
+ /**
9621
+ * Gets the media reachability metrics
9622
+ *
9623
+ * @returns {Promise<MediaReachabilityMetrics>}
9624
+ */
9625
+ private async getMediaReachabilityMetricFields(): Promise<MediaReachabilityMetrics> {
9626
+ const reachabilityMetrics: ReachabilityMetrics =
9627
+ // @ts-ignore
9628
+ await this.webex.meetings.reachability.getReachabilityMetrics();
9629
+
9630
+ const successKeys: Array<keyof ReachabilityMetrics> = [
9631
+ 'reachability_public_udp_success',
9632
+ 'reachability_public_tcp_success',
9633
+ 'reachability_public_xtls_success',
9634
+ 'reachability_vmn_udp_success',
9635
+ 'reachability_vmn_tcp_success',
9636
+ 'reachability_vmn_xtls_success',
9637
+ ];
9638
+
9639
+ const totalSuccessCases = successKeys.reduce((total, key) => {
9640
+ const value = reachabilityMetrics[key];
9641
+ if (typeof value === 'number') {
9642
+ return total + value;
9643
+ }
9644
+
9645
+ return total;
9646
+ }, 0);
9647
+
9648
+ let isSubnetReachable = null;
9649
+ if (totalSuccessCases > 0) {
9650
+ // @ts-ignore
9651
+ isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9652
+ }
9653
+
9654
+ return {
9655
+ ...reachabilityMetrics,
9656
+ isSubnetReachable,
9657
+ };
9658
+ }
9387
9659
  }