@webex/plugin-meetings 3.8.0-next.4 → 3.8.0-next.41

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 (130) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +1 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +14 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/controls-options-manager/enums.js +2 -0
  8. package/dist/controls-options-manager/enums.js.map +1 -1
  9. package/dist/controls-options-manager/types.js.map +1 -1
  10. package/dist/controls-options-manager/util.js +52 -0
  11. package/dist/controls-options-manager/util.js.map +1 -1
  12. package/dist/interpretation/index.js +1 -1
  13. package/dist/interpretation/siLanguage.js +1 -1
  14. package/dist/locus-info/controlsUtils.js +28 -10
  15. package/dist/locus-info/controlsUtils.js.map +1 -1
  16. package/dist/locus-info/index.js +20 -1
  17. package/dist/locus-info/index.js.map +1 -1
  18. package/dist/media/index.js +3 -15
  19. package/dist/media/index.js.map +1 -1
  20. package/dist/meeting/in-meeting-actions.js +11 -1
  21. package/dist/meeting/in-meeting-actions.js.map +1 -1
  22. package/dist/meeting/index.js +443 -256
  23. package/dist/meeting/index.js.map +1 -1
  24. package/dist/meeting/locusMediaRequest.js +21 -22
  25. package/dist/meeting/locusMediaRequest.js.map +1 -1
  26. package/dist/meeting/muteState.js +0 -2
  27. package/dist/meeting/muteState.js.map +1 -1
  28. package/dist/meeting/request.js +30 -0
  29. package/dist/meeting/request.js.map +1 -1
  30. package/dist/meeting/request.type.js.map +1 -1
  31. package/dist/meeting/util.js +10 -2
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +359 -60
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +60 -1
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/member/index.js +10 -0
  38. package/dist/member/index.js.map +1 -1
  39. package/dist/member/util.js +3 -0
  40. package/dist/member/util.js.map +1 -1
  41. package/dist/metrics/constants.js +9 -0
  42. package/dist/metrics/constants.js.map +1 -1
  43. package/dist/reachability/clusterReachability.js +52 -8
  44. package/dist/reachability/clusterReachability.js.map +1 -1
  45. package/dist/reachability/index.js +70 -45
  46. package/dist/reachability/index.js.map +1 -1
  47. package/dist/reachability/reachability.types.js +14 -0
  48. package/dist/reachability/reachability.types.js.map +1 -1
  49. package/dist/reachability/request.js +19 -3
  50. package/dist/reachability/request.js.map +1 -1
  51. package/dist/reconnection-manager/index.js +2 -2
  52. package/dist/reconnection-manager/index.js.map +1 -1
  53. package/dist/recording-controller/util.js +5 -5
  54. package/dist/recording-controller/util.js.map +1 -1
  55. package/dist/roap/index.js.map +1 -1
  56. package/dist/roap/turnDiscovery.js +45 -27
  57. package/dist/roap/turnDiscovery.js.map +1 -1
  58. package/dist/roap/types.js +17 -0
  59. package/dist/roap/types.js.map +1 -0
  60. package/dist/types/config.d.ts +1 -0
  61. package/dist/types/constants.d.ts +10 -0
  62. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  63. package/dist/types/controls-options-manager/types.d.ts +7 -1
  64. package/dist/types/locus-info/index.d.ts +1 -0
  65. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  66. package/dist/types/meeting/index.d.ts +47 -1
  67. package/dist/types/meeting/muteState.d.ts +0 -1
  68. package/dist/types/meeting/request.d.ts +12 -1
  69. package/dist/types/meeting/request.type.d.ts +6 -0
  70. package/dist/types/meeting/util.d.ts +2 -1
  71. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  72. package/dist/types/meetings/index.d.ts +29 -0
  73. package/dist/types/member/index.d.ts +1 -0
  74. package/dist/types/metrics/constants.d.ts +9 -0
  75. package/dist/types/reachability/clusterReachability.d.ts +13 -1
  76. package/dist/types/reachability/index.d.ts +2 -1
  77. package/dist/types/reachability/reachability.types.d.ts +5 -0
  78. package/dist/types/roap/index.d.ts +3 -2
  79. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  80. package/dist/types/roap/types.d.ts +16 -0
  81. package/dist/webinar/index.js +1 -1
  82. package/package.json +22 -22
  83. package/src/config.ts +1 -0
  84. package/src/constants.ts +17 -0
  85. package/src/controls-options-manager/enums.ts +2 -0
  86. package/src/controls-options-manager/types.ts +11 -1
  87. package/src/controls-options-manager/util.ts +62 -0
  88. package/src/locus-info/controlsUtils.ts +44 -14
  89. package/src/locus-info/index.ts +23 -1
  90. package/src/media/index.ts +5 -21
  91. package/src/meeting/in-meeting-actions.ts +20 -0
  92. package/src/meeting/index.ts +263 -69
  93. package/src/meeting/locusMediaRequest.ts +27 -22
  94. package/src/meeting/muteState.ts +0 -2
  95. package/src/meeting/request.ts +36 -1
  96. package/src/meeting/request.type.ts +7 -0
  97. package/src/meeting/util.ts +9 -2
  98. package/src/meeting-info/meeting-info-v2.ts +247 -6
  99. package/src/meetings/index.ts +72 -1
  100. package/src/member/index.ts +11 -0
  101. package/src/member/util.ts +3 -0
  102. package/src/metrics/constants.ts +9 -0
  103. package/src/reachability/clusterReachability.ts +47 -1
  104. package/src/reachability/index.ts +15 -0
  105. package/src/reachability/reachability.types.ts +6 -0
  106. package/src/reachability/request.ts +7 -0
  107. package/src/reconnection-manager/index.ts +2 -2
  108. package/src/recording-controller/util.ts +17 -13
  109. package/src/roap/index.ts +3 -7
  110. package/src/roap/turnDiscovery.ts +34 -39
  111. package/src/roap/types.ts +23 -0
  112. package/test/unit/spec/controls-options-manager/util.js +120 -0
  113. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  114. package/test/unit/spec/locus-info/index.js +28 -0
  115. package/test/unit/spec/media/index.ts +6 -16
  116. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -4
  117. package/test/unit/spec/meeting/index.js +490 -130
  118. package/test/unit/spec/meeting/locusMediaRequest.ts +95 -87
  119. package/test/unit/spec/meeting/muteState.js +0 -2
  120. package/test/unit/spec/meeting/request.js +32 -1
  121. package/test/unit/spec/meeting/utils.js +115 -18
  122. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  123. package/test/unit/spec/meetings/index.js +78 -1
  124. package/test/unit/spec/member/index.js +7 -0
  125. package/test/unit/spec/member/util.js +24 -0
  126. package/test/unit/spec/reachability/clusterReachability.ts +47 -1
  127. package/test/unit/spec/reachability/index.ts +12 -0
  128. package/test/unit/spec/reachability/request.js +47 -2
  129. package/test/unit/spec/reconnection-manager/index.js +4 -4
  130. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -17,7 +17,9 @@ import {
17
17
  MEETING_REMOVED_REASON,
18
18
  CALL_REMOVED_REASON,
19
19
  RECORDING_STATE,
20
+ BREAKOUTS,
20
21
  } from '../constants';
