@webex/plugin-meetings 3.9.0-webinar5k.1 → 3.10.0

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 (138) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +24 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/index.js +22 -5
  6. package/dist/controls-options-manager/index.js.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/interceptors/index.js +7 -0
  10. package/dist/interceptors/index.js.map +1 -1
  11. package/dist/interceptors/locusRouteToken.js +116 -0
  12. package/dist/interceptors/locusRouteToken.js.map +1 -0
  13. package/dist/interpretation/index.js +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/controlsUtils.js +11 -2
  16. package/dist/locus-info/controlsUtils.js.map +1 -1
  17. package/dist/locus-info/index.js +76 -322
  18. package/dist/locus-info/index.js.map +1 -1
  19. package/dist/locus-info/parser.js +4 -1
  20. package/dist/locus-info/parser.js.map +1 -1
  21. package/dist/media/properties.js +53 -5
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +14 -0
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +467 -277
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/request.js +177 -14
  28. package/dist/meeting/request.js.map +1 -1
  29. package/dist/meeting/type.js +7 -0
  30. package/dist/meeting/type.js.map +1 -0
  31. package/dist/meeting/util.js +100 -3
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +29 -21
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +20 -16
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/member/index.js +9 -0
  38. package/dist/member/index.js.map +1 -1
  39. package/dist/member/util.js +10 -0
  40. package/dist/member/util.js.map +1 -1
  41. package/dist/members/index.js +10 -7
  42. package/dist/members/index.js.map +1 -1
  43. package/dist/members/util.js +7 -2
  44. package/dist/members/util.js.map +1 -1
  45. package/dist/metrics/constants.js +2 -1
  46. package/dist/metrics/constants.js.map +1 -1
  47. package/dist/multistream/mediaRequestManager.js +1 -1
  48. package/dist/multistream/mediaRequestManager.js.map +1 -1
  49. package/dist/multistream/remoteMedia.js +34 -5
  50. package/dist/multistream/remoteMedia.js.map +1 -1
  51. package/dist/multistream/remoteMediaGroup.js +42 -2
  52. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  53. package/dist/reachability/index.js +3 -3
  54. package/dist/reachability/index.js.map +1 -1
  55. package/dist/types/constants.d.ts +23 -0
  56. package/dist/types/controls-options-manager/index.d.ts +9 -1
  57. package/dist/types/interceptors/index.d.ts +2 -1
  58. package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
  59. package/dist/types/locus-info/index.d.ts +9 -54
  60. package/dist/types/media/properties.d.ts +21 -0
  61. package/dist/types/meeting/in-meeting-actions.d.ts +14 -0
  62. package/dist/types/meeting/index.d.ts +64 -29
  63. package/dist/types/meeting/request.d.ts +42 -0
  64. package/dist/types/meeting/type.d.ts +9 -0
  65. package/dist/types/meeting/util.d.ts +13 -0
  66. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  67. package/dist/types/meetings/index.d.ts +3 -1
  68. package/dist/types/member/index.d.ts +1 -0
  69. package/dist/types/member/util.d.ts +5 -0
  70. package/dist/types/members/index.d.ts +12 -11
  71. package/dist/types/members/util.d.ts +8 -4
  72. package/dist/types/metrics/constants.d.ts +1 -0
  73. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  74. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  75. package/dist/webinar/index.js +1 -1
  76. package/package.json +25 -27
  77. package/src/constants.ts +26 -2
  78. package/src/controls-options-manager/index.ts +26 -5
  79. package/src/index.ts +2 -1
  80. package/src/interceptors/index.ts +2 -1
  81. package/src/interceptors/locusRouteToken.ts +80 -0
  82. package/src/locus-info/controlsUtils.ts +18 -0
  83. package/src/locus-info/index.ts +69 -357
  84. package/src/locus-info/parser.ts +5 -1
  85. package/src/media/properties.ts +43 -0
  86. package/src/meeting/in-meeting-actions.ts +29 -0
  87. package/src/meeting/index.ts +296 -87
  88. package/src/meeting/request.ts +141 -0
  89. package/src/meeting/type.ts +9 -0
  90. package/src/meeting/util.ts +107 -3
  91. package/src/meeting-info/meeting-info-v2.ts +24 -5
  92. package/src/meetings/index.ts +15 -22
  93. package/src/member/index.ts +10 -0
  94. package/src/member/util.ts +14 -0
  95. package/src/members/index.ts +20 -10
  96. package/src/members/util.ts +20 -3
  97. package/src/metrics/constants.ts +1 -0
  98. package/src/multistream/mediaRequestManager.ts +7 -7
  99. package/src/multistream/remoteMedia.ts +34 -4
  100. package/src/multistream/remoteMediaGroup.ts +37 -2
  101. package/src/reachability/index.ts +3 -3
  102. package/test/unit/spec/common/browser-detection.js +0 -24
  103. package/test/unit/spec/controls-options-manager/index.js +47 -0
  104. package/test/unit/spec/fixture/locus.js +1 -0
  105. package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
  106. package/test/unit/spec/locus-info/index.js +80 -361
  107. package/test/unit/spec/locus-info/parser.js +3 -2
  108. package/test/unit/spec/media/properties.ts +137 -0
  109. package/test/unit/spec/meeting/in-meeting-actions.ts +14 -0
  110. package/test/unit/spec/meeting/index.js +637 -53
  111. package/test/unit/spec/meeting/muteState.js +32 -6
  112. package/test/unit/spec/meeting/request.js +21 -0
  113. package/test/unit/spec/meeting/utils.js +171 -18
  114. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  115. package/test/unit/spec/meetings/index.js +12 -5
  116. package/test/unit/spec/member/util.js +24 -0
  117. package/test/unit/spec/members/collection.js +120 -0
  118. package/test/unit/spec/members/index.js +107 -2
  119. package/test/unit/spec/members/request.js +55 -0
  120. package/test/unit/spec/members/utils.js +116 -14
  121. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  122. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  123. package/test/unit/spec/reachability/index.ts +158 -3
  124. package/test/unit/spec/roap/turnDiscovery.ts +3 -3
  125. package/dist/hashTree/constants.js +0 -23
  126. package/dist/hashTree/constants.js.map +0 -1
  127. package/dist/hashTree/hashTree.js +0 -516
  128. package/dist/hashTree/hashTree.js.map +0 -1
  129. package/dist/hashTree/hashTreeParser.js +0 -521
  130. package/dist/hashTree/hashTreeParser.js.map +0 -1
  131. package/dist/types/hashTree/constants.d.ts +0 -8
  132. package/dist/types/hashTree/hashTree.d.ts +0 -128
  133. package/dist/types/hashTree/hashTreeParser.d.ts +0 -152
  134. package/src/hashTree/constants.ts +0 -12
  135. package/src/hashTree/hashTree.ts +0 -460
  136. package/src/hashTree/hashTreeParser.ts +0 -556
  137. package/test/unit/spec/hashTree/hashTree.ts +0 -394
  138. package/test/unit/spec/hashTree/hashTreeParser.ts +0 -156