22
+
21
23
  import InfoUtils from './infoUtils';
22
24
  import FullState from './fullState';
23
25
  import SelfUtils from './selfUtils';
@@ -67,6 +69,7 @@ export default class LocusInfo extends EventsScope {
67
69
  services: any;
68
70
  resources: any;
69
71
  mainSessionLocusCache: any;
72
+ self: any;
70
73
  /**
71
74
  * Constructor
72
75
  * @param {function} updateMeeting callback to update the meeting object from an object
@@ -814,6 +817,8 @@ export default class LocusInfo extends EventsScope {
814
817
  hasMeetingFullChanged,
815
818
  hasPracticeSessionEnabledChanged,
816
819
  hasStageViewChanged,
820
+ hasAnnotationControlChanged,
821
+ hasRemoteDesktopControlChanged,
817
822
  },
818
823
  current,
819
824
  } = ControlsUtils.getControls(this.controls, controls);
@@ -1049,6 +1054,22 @@ export default class LocusInfo extends EventsScope {
1049
1054
  );
1050
1055
  }
1051
1056
 
1057
+ if (hasAnnotationControlChanged) {
1058
+ this.emitScoped(
1059
+ {file: 'locus-info', function: 'updateControls'},
1060
+ LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED,
1061
+ {state: current.annotationControl}
1062
+ );
1063
+ }
1064
+
1065
+ if (hasRemoteDesktopControlChanged) {
1066
+ this.emitScoped(
1067
+ {file: 'locus-info', function: 'updateControls'},
1068
+ LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED,
1069
+ {state: current.rdcControl}
1070
+ );
1071
+ }
1072
+
1052
1073
  this.controls = controls;
1053
1074
  }
1054
1075
  }
@@ -1706,7 +1727,8 @@ export default class LocusInfo extends EventsScope {
1706
1727
  * @memberof LocusInfo
1707
1728
  */
1708
1729
  getTheLocusToUpdate(newLocus: any) {
1709
- const switchStatus = ControlsUtils.getSessionSwitchStatus(this.controls, newLocus?.controls);
1730
+ const switchStatus = ControlsUtils.getSessionSwitchStatus(this, newLocus);
1731
+
1710
1732
  if (switchStatus.isReturnToMain && this.mainSessionLocusCache) {
1711
1733
  return cloneDeep(this.mainSessionLocusCache);
1712
1734
  }
@@ -21,6 +21,7 @@ import {MEDIA_TRACK_CONSTRAINT} from '../constants';
21
21
  import Config from '../config';
22
22
  import StaticConfig from '../common/config';
23
23
  import BrowserDetection from '../common/browser-detection';
24
+ import {TurnServerInfo} from '../roap/types';
24
25
 
25
26
  const {isBrowser} = BrowserDetection();
26
27
 
@@ -138,11 +139,7 @@ Media.createMediaConnection = (
138
139
  remoteQualityLevel?: 'LOW' | 'MEDIUM' | 'HIGH';
139
140
  enableRtx?: boolean;
140
141
  enableExtmap?: boolean;
141
- turnServerInfo?: {
142
- url: string;
143
- username: string;
144
- password: string;
145
- };
142
+ turnServerInfo?: TurnServerInfo;
146
143
  bundlePolicy?: BundlePolicy;
147
144
  iceCandidatesTimeout?: number;
148
145
  }
@@ -160,24 +157,11 @@ Media.createMediaConnection = (
160
157
 
161
158
  const iceServers = [];
162
159
 
163
- // we might not have any TURN server if TURN discovery failed or wasn't done or
164
- // we might get an empty TURN url if we land on a video mesh node
165
- if (turnServerInfo?.url) {
166
- if (!isBrowser('firefox')) {
167
- let bareTurnServer = turnServerInfo.url;
168
- bareTurnServer = bareTurnServer.replace('turns:', 'turn:');
169
- bareTurnServer = bareTurnServer.replace('443', '5004');
170
-
171
- iceServers.push({
172
- urls: bareTurnServer,
173
- username: turnServerInfo.username || '',
174
- credential: turnServerInfo.password || '',
175
- });
176
- }
177
-
160
+ // we might not have any TURN server if TURN discovery failed or wasn't done or we land on a video mesh node
161
+ if (turnServerInfo?.urls.length > 0) {
178
162
  // TURN-TLS server
179
163
  iceServers.push({
180
- urls: turnServerInfo.url,
164
+ urls: turnServerInfo.urls,
181
165
  username: turnServerInfo.username || '',
182
166
  credential: turnServerInfo.password || '',
183
167
  });
@@ -99,6 +99,11 @@ interface IInMeetingActions {
99
99
  isPracticeSessionOff?: boolean;
100
100
  canStartPracticeSession?: boolean;
101
101
  canStopPracticeSession?: boolean;
102
+ requiresPostMeetingDataConsentPrompt?: boolean;
103
+ canEnableAnnotation?: boolean;
104
+ canDisableAnnotation?: boolean;
105
+ canEnableRemoteDesktopControl?: boolean;
106
+ canDisableRemoteDesktopControl?: boolean;
102
107
  }
103
108
 
104
109
  /**
@@ -285,6 +290,16 @@ export default class InMeetingActions implements IInMeetingActions {
285
290
 
286
291
  canStopPracticeSession = null;
287
292
 
293
+ requiresPostMeetingDataConsentPrompt = null;
294
+
295
+ canEnableAnnotation = null;
296
+
297
+ canDisableAnnotation = null;
298
+
299
+ canEnableRemoteDesktopControl = null;
300
+
301
+ canDisableRemoteDesktopControl = null;
302
+
288
303
  /**
289
304
  * Returns all meeting action options
290
305
  * @returns {Object}
@@ -379,6 +394,11 @@ export default class InMeetingActions implements IInMeetingActions {
379
394
  isPracticeSessionOff: this.isPracticeSessionOff,
380
395
  canStartPracticeSession: this.canStartPracticeSession,
381
396
  canStopPracticeSession: this.canStopPracticeSession,
397
+ requiresPostMeetingDataConsentPrompt: this.requiresPostMeetingDataConsentPrompt,
398
+ canEnableAnnotation: this.canEnableAnnotation,
399
+ canDisableAnnotation: this.canDisableAnnotation,
400
+ canEnableRemoteDesktopControl: this.canEnableRemoteDesktopControl,
401
+ canDisableRemoteDesktopControl: this.canDisableRemoteDesktopControl,
382
402
  });
383
403
 
384
404
  /**
@@ -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';
@@ -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 = {
@@ -648,6 +647,13 @@ export default class Meeting extends StatelessWebexPlugin {
648
647
  allowMediaInLobby: boolean;
649
648
  localShareInstanceId: string;
650
649
  remoteShareInstanceId: string;
650
+ shareCAEventSentStatus: {
651
+ transmitStart: boolean;
652
+ transmitStop: boolean;
653
+ receiveStart: boolean;
654
+ receiveStop: boolean;
655
+ };
656
+
651
657
  turnDiscoverySkippedReason: TurnDiscoverySkipReason;
652
658
  turnServerUsed: boolean;
653
659
  areVoiceaEventsSetup = false;
@@ -1420,6 +1426,19 @@ export default class Meeting extends StatelessWebexPlugin {
1420
1426
  */
1421
1427
  this.remoteShareInstanceId = null;
1422
1428
 
1429
+ /**
1430
+ * Status used for ensuring we do not oversend metrics
1431
+ * @instance
1432
+ * @private
1433
+ * @memberof Meeting
1434
+ */
1435
+ this.shareCAEventSentStatus = {
1436
+ transmitStart: false,
1437
+ transmitStop: false,
1438
+ receiveStart: false,
1439
+ receiveStop: false,
1440
+ };
1441
+
1423
1442
  /**
1424
1443
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1425
1444
  * @instance
@@ -1627,6 +1646,38 @@ export default class Meeting extends StatelessWebexPlugin {
1627
1646
  this.callStateForMetrics.correlationId = correlationId;
1628
1647
  }
1629
1648
 
1649
+ /**
1650
+ * Getter - Returns callStateForMetrics.userNameInput
1651
+ * @returns {string}
1652
+ */
1653
+ get userNameInput() {
1654
+ return this.callStateForMetrics?.userNameInput;
1655
+ }
1656
+
1657
+ /**
1658
+ * Setter - sets callStateForMetrics.userNameInput
1659
+ * @param {string} userNameInput
1660
+ */
1661
+ set userNameInput(userNameInput: string) {
1662
+ this.callStateForMetrics.userNameInput = userNameInput;
1663
+ }
1664
+
1665
+ /**
1666
+ * Getter - Returns callStateForMetrics.emailInput
1667
+ * @returns {string}
1668
+ */
1669
+ get emailInput() {
1670
+ return this.callStateForMetrics?.emailInput;
1671
+ }
1672
+
1673
+ /**
1674
+ * Setter - sets callStateForMetrics.emailInput
1675
+ * @param {string} emailInput
1676
+ */
1677
+ set emailInput(emailInput: string) {
1678
+ this.callStateForMetrics.emailInput = emailInput;
1679
+ }
1680
+
1630
1681
  /**
1631
1682
  * Getter - Returns callStateForMetrics.sessionCorrelationId
1632
1683
  * @returns {string}
@@ -1652,6 +1703,33 @@ export default class Meeting extends StatelessWebexPlugin {
1652
1703
  return this.#isoLocalClientMeetingJoinTime;
1653
1704
  }
1654
1705
 
1706
+ /**
1707
+ * Setter - sets isoLocalClientMeetingJoinTime
1708
+ * This will be set once on meeting join, and not updated again
1709
+ * this will always produce an ISO string
1710
+ * If the iso string is invalid, it will fallback to the current system time
1711
+ * @param {string | undefined} time
1712
+ */
1713
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1714
+ const fallback = new Date().toISOString();
1715
+ if (!time) {
1716
+ this.#isoLocalClientMeetingJoinTime = fallback;
1717
+ } else {
1718
+ const date = new Date(time);
1719
+
1720
+ // Check if the date is valid
1721
+ if (Number.isNaN(date.getTime())) {
1722
+ LoggerProxy.logger.info(
1723
+ // @ts-ignore
1724
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1725
+ );
1726
+ this.#isoLocalClientMeetingJoinTime = fallback;
1727
+ } else {
1728
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1729
+ }
1730
+ }
1731
+ }
1732
+
1655
1733
  /**
1656
1734
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1657
1735
  * @param {any} info
@@ -2793,6 +2871,24 @@ export default class Meeting extends StatelessWebexPlugin {
2793
2871
  {state}
2794
2872
  );
2795
2873
  });
2874
+
2875
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2876
+ Trigger.trigger(
2877
+ this,
2878
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2879
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
2880
+ {state}
2881
+ );
2882
+ });
2883
+
2884
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED, ({state}) => {
2885
+ Trigger.trigger(
2886
+ this,
2887
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2888
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
2889
+ {state}
2890
+ );
2891
+ });
2796
2892
  }
2797
2893
 
2798
2894
  /**
@@ -2965,6 +3061,8 @@ export default class Meeting extends StatelessWebexPlugin {
2965
3061
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
2966
3062
  const sendStartedSharingRemote = () => {
2967
3063
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3064
+ this.shareCAEventSentStatus.receiveStart = false;
3065
+ this.shareCAEventSentStatus.receiveStop = false;
2968
3066
 
2969
3067
  Trigger.trigger(
2970
3068
  this,
@@ -3018,6 +3116,7 @@ export default class Meeting extends StatelessWebexPlugin {
3018
3116
  },
3019
3117
  options: {meetingId: this.id},
3020
3118
  });
3119
+
3021
3120
  break;
3022
3121
 
3023
3122
  case SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE:
@@ -3058,6 +3157,8 @@ export default class Meeting extends StatelessWebexPlugin {
3058
3157
  // if we got here, then some remote participant has stolen
3059
3158
  // the presentation from another remote participant
3060
3159
  this.remoteShareInstanceId = contentShare.shareInstanceId;
3160
+ this.shareCAEventSentStatus.receiveStart = false;
3161
+ this.shareCAEventSentStatus.receiveStop = false;
3061
3162
 
3062
3163
  Trigger.trigger(
3063
3164
  this,
@@ -3741,7 +3842,13 @@ export default class Meeting extends StatelessWebexPlugin {
3741
3842
  return Promise.reject(error);
3742
3843
  }
3743
3844
 
3744
- return this.brbState.enable(enabled, this.sendSlotManager);
3845
+ return this.brbState.enable(enabled, this.sendSlotManager).then(() => {
3846
+ if (this.audio && enabled) {
3847
+ // locus mutes the participant with brb enabled request,
3848
+ // so we need to explicitly update remote mute for correct logic flow
3849
+ this.audio.handleServerRemoteMuteUpdate(this, enabled);
3850
+ }
3851
+ });
3745
3852
  }
3746
3853
 
3747
3854
  /**
@@ -3933,7 +4040,10 @@ export default class Meeting extends StatelessWebexPlugin {
3933
4040
  canAdmitParticipant: MeetingUtil.canAdmitParticipant(this.userDisplayHints),
3934
4041
  canLock: MeetingUtil.canUserLock(this.userDisplayHints),
3935
4042
  canUnlock: MeetingUtil.canUserUnlock(this.userDisplayHints),
3936
- canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(this.userDisplayHints),
4043
+ canShareWhiteBoard: MeetingUtil.canShareWhiteBoard(
4044
+ this.userDisplayHints,
4045
+ this.selfUserPolicies
4046
+ ),
3937
4047
  canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(this.userDisplayHints),
3938
4048
  canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(this.userDisplayHints),
3939
4049
  canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(this.userDisplayHints),
@@ -3982,6 +4092,9 @@ export default class Meeting extends StatelessWebexPlugin {
3982
4092
  this.inMeetingActions.canSendReactions,
3983
4093
  this.userDisplayHints
3984
4094
  ),
4095
+ requiresPostMeetingDataConsentPrompt: MeetingUtil.requiresPostMeetingDataConsentPrompt(
4096
+ this.userDisplayHints
4097
+ ),
3985
4098
  canManageBreakout: MeetingUtil.canManageBreakout(this.userDisplayHints),
3986
4099
  canStartBreakout: MeetingUtil.canStartBreakout(this.userDisplayHints),
3987
4100
  canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
@@ -4177,6 +4290,22 @@ export default class Meeting extends StatelessWebexPlugin {
4177
4290
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
4178
4291
  policies: this.selfUserPolicies,
4179
4292
  }),
4293
+ canEnableAnnotation: ControlsOptionsUtil.hasHints({
4294
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
4295
+ displayHints: this.userDisplayHints,
4296
+ }),
4297
+ canDisableAnnotation: ControlsOptionsUtil.hasHints({
4298
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
4299
+ displayHints: this.userDisplayHints,
4300
+ }),
4301
+ canEnableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4302
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
4303
+ displayHints: this.userDisplayHints,
4304
+ }),
4305
+ canDisableRemoteDesktopControl: ControlsOptionsUtil.hasHints({
4306
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
4307
+ displayHints: this.userDisplayHints,
4308
+ }),
4180
4309
  }) || changed;
4181
4310
  }
4182
4311
  if (changed) {
@@ -5684,8 +5813,6 @@ export default class Meeting extends StatelessWebexPlugin {
5684
5813
  // @ts-ignore
5685
5814
  this.webex.internal.device.meetingStarted();
5686
5815
 
5687
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5688
-
5689
5816
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5690
5817
 
5691
5818
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -6146,10 +6273,18 @@ export default class Meeting extends StatelessWebexPlugin {
6146
6273
  },
6147
6274
  options: {meetingId: this.id, rawError: error},
6148
6275
  });
6149
- } else if (
6150
- error instanceof Errors.SdpOfferHandlingError ||
6151
- error instanceof Errors.SdpAnswerHandlingError
6152
- ) {
6276
+ } else if (error instanceof Errors.SdpOfferHandlingError) {
6277
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6278
+
6279
+ // @ts-ignore
6280
+ this.webex.internal.newMetrics.submitClientEvent({
6281
+ name: 'client.media-engine.remote-sdp-received',
6282
+ payload: {
6283
+ canProceed: false,
6284
+ },
6285
+ options: {meetingId: this.id, rawError: error},
6286
+ });
6287
+ } else if (error instanceof Errors.SdpAnswerHandlingError) {
6153
6288
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6154
6289
 
6155
6290
  // @ts-ignore
@@ -6160,6 +6295,13 @@ export default class Meeting extends StatelessWebexPlugin {
6160
6295
  },
6161
6296
  options: {meetingId: this.id, rawError: error},
6162
6297
  });
6298
+
6299
+ if (this.deferSDPAnswer) {
6300
+ clearTimeout(this.sdpResponseTimer);
6301
+ this.sdpResponseTimer = undefined;
6302
+
6303
+ this.deferSDPAnswer.reject();
6304
+ }
6163
6305
  } else if (error instanceof Errors.SdpError) {
6164
6306
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
6165
6307
  sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.correlationId);
@@ -6694,30 +6836,42 @@ export default class Meeting extends StatelessWebexPlugin {
6694
6836
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
6695
6837
  data
6696
6838
  );
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
- });
6839
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStart) {
6840
+ // @ts-ignore
6841
+ this.webex.internal.newMetrics.submitClientEvent({
6842
+ name: 'client.media.tx.start',
6843
+ payload: {
6844
+ mediaType: data.mediaType,
6845
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6846
+ },
6847
+ options: {
6848
+ meetingId: this.id,
6849
+ },
6850
+ });
6851
+
6852
+ if (data.mediaType === 'share') {
6853
+ this.shareCAEventSentStatus.transmitStart = true;
6854
+ }
6855
+ }
6708
6856
  });
6709
6857
  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
- });
6858
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.transmitStop) {
6859
+ // @ts-ignore
6860
+ this.webex.internal.newMetrics.submitClientEvent({
6861
+ name: 'client.media.tx.stop',
6862
+ payload: {
6863
+ mediaType: data.mediaType,
6864
+ shareInstanceId: data.mediaType === 'share' ? this.localShareInstanceId : undefined,
6865
+ },
6866
+ options: {
6867
+ meetingId: this.id,
6868
+ },
6869
+ });
6870
+
6871
+ if (data.mediaType === 'share') {
6872
+ this.shareCAEventSentStatus.transmitStop = true;
6873
+ }
6874
+ }
6721
6875
  });
6722
6876
  this.statsAnalyzer.on(StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED, (data) => {
6723
6877
  Trigger.trigger(
@@ -6729,57 +6883,65 @@ export default class Meeting extends StatelessWebexPlugin {
6729
6883
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
6730
6884
  data
6731
6885
  );
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') {
6886
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStart) {
6745
6887
  // @ts-ignore
6746
6888
  this.webex.internal.newMetrics.submitClientEvent({
6747
- name: 'client.media.render.start',
6889
+ name: 'client.media.rx.start',
6748
6890
  payload: {
6749
- mediaType: 'share',
6750
- shareInstanceId: this.remoteShareInstanceId,
6891
+ mediaType: data.mediaType,
6892
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6751
6893
  },
6752
6894
  options: {
6753
6895
  meetingId: this.id,
6754
6896
  },
6755
6897
  });
6898
+
6899
+ if (data.mediaType === 'share') {
6900
+ // @ts-ignore
6901
+ this.webex.internal.newMetrics.submitClientEvent({
6902
+ name: 'client.media.render.start',
6903
+ payload: {
6904
+ mediaType: 'share',
6905
+ shareInstanceId: this.remoteShareInstanceId,
6906
+ },
6907
+ options: {
6908
+ meetingId: this.id,
6909
+ },
6910
+ });
6911
+
6912
+ this.shareCAEventSentStatus.receiveStart = true;
6913
+ }
6756
6914
  }
6757
6915
  });
6758
6916
  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') {
6917
+ if (data.mediaType !== 'share' || !this.shareCAEventSentStatus.receiveStop) {
6772
6918
  // @ts-ignore
6773
6919
  this.webex.internal.newMetrics.submitClientEvent({
6774
- name: 'client.media.render.stop',
6920
+ name: 'client.media.rx.stop',
6775
6921
  payload: {
6776
- mediaType: 'share',
6777
- shareInstanceId: this.remoteShareInstanceId,
6922
+ mediaType: data.mediaType,
6923
+ shareInstanceId: data.mediaType === 'share' ? this.remoteShareInstanceId : undefined,
6778
6924
  },
6779
6925
  options: {
6780
6926
  meetingId: this.id,
6781
6927
  },
6782
6928
  });
6929
+
6930
+ if (data.mediaType === 'share') {
6931
+ // @ts-ignore
6932
+ this.webex.internal.newMetrics.submitClientEvent({
6933
+ name: 'client.media.render.stop',
6934
+ payload: {
6935
+ mediaType: 'share',
6936
+ shareInstanceId: this.remoteShareInstanceId,
6937
+ },
6938
+ options: {
6939
+ meetingId: this.id,
6940
+ },
6941
+ });
6942
+
6943
+ this.shareCAEventSentStatus.receiveStop = true;
6944
+ }
6783
6945
  }
6784
6946
  });
6785
6947
  };
@@ -6796,7 +6958,10 @@ export default class Meeting extends StatelessWebexPlugin {
6796
6958
  * @param {AddMediaOptions} [options] Options for enabling/disabling audio/video
6797
6959
  * @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
6798
6960
  */
6799
- private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
6961
+ private async createMediaConnection(
6962
+ turnServerInfo?: TurnServerInfo,
6963
+ bundlePolicy?: BundlePolicy
6964
+ ) {
6800
6965
  this.rtcMetrics = this.isMultistream
6801
6966
  ? // @ts-ignore
6802
6967
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
@@ -6997,6 +7162,12 @@ export default class Meeting extends StatelessWebexPlugin {
6997
7162
  networkQualityMonitor: this.networkQualityMonitor,
6998
7163
  isMultistream: this.isMultistream,
6999
7164
  });
7165
+ this.shareCAEventSentStatus = {
7166
+ transmitStart: false,
7167
+ transmitStop: false,
7168
+ receiveStart: false,
7169
+ receiveStop: false,
7170
+ };
7000
7171
  this.setupStatsAnalyzerEventHandlers();
7001
7172
  this.networkQualityMonitor.on(
7002
7173
  NetworkQualityEventNames.NETWORK_QUALITY,
@@ -8681,6 +8852,9 @@ export default class Meeting extends StatelessWebexPlugin {
8681
8852
  LoggerProxy.logger.log(
8682
8853
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8683
8854
  );
8855
+
8856
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8857
+
8684
8858
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8685
8859
  correlationId: this.correlationId,
8686
8860
  muted,
@@ -8689,8 +8863,9 @@ export default class Meeting extends StatelessWebexPlugin {
8689
8863
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8690
8864
  // all we're doing here is adding metrics.
8691
8865
  // eslint-disable-next-line dot-notation
8692
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8866
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8693
8867
  isMultistream: this.isMultistream,
8868
+ frameRate: shareVideoStreamSettings?.frameRate,
8694
8869
  });
8695
8870
  };
8696
8871
 
@@ -9017,6 +9192,23 @@ export default class Meeting extends StatelessWebexPlugin {
9017
9192
  });
9018
9193
  }
9019
9194
 
9195
+ /**
9196
+ * Method to set post meeting data consent.
9197
+ *
9198
+ * @param {boolean} accept - whether consent accepted or declined
9199
+ * @returns {Promise}
9200
+ * @public
9201
+ * @memberof Meeting
9202
+ */
9203
+ public setPostMeetingDataConsent(accept: boolean) {
9204
+ return this.meetingRequest.setPostMeetingDataConsent({
9205
+ postMeetingDataConsent: accept,
9206
+ locusUrl: this.locusUrl,
9207
+ deviceUrl: this.deviceUrl,
9208
+ selfId: this.members.selfId,
9209
+ });
9210
+ }
9211
+
9020
9212
  /**
9021
9213
  * Throws if we don't have a media connection created
9022
9214
  *
@@ -9257,6 +9449,8 @@ export default class Meeting extends StatelessWebexPlugin {
9257
9449
 
9258
9450
  if (floorRequestNeeded) {
9259
9451
  this.localShareInstanceId = uuid.v4();
9452
+ this.shareCAEventSentStatus.transmitStart = false;
9453
+ this.shareCAEventSentStatus.transmitStop = false;
9260
9454
 
9261
9455
  // @ts-ignore
9262
9456
  this.webex.internal.newMetrics.submitClientEvent({