@@ -28,6 +28,8 @@ import {
28
28
  StatsAnalyzerEventNames,
29
29
  NetworkQualityEventNames,
30
30
  NetworkQualityMonitor,
31
+ StatsMonitor,
32
+ StatsMonitorEventNames,
31
33
  } from '@webex/internal-media-core';
32
34
 
33
35
  import {
@@ -66,7 +68,7 @@ import Media, {type BundlePolicy} from '../media';
66
68
  import MediaProperties from '../media/properties';
67
69
  import MeetingStateMachine from './state';
68
70
  import {createMuteState} from './muteState';
69
- import LocusInfo, {LocusDTO, LocusLLMEvent} from '../locus-info';
71
+ import LocusInfo from '../locus-info';
70
72
  import Metrics from '../metrics';
71
73
  import ReconnectionManager from '../reconnection-manager';
72
74
  import ReconnectionNotStartedError from '../common/errors/reconnection-not-started';
@@ -166,7 +168,7 @@ import MultistreamNotSupportedError from '../common/errors/multistream-not-suppo
166
168
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
167
169
  import {ReachabilityMetrics} from '../reachability/reachability.types';
168
170
  import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
169
- import {DataSet} from '../hashTree/hashTreeParser';
171
+ import {Invitee} from './type';
170
172
 
171
173
  // default callback so we don't call an undefined function, but in practice it should never be used
172
174
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -251,6 +253,7 @@ export type CallStateForMetrics = {
251
253
  loginType?: string;
252
254
  userNameInput?: string;
253
255
  emailInput?: string;
256
+ pstnCorrelationId?: string;
254
257
  };
255
258
 
256
259
  export const MEDIA_UPDATE_TYPE = {
@@ -268,6 +271,7 @@ export enum ScreenShareFloorStatus {
268
271
  type FetchMeetingInfoParams = {
269
272
  password?: string;
270
273
  registrationId?: string;
274
+ classificationId?: string;
271
275
  captchaCode?: string;
272
276
  extraParams?: Record<string, any>;
273
277
  sendCAevents?: boolean;
@@ -632,6 +636,7 @@ export default class Meeting extends StatelessWebexPlugin {
632
636
  shareStatus: string;
633
637
  screenShareFloorState: ScreenShareFloorStatus;
634
638
  statsAnalyzer: StatsAnalyzer;
639
+ statsMonitor: StatsMonitor;
635
640
  transcription: Transcription;
636
641
  updateMediaConnections: (mediaConnections: any[]) => void;
637
642
  userDisplayHints: any;
@@ -744,10 +749,11 @@ export default class Meeting extends StatelessWebexPlugin {
744
749
  /**
745
750
  * @param {Object} attrs
746
751
  * @param {Object} options
752
+ * @param {Function} callback - if provided, it will be called with the newly created meeting object as soon as the meeting.id is set
747
753
  * @constructor
748
754
  * @memberof Meeting
749
755
  */
750
- constructor(attrs: any, options: object) {
756
+ constructor(attrs: any, options: object, callback: (meeting: Meeting) => void) {
751
757
  super({}, options);
752
758
  /**
753
759
  * @instance
@@ -773,6 +779,11 @@ export default class Meeting extends StatelessWebexPlugin {
773
779
  * @memberof Meeting
774
780
  */
775
781
  this.id = uuid.v4();
782
+
783
+ if (callback) {
784
+ callback(this);
785
+ }
786
+
776
787
  /**
777
788
  * Call state used for metrics
778
789
  * @instance
@@ -1279,6 +1290,13 @@ export default class Meeting extends StatelessWebexPlugin {
1279
1290
  * @memberof Meeting
1280
1291
  */
1281
1292
  this.networkQualityMonitor = null;
1293
+ /**
1294
+ * @instance
1295
+ * @type {StatsMonitor}
1296
+ * @private
1297
+ * @memberof Meeting
1298
+ */
1299
+ this.statsMonitor = null;
1282
1300
  /**
1283
1301
  * Indicates network status of the webrtc media connection
1284
1302
  * @instance
@@ -1677,6 +1695,22 @@ export default class Meeting extends StatelessWebexPlugin {
1677
1695
  this.callStateForMetrics.correlationId = correlationId;
1678
1696
  }
1679
1697
 
1698
+ /**
1699
+ * Getter - Returns callStateForMetrics.pstnCorrelationId
1700
+ * @returns {string | undefined}
1701
+ */
1702
+ get pstnCorrelationId(): string | undefined {
1703
+ return this.callStateForMetrics.pstnCorrelationId;
1704
+ }
1705
+
1706
+ /**
1707
+ * Setter - sets callStateForMetrics.pstnCorrelationId
1708
+ * @param {string | undefined} correlationId
1709
+ */
1710
+ set pstnCorrelationId(correlationId: string | undefined) {
1711
+ this.callStateForMetrics.pstnCorrelationId = correlationId;
1712
+ }
1713
+
1680
1714
  /**
1681
1715
  * Getter - Returns callStateForMetrics.userNameInput
1682
1716
  * @returns {string}
@@ -1879,6 +1913,7 @@ export default class Meeting extends StatelessWebexPlugin {
1879
1913
  extraParams = {},
1880
1914
  sendCAevents = false,
1881
1915
  registrationId = null,
1916
+ classificationId = null,
1882
1917
  }): Promise<void> {
1883
1918
  try {
1884
1919
  const captchaInfo = captchaCode
@@ -1895,7 +1930,9 @@ export default class Meeting extends StatelessWebexPlugin {
1895
1930
  this.locusId,
1896
1931
  extraParams,
1897
1932
  {meetingId: this.id, sendCAevents},
1898
- registrationId
1933
+ registrationId,
1934
+ null,
1935
+ classificationId
1899
1936
  );
1900
1937
 
1901
1938
  this.parseMeetingInfo(info?.body, this.destination, info?.errors);
@@ -2939,6 +2976,18 @@ export default class Meeting extends StatelessWebexPlugin {
2939
2976
  );
2940
2977
  });
2941
2978
 
2979
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED, ({state}) => {
2980
+ Trigger.trigger(
2981
+ this,
2982
+ {
2983
+ file: 'meeting/index',
2984
+ function: 'setupLocusControlsListener',
2985
+ },
2986
+ EVENT_TRIGGERS.MEETING_CONTROLS_AUTO_END_MEETING_WARNING_UPDATED,
2987
+ {state}
2988
+ );
2989
+ });
2990
+
2942
2991
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2943
2992
  Trigger.trigger(
2944
2993
  this,
@@ -3059,12 +3108,16 @@ export default class Meeting extends StatelessWebexPlugin {
3059
3108
  // There is no concept of local/remote share for whiteboard
3060
3109
  // It does not matter who requested to share the whiteboard, everyone gets the same view
3061
3110
  else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
3062
- // WHITEBOARD - sharing whiteboard
3063
- // Webinar attendee should receive whiteboard as remote share
3064
- newShareStatus =
3065
- this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
3066
- ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
3067
- : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3111
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3112
+ // WHITEBOARD - sharing whiteboard
3113
+ // Webinar attendee should receive whiteboard as remote share
3114
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3115
+ } else if (this.guest) {
3116
+ // If user is a guest to a meeting, they should receive whiteboard as remote share
3117
+ newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
3118
+ } else {
3119
+ newShareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
3120
+ }
3068
3121
  }
3069
3122
  // or if content share is either released or null and whiteboard share is either released or null, no one is sharing
3070
3123
  else if (
@@ -3122,6 +3175,23 @@ export default class Meeting extends StatelessWebexPlugin {
3122
3175
  },
3123
3176
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD
3124
3177
  );
3178
+ // @ts-ignore
3179
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
3180
+ key: 'internal.client.share.stopped',
3181
+ });
3182
+ // @ts-ignore
3183
+ this.webex.internal.newMetrics.submitClientEvent({
3184
+ name: 'client.share.stopped',
3185
+ payload: {
3186
+ mediaType: 'whiteboard',
3187
+ shareDuration:
3188
+ // @ts-ignore
3189
+ this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
3190
+ },
3191
+ options: {
3192
+ meetingId: this.id,
3193
+ },
3194
+ });
3125
3195
  break;
3126
3196
 
3127
3197
  case SHARE_STATUS.NO_SHARE:
@@ -3140,6 +3210,14 @@ export default class Meeting extends StatelessWebexPlugin {
3140
3210
  this.shareCAEventSentStatus.receiveStart = false;
3141
3211
  this.shareCAEventSentStatus.receiveStop = false;
3142
3212
 
3213
+ let finalBeneficiaryId = contentShare.beneficiaryId;
3214
+ // In case of attendee in webinar, the whiteboard is shared by other participants
3215
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3216
+ if (!finalBeneficiaryId && whiteboardShare.beneficiaryId) {
3217
+ finalBeneficiaryId = whiteboardShare.beneficiaryId;
3218
+ }
3219
+ }
3220
+
3143
3221
  Trigger.trigger(
3144
3222
  this,
3145
3223
  {
@@ -3148,7 +3226,7 @@ export default class Meeting extends StatelessWebexPlugin {
3148
3226
  },
3149
3227
  EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
3150
3228
  {
3151
- memberId: contentShare.beneficiaryId,
3229
+ memberId: finalBeneficiaryId,
3152
3230
  url: contentShare.url,
3153
3231
  shareInstanceId: this.remoteShareInstanceId,
3154
3232
  annotationInfo: contentShare.annotation,
@@ -3290,27 +3368,31 @@ export default class Meeting extends StatelessWebexPlugin {
3290
3368
  * @memberof Meeting
3291
3369
  */
3292
3370
  private setUpLocusUrlListener() {
3293
- this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
3294
- this.members.locusUrlUpdate(payload);
3295
- this.breakouts.locusUrlUpdate(payload);
3296
- this.simultaneousInterpretation.locusUrlUpdate(payload);
3297
- this.annotation.locusUrlUpdate(payload);
3298
- this.locusUrl = payload;
3299
- this.locusId = this.locusUrl?.split('/').pop();
3300
- this.recordingController.setLocusUrl(this.locusUrl);
3301
- this.controlsOptionsManager.setLocusUrl(this.locusUrl);
3302
- this.webinar.locusUrlUpdate(payload);
3371
+ this.locusInfo.on(
3372
+ EVENTS.LOCUS_INFO_UPDATE_URL,
3373
+ (payload: {url: string; isMainLocus?: boolean}) => {
3374
+ const {url, isMainLocus} = payload;
3375
+ this.members.locusUrlUpdate(url);
3376
+ this.breakouts.locusUrlUpdate(url);
3377
+ this.simultaneousInterpretation.locusUrlUpdate(url);
3378
+ this.annotation.locusUrlUpdate(url);
3379
+ this.locusUrl = url;
3380
+ this.locusId = this.locusUrl?.split('/').pop();
3381
+ this.recordingController.setLocusUrl(this.locusUrl);
3382
+ this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3383
+ this.webinar.locusUrlUpdate(url);
3303
3384
 
3304
- Trigger.trigger(
3305
- this,
3306
- {
3307
- file: 'meeting/index',
3308
- function: 'setUpLocusSelfListener',
3309
- },
3310
- EVENT_TRIGGERS.MEETING_LOCUS_URL_UPDATE,
3311
- {locusUrl: payload}
3312
- );
3313
- });
3385
+ Trigger.trigger(
3386
+ this,
3387
+ {
3388
+ file: 'meeting/index',
3389
+ function: 'setUpLocusSelfListener',
3390
+ },
3391
+ EVENT_TRIGGERS.MEETING_LOCUS_URL_UPDATE,
3392
+ {locusUrl: url}
3393
+ );
3394
+ }
3395
+ );
3314
3396
  }
3315
3397
 
3316
3398
  /**
@@ -3830,49 +3912,43 @@ export default class Meeting extends StatelessWebexPlugin {
3830
3912
 
3831
3913
  /**
3832
3914
  * Invite a guest to the call that isn't normally part of this call
3833
- * @param {Object} invitee
3915
+ * @param {Invitee} invitee
3834
3916
  * @param {String} invitee.emailAddress
3835
3917
  * @param {String} invitee.email
3836
3918
  * @param {String} invitee.phoneNumber
3837
3919
  * @param {Boolean} [alertIfActive]
3920
+ * @param {Boolean} [invitee.skipEmailValidation]
3921
+ * @param {Boolean} [invitee.isInternalNumber]
3838
3922
  * @returns {Promise} see #members.addMember
3839
3923
  * @public
3840
3924
  * @memberof Meeting
3841
3925
  */
3842
- public invite(
3843
- invitee: {
3844
- emailAddress: string;
3845
- email: string;
3846
- phoneNumber: string;
3847
- roles: Array<string>;
3848
- },
3849
- alertIfActive = true
3850
- ) {
3926
+ public invite(invitee: Invitee, alertIfActive = true) {
3851
3927
  return this.members.addMember(invitee, alertIfActive);
3852
3928
  }
3853
3929
 
3854
3930
  /**
3855
3931
  * Cancel an outgoing phone call invitation made during a meeting
3856
- * @param {Object} invitee
3932
+ * @param {Invitee} invitee
3857
3933
  * @param {String} invitee.phoneNumber
3858
3934
  * @returns {Promise} see #members.cancelPhoneInvite
3859
3935
  * @public
3860
3936
  * @memberof Meeting
3861
3937
  */
3862
- public cancelPhoneInvite(invitee: {phoneNumber: string}) {
3938
+ public cancelPhoneInvite(invitee: Invitee) {
3863
3939
  return this.members.cancelPhoneInvite(invitee);
3864
3940
  }
3865
3941
 
3866
3942
  /**
3867
3943
  * Cancel an SIP/phone call invitation made during a meeting
3868
- * @param {Object} invitee
3944
+ * @param {Invitee} invitee
3869
3945
  * @param {String} invitee.memberId
3870
3946
  * @param {Boolean} [invitee.isInternalNumber] - When cancel phone invitation, if the number is internal
3871
3947
  * @returns {Promise} see #members.cancelInviteByMemberId
3872
3948
  * @public
3873
3949
  * @memberof Meeting
3874
3950
  */
3875
- public cancelInviteByMemberId(invitee: {memberId: string; isInternalNumber?: boolean}) {
3951
+ public cancelInviteByMemberId(invitee: Invitee) {
3876
3952
  return this.members.cancelInviteByMemberId(invitee);
3877
3953
  }
3878
3954
 
@@ -4159,6 +4235,7 @@ export default class Meeting extends StatelessWebexPlugin {
4159
4235
  this.userDisplayHints,
4160
4236
  this.selfUserPolicies
4161
4237
  ),
4238
+ showAutoEndMeetingWarning: MeetingUtil.showAutoEndMeetingWarning(this.userDisplayHints),
4162
4239
  canRaiseHand: MeetingUtil.canUserRaiseHand(this.userDisplayHints),
4163
4240
  canLowerAllHands: MeetingUtil.canUserLowerAllHands(this.userDisplayHints),
4164
4241
  canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(this.userDisplayHints),
@@ -4171,8 +4248,16 @@ export default class Meeting extends StatelessWebexPlugin {
4171
4248
  isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(this.userDisplayHints),
4172
4249
  canStartManualCaption: MeetingUtil.canStartManualCaption(this.userDisplayHints),
4173
4250
  canStopManualCaption: MeetingUtil.canStopManualCaption(this.userDisplayHints),
4251
+ isLocalRecordingStarted: MeetingUtil.isLocalRecordingStarted(this.userDisplayHints),
4252
+ isLocalRecordingStopped: MeetingUtil.isLocalRecordingStopped(this.userDisplayHints),
4253
+ isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
4254
+ isLocalStreamingStarted: MeetingUtil.isLocalStreamingStarted(this.userDisplayHints),
4255
+ isLocalStreamingStopped: MeetingUtil.isLocalStreamingStopped(this.userDisplayHints),
4174
4256
  isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
4175
4257
  isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
4258
+ isSpokenLanguageAutoDetectionEnabled: MeetingUtil.isSpokenLanguageAutoDetectionEnabled(
4259
+ this.userDisplayHints
4260
+ ),
4176
4261
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
4177
4262
  canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(this.userDisplayHints),
4178
4263
  isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
@@ -4479,13 +4564,11 @@ export default class Meeting extends StatelessWebexPlugin {
4479
4564
  setLocus(
4480
4565
  locus:
4481
4566
  | {
4482
- locus: LocusDTO;
4483
4567
  mediaConnections: Array<any>;
4484
4568
  locusUrl: string;
4485
4569
  locusId: string;
4486
4570
  mediaId: string;
4487
4571
  host: object;
4488
- dataSets: DataSet[];
4489
4572
  }
4490
4573
  | any
4491
4574
  ) {
@@ -4499,7 +4582,7 @@ export default class Meeting extends StatelessWebexPlugin {
4499
4582
  this.selfId = locus.selfId;
4500
4583
  this.mediaId = locus.mediaId;
4501
4584
  this.hostId = mtgLocus.host ? mtgLocus.host.id : this.hostId;
4502
- this.locusInfo.initialSetup(mtgLocus, locus.dataSets);
4585
+ this.locusInfo.initialSetup(mtgLocus);
4503
4586
  }
4504
4587
 
4505
4588
  /**
@@ -5612,21 +5695,6 @@ export default class Meeting extends StatelessWebexPlugin {
5612
5695
  }
5613
5696
  }
5614
5697
 
5615
- /** Handles Locus LLM events
5616
- *
5617
- * @param {LocusLLMEvent} event - The Locus LLM event to process
5618
- * @returns {void}
5619
- */
5620
- private processLocusLLMEvent = (event: LocusLLMEvent): void => {
5621
- if (event.data.eventType === 'locus.state_message') {
5622
- this.locusInfo.parse(this, event.data);
5623
- } else {
5624
- LoggerProxy.logger.warn(
5625
- `Meeting:index#processLocusLLMEvent --> Unknown event type: ${event.data.eventType}`
5626
- );
5627
- }
5628
- };
5629
-
5630
5698
  /**
5631
5699
  * Callback called when a relay event is received from meeting LLM Connection
5632
5700
  * @param {RelayEvent} e Event object coming from LLM Connection
@@ -5945,15 +6013,6 @@ export default class Meeting extends StatelessWebexPlugin {
5945
6013
  this.meetingFiniteStateMachine.fail(error);
5946
6014
  LoggerProxy.logger.error('Meeting:index#join --> Failed', error);
5947
6015
 
5948
- // @ts-ignore
5949
- this.webex.internal.newMetrics.submitClientEvent({
5950
- name: 'client.locus.join.response',
5951
- payload: {
5952
- identifiers: {meetingLookupUrl: this.meetingInfo?.meetingLookupUrl},
5953
- },
5954
- options: {meetingId: this.id, rawError: error},
5955
- });
5956
-
5957
6016
  // TODO: change this to error codes and pre defined dictionary
5958
6017
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_FAILURE, {
5959
6018
  correlation_id: this.correlationId,
@@ -6047,8 +6106,6 @@ export default class Meeting extends StatelessWebexPlugin {
6047
6106
  );
6048
6107
  // @ts-ignore - Fix type
6049
6108
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6050
- // @ts-ignore - Fix type
6051
- this.webex.internal.llm.off('event:locus.state_message', this.processLocusLLMEvent);
6052
6109
  }
6053
6110
 
6054
6111
  if (!isJoined) {
@@ -6063,10 +6120,6 @@ export default class Meeting extends StatelessWebexPlugin {
6063
6120
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6064
6121
  // @ts-ignore - Fix type
6065
6122
  this.webex.internal.llm.on('event:relay.event', this.processRelayEvent);
6066
- // @ts-ignore - Fix type
6067
- this.webex.internal.llm.off('event:locus.state_message', this.processLocusLLMEvent);
6068
- // @ts-ignore - Fix type
6069
- this.webex.internal.llm.on('event:locus.state_message', this.processLocusLLMEvent);
6070
6123
  LoggerProxy.logger.info(
6071
6124
  'Meeting:index#updateLLMConnection --> enabled to receive relay events!'
6072
6125
  );
@@ -6109,8 +6162,9 @@ export default class Meeting extends StatelessWebexPlugin {
6109
6162
  */
6110
6163
  private dialInPstn() {
6111
6164
  if (this.isPhoneProvisioned(this.dialInDeviceStatus)) return Promise.resolve(); // prevent multiple dial in devices from being provisioned
6165
+ this.pstnCorrelationId = uuid.v4();
6112
6166
 
6113
- const {correlationId, locusUrl} = this;
6167
+ const {pstnCorrelationId, locusUrl} = this;
6114
6168
 
6115
6169
  if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
6116
6170
 
@@ -6118,7 +6172,7 @@ export default class Meeting extends StatelessWebexPlugin {
6118
6172
  this.meetingRequest
6119
6173
  // @ts-ignore
6120
6174
  .dialIn({
6121
- correlationId,
6175
+ correlationId: pstnCorrelationId,
6122
6176
  dialInUrl: this.dialInUrl,
6123
6177
  locusUrl,
6124
6178
  clientUrl: this.deviceUrl,
@@ -6127,12 +6181,17 @@ export default class Meeting extends StatelessWebexPlugin {
6127
6181
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
6128
6182
  correlation_id: this.correlationId,
6129
6183
  dial_in_url: this.dialInUrl,
6184
+ dial_in_correlation_id: pstnCorrelationId,
6130
6185
  locus_id: locusUrl.split('/').pop(),
6131
6186
  client_url: this.deviceUrl,
6132
6187
  reason: error.error?.message,
6133
6188
  stack: error.stack,
6134
6189
  });
6135
6190
 
6191
+ if (this.pstnCorrelationId === pstnCorrelationId) {
6192
+ this.pstnCorrelationId = undefined;
6193
+ }
6194
+
6136
6195
  return Promise.reject(error);
6137
6196
  })
6138
6197
  );
@@ -6147,8 +6206,9 @@ export default class Meeting extends StatelessWebexPlugin {
6147
6206
  */
6148
6207
  private dialOutPstn(phoneNumber: string) {
6149
6208
  if (this.isPhoneProvisioned(this.dialOutDeviceStatus)) return Promise.resolve(); // prevent multiple dial out devices from being provisioned
6209
+ this.pstnCorrelationId = uuid.v4();
6150
6210
 
6151
- const {correlationId, locusUrl} = this;
6211
+ const {locusUrl, pstnCorrelationId} = this;
6152
6212
 
6153
6213
  if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
6154
6214
 
@@ -6156,7 +6216,7 @@ export default class Meeting extends StatelessWebexPlugin {
6156
6216
  this.meetingRequest
6157
6217
  // @ts-ignore
6158
6218
  .dialOut({
6159
- correlationId,
6219
+ correlationId: pstnCorrelationId,
6160
6220
  dialOutUrl: this.dialOutUrl,
6161
6221
  phoneNumber,
6162
6222
  locusUrl,
@@ -6166,12 +6226,17 @@ export default class Meeting extends StatelessWebexPlugin {
6166
6226
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
6167
6227
  correlation_id: this.correlationId,
6168
6228
  dial_out_url: this.dialOutUrl,
6229
+ dial_out_correlation_id: pstnCorrelationId,
6169
6230
  locus_id: locusUrl.split('/').pop(),
6170
6231
  client_url: this.deviceUrl,
6171
6232
  reason: error.error?.message,
6172
6233
  stack: error.stack,
6173
6234
  });
6174
6235
 
6236
+ if (this.pstnCorrelationId === pstnCorrelationId) {
6237
+ this.pstnCorrelationId = undefined;
6238
+ }
6239
+
6175
6240
  return Promise.reject(error);
6176
6241
  })
6177
6242
  );
@@ -6185,6 +6250,8 @@ export default class Meeting extends StatelessWebexPlugin {
6185
6250
  * @returns {Promise}
6186
6251
  */
6187
6252
  public disconnectPhoneAudio() {
6253
+ const correlationToClear = this.pstnCorrelationId;
6254
+
6188
6255
  return Promise.all([
6189
6256
  this.isPhoneProvisioned(this.dialInDeviceStatus)
6190
6257
  ? MeetingUtil.disconnectPhoneAudio(this, this.dialInUrl)
@@ -6192,7 +6259,11 @@ export default class Meeting extends StatelessWebexPlugin {
6192
6259
  this.isPhoneProvisioned(this.dialOutDeviceStatus)
6193
6260
  ? MeetingUtil.disconnectPhoneAudio(this, this.dialOutUrl)
6194
6261
  : Promise.resolve(),
6195
- ]);
6262
+ ]).then(() => {
6263
+ if (this.pstnCorrelationId === correlationToClear) {
6264
+ this.pstnCorrelationId = undefined;
6265
+ }
6266
+ });
6196
6267
  }
6197
6268
 
6198
6269
  /**
@@ -6769,6 +6840,10 @@ export default class Meeting extends StatelessWebexPlugin {
6769
6840
  // @ts-ignore
6770
6841
  this.webex.internal.newMetrics.submitClientEvent({
6771
6842
  name: 'client.ice.start',
6843
+ payload: {
6844
+ // @ts-ignore
6845
+ labels: MeetingUtil.getCaEventLabelsForIpVersion(this.webex),
6846
+ },
6772
6847
  options: {
6773
6848
  meetingId: this.id,
6774
6849
  },
@@ -6938,10 +7013,10 @@ export default class Meeting extends StatelessWebexPlugin {
6938
7013
  }
6939
7014
  }
6940
7015
 
6941
- // Count members that are in the meeting.
7016
+ // Count members that are in the meeting or in the lobby.
6942
7017
  const {members} = this.getMembers().membersCollection;
6943
7018
  event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6944
- (member: Member) => member.isInMeeting
7019
+ (member: Member) => member.isInMeeting || member.isInLobby
6945
7020
  ).length;
6946
7021
 
6947
7022
  // @ts-ignore
@@ -7300,10 +7375,12 @@ export default class Meeting extends StatelessWebexPlugin {
7300
7375
  if (this.config.stats.enableStatsAnalyzer) {
7301
7376
  // @ts-ignore - config coming from registerPlugin
7302
7377
  this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
7378
+ this.statsMonitor = new StatsMonitor();
7303
7379
  this.statsAnalyzer = new StatsAnalyzer({
7304
7380
  // @ts-ignore - config coming from registerPlugin
7305
7381
  config: this.config.stats,
7306
7382
  networkQualityMonitor: this.networkQualityMonitor,
7383
+ statsMonitor: this.statsMonitor,
7307
7384
  isMultistream: this.isMultistream,
7308
7385
  });
7309
7386
  this.shareCAEventSentStatus = {
@@ -7317,6 +7394,33 @@ export default class Meeting extends StatelessWebexPlugin {
7317
7394
  NetworkQualityEventNames.NETWORK_QUALITY,
7318
7395
  this.sendNetworkQualityEvent.bind(this)
7319
7396
  );
7397
+
7398
+ this.statsMonitor.on(StatsMonitorEventNames.INBOUND_AUDIO_ISSUE, (data) => {
7399
+ // Before forwarding any inbound audio issues to the app, make sure that we have at least one other
7400
+ // participant in the meeting with unmuted audio.
7401
+ // We don't check this.mediaProperties.mediaDirection here, because that's already handled in statsAnalyzer,
7402
+ // so we won't get this event if we are not setup to receive any audio
7403
+ const atLeastOneUnmutedOtherMember = Object.values(
7404
+ this.members.membersCollection.getAll()
7405
+ ).find((member) => {
7406
+ return !member.isSelf && !member.isPairedWithSelf && !member.isAudioMuted;
7407
+ });
7408
+
7409
+ if (atLeastOneUnmutedOtherMember) {
7410
+ this.mediaProperties.sendMediaIssueMetric(
7411
+ 'inbound_audio',
7412
+ data.issueSubType,
7413
+ this.correlationId
7414
+ );
7415
+
7416
+ Trigger.trigger(
7417
+ this,
7418
+ {file: 'meeting/index', function: 'createStatsAnalyzer'},
7419
+ EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
7420
+ data
7421
+ );
7422
+ }
7423
+ });
7320
7424
  }
7321
7425
  }
7322
7426
 
@@ -7615,6 +7719,10 @@ export default class Meeting extends StatelessWebexPlugin {
7615
7719
  }
7616
7720
 
7617
7721
  this.statsAnalyzer = null;
7722
+ this.networkQualityMonitor?.removeAllListeners();
7723
+ this.networkQualityMonitor = null;
7724
+ this.statsMonitor?.removeAllListeners();
7725
+ this.statsMonitor = null;
7618
7726
 
7619
7727
  // when media fails, we want to upload a webrtc dump to see whats going on
7620
7728
  // this function is async, but returns once the stats have been gathered
@@ -7638,6 +7746,10 @@ export default class Meeting extends StatelessWebexPlugin {
7638
7746
  await this.statsAnalyzer.stopAnalyzer();
7639
7747
  }
7640
7748
  this.statsAnalyzer = null;
7749
+ this.networkQualityMonitor?.removeAllListeners();
7750
+ this.networkQualityMonitor = null;
7751
+ this.statsMonitor?.removeAllListeners();
7752
+ this.statsMonitor = null;
7641
7753
 
7642
7754
  this.isMultistream = false;
7643
7755
 
@@ -7809,6 +7921,9 @@ export default class Meeting extends StatelessWebexPlugin {
7809
7921
 
7810
7922
  this.allowMediaInLobby = options?.allowMediaInLobby;
7811
7923
 
7924
+ // @ts-ignore
7925
+ const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
7926
+
7812
7927
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
7813
7928
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
7814
7929
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
@@ -7907,6 +8022,7 @@ export default class Meeting extends StatelessWebexPlugin {
7907
8022
  locus_id: this.locusUrl.split('/').pop(),
7908
8023
  connectionType,
7909
8024
  ipVersion,
8025
+ ipver,
7910
8026
  selectedCandidatePairChanges,
7911
8027
  numTransports,
7912
8028
  isMultistream: this.isMultistream,
@@ -7975,6 +8091,7 @@ export default class Meeting extends StatelessWebexPlugin {
7975
8091
  ...reachabilityMetrics,
7976
8092
  ...iceCandidateErrors,
7977
8093
  iceCandidatesCount: this.iceCandidatesCount,
8094
+ ipver,
7978
8095
  });
7979
8096
 
7980
8097
  await this.cleanUpOnAddMediaFailure();
@@ -8414,6 +8531,10 @@ export default class Meeting extends StatelessWebexPlugin {
8414
8531
  }
8415
8532
 
8416
8533
  if (whiteboard) {
8534
+ // @ts-ignore
8535
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8536
+ key: 'internal.client.share.initiated',
8537
+ });
8417
8538
  // @ts-ignore
8418
8539
  this.webex.internal.newMetrics.submitClientEvent({
8419
8540
  name: 'client.share.initiated',
@@ -8473,11 +8594,17 @@ export default class Meeting extends StatelessWebexPlugin {
8473
8594
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
8474
8595
 
8475
8596
  if (whiteboard) {
8597
+ // @ts-ignore
8598
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8599
+ key: 'internal.client.share.stopped',
8600
+ });
8476
8601
  // @ts-ignore
8477
8602
  this.webex.internal.newMetrics.submitClientEvent({
8478
8603
  name: 'client.share.stopped',
8479
8604
  payload: {
8480
8605
  mediaType: 'whiteboard',
8606
+ // @ts-ignore
8607
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8481
8608
  },
8482
8609
  options: {
8483
8610
  meetingId: this.id,
@@ -8635,12 +8762,18 @@ export default class Meeting extends StatelessWebexPlugin {
8635
8762
  }
8636
8763
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
8637
8764
  if (content) {
8765
+ // @ts-ignore
8766
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
8767
+ key: 'internal.client.share.stopped',
8768
+ });
8638
8769
  // @ts-ignore
8639
8770
  this.webex.internal.newMetrics.submitClientEvent({
8640
8771
  name: 'client.share.stopped',
8641
8772
  payload: {
8642
8773
  mediaType: 'share',
8643
8774
  shareInstanceId: this.localShareInstanceId,
8775
+ // @ts-ignore
8776
+ shareDuration: this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
8644
8777
  },
8645
8778
  options: {meetingId: this.id},
8646
8779
  });
@@ -9238,8 +9371,6 @@ export default class Meeting extends StatelessWebexPlugin {
9238
9371
 
9239
9372
  // @ts-ignore - fix types
9240
9373
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
9241
- // @ts-ignore - Fix type
9242
- this.webex.internal.llm.off('event:locus.state_message', this.processLocusLLMEvent);
9243
9374
  };
9244
9375
 
9245
9376
  /**
@@ -9337,6 +9468,36 @@ export default class Meeting extends StatelessWebexPlugin {
9337
9468
  return Promise.reject(new Error('Error sending reaction, service url not found.'));
9338
9469
  }
9339
9470
 
9471
+ /**
9472
+ * Extend the current meeting duration.
9473
+ *
9474
+ * @param {number} extensionMinutes - how many minutes to extend
9475
+ * @returns {Promise}
9476
+ * @public
9477
+ * @memberof Meeting
9478
+ */
9479
+ public extendMeeting({
9480
+ meetingPolicyUrl,
9481
+ meetingInstanceId,
9482
+ participantId,
9483
+ extensionMinutes = 30,
9484
+ }) {
9485
+ if (!meetingInstanceId || !participantId) {
9486
+ return Promise.reject(new Error('Missing meetingInstanceId or participantId'));
9487
+ }
9488
+
9489
+ if (!meetingPolicyUrl) {
9490
+ return Promise.reject(new Error('Missing meetingPolicyUrl'));
9491
+ }
9492
+
9493
+ return this.meetingRequest.extendMeeting({
9494
+ meetingInstanceId,
9495
+ participantId,
9496
+ extensionMinutes,
9497
+ meetingPolicyUrl,
9498
+ });
9499
+ }
9500
+
9340
9501
  /**
9341
9502
  * Method to enable or disable reactions inside the meeting.
9342
9503
  *
@@ -9619,6 +9780,11 @@ export default class Meeting extends StatelessWebexPlugin {
9619
9780
  this.shareCAEventSentStatus.transmitStart = false;
9620
9781
  this.shareCAEventSentStatus.transmitStop = false;
9621
9782
 
9783
+ // @ts-ignore
9784
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
9785
+ key: 'internal.client.share.initiated',
9786
+ });
9787
+
9622
9788
  // @ts-ignore
9623
9789
  this.webex.internal.newMetrics.submitClientEvent({
9624
9790
  name: 'client.share.initiated',
@@ -9861,4 +10027,47 @@ export default class Meeting extends StatelessWebexPlugin {
9861
10027
 
9862
10028
  return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9863
10029
  }
10030
+
10031
+ /**
10032
+ * Notifies the host with the given meeting UUID and display names.
10033
+ *
10034
+ * @param {string} meetingUuid - The UUID of the meeting.
10035
+ * @param {string[]} displayName - An array of display names to notify the host with.
10036
+ * @returns {Promise<any>} The result of the notifyHost request.
10037
+ */
10038
+ notifyHost(meetingUuid: string, displayName: string[]) {
10039
+ return this.meetingRequest.notifyHost(
10040
+ this.meetingInfo.siteFullUrl,
10041
+ this.locusId,
10042
+ meetingUuid,
10043
+ displayName
10044
+ );
10045
+ }
10046
+
10047
+ /**
10048
+ * Call out a SIP participant to a meeting
10049
+ * @param {string} address - The SIP address or phone number
10050
+ * @param {string} displayName - The display name for the participant
10051
+ * @param {string} [correlationId] - Optional correlation ID
10052
+ * @returns {Promise} Promise that resolves when the call-out is initiated
10053
+ */
10054
+ sipCallOut(address: string, displayName: string) {
10055
+ return this.meetingRequest.sipCallOut(
10056
+ this.meetingInfo.meetingId,
10057
+ this.meetingInfo.meetingId,
10058
+ address,
10059
+ displayName
10060
+ );
10061
+ }
10062
+
10063
+ /**
10064
+ * Cancel an ongoing SIP call-out
10065
+ * @param {string} participantId - The participant ID to cancel
10066
+ * @returns {Promise} Promise that resolves when the call-out is cancelled
10067
+ * @public
10068
+ * @memberof Meetings
10069
+ */
10070
+ cancelSipCallOut(participantId: string) {
10071
+ return this.meetingRequest.cancelSipCallOut(participantId);
10072
+ }
9864
10073
  }