@webex/plugin-meetings 3.9.0 → 3.10.0-multi-llms.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) 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 +21 -1
  4. package/dist/common/errors/webex-errors.js.map +1 -1
  5. package/dist/constants.js +9 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/controls-options-manager/index.js +22 -5
  8. package/dist/controls-options-manager/index.js.map +1 -1
  9. package/dist/index.js +9 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/interceptors/index.js +7 -0
  12. package/dist/interceptors/index.js.map +1 -1
  13. package/dist/interceptors/locusRouteToken.js +116 -0
  14. package/dist/interceptors/locusRouteToken.js.map +1 -0
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/controlsUtils.js +11 -2
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/index.js +56 -14
  20. package/dist/locus-info/index.js.map +1 -1
  21. package/dist/locus-info/parser.js +4 -1
  22. package/dist/locus-info/parser.js.map +1 -1
  23. package/dist/media/index.js +5 -0
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/media/properties.js +53 -5
  26. package/dist/media/properties.js.map +1 -1
  27. package/dist/meeting/in-meeting-actions.js +8 -0
  28. package/dist/meeting/in-meeting-actions.js.map +1 -1
  29. package/dist/meeting/index.js +340 -186
  30. package/dist/meeting/index.js.map +1 -1
  31. package/dist/meeting/muteState.js +2 -5
  32. package/dist/meeting/muteState.js.map +1 -1
  33. package/dist/meeting/request.js +177 -14
  34. package/dist/meeting/request.js.map +1 -1
  35. package/dist/meeting/util.js +39 -11
  36. package/dist/meeting/util.js.map +1 -1
  37. package/dist/meeting-info/meeting-info-v2.js +29 -21
  38. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  39. package/dist/meetings/index.js +31 -25
  40. package/dist/meetings/index.js.map +1 -1
  41. package/dist/member/index.js +9 -0
  42. package/dist/member/index.js.map +1 -1
  43. package/dist/member/types.js.map +1 -1
  44. package/dist/member/util.js +10 -0
  45. package/dist/member/util.js.map +1 -1
  46. package/dist/members/collection.js +13 -0
  47. package/dist/members/collection.js.map +1 -1
  48. package/dist/members/index.js +42 -20
  49. package/dist/members/index.js.map +1 -1
  50. package/dist/members/util.js +7 -2
  51. package/dist/members/util.js.map +1 -1
  52. package/dist/metrics/constants.js +2 -1
  53. package/dist/metrics/constants.js.map +1 -1
  54. package/dist/reachability/index.js +3 -3
  55. package/dist/reachability/index.js.map +1 -1
  56. package/dist/types/common/errors/webex-errors.d.ts +12 -0
  57. package/dist/types/constants.d.ts +7 -0
  58. package/dist/types/controls-options-manager/index.d.ts +9 -1
  59. package/dist/types/index.d.ts +2 -1
  60. package/dist/types/interceptors/index.d.ts +2 -1
  61. package/dist/types/interceptors/locusRouteToken.d.ts +38 -0
  62. package/dist/types/locus-info/index.d.ts +56 -2
  63. package/dist/types/media/properties.d.ts +21 -0
  64. package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
  65. package/dist/types/meeting/index.d.ts +41 -1
  66. package/dist/types/meeting/request.d.ts +42 -0
  67. package/dist/types/meeting/util.d.ts +13 -3
  68. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  69. package/dist/types/meetings/index.d.ts +3 -1
  70. package/dist/types/member/index.d.ts +1 -0
  71. package/dist/types/member/types.d.ts +1 -0
  72. package/dist/types/member/util.d.ts +5 -0
  73. package/dist/types/members/collection.d.ts +6 -0
  74. package/dist/types/members/index.d.ts +12 -2
  75. package/dist/types/members/util.d.ts +6 -3
  76. package/dist/types/metrics/constants.d.ts +1 -0
  77. package/dist/webinar/index.js +1 -1
  78. package/package.json +24 -24
  79. package/src/common/errors/webex-errors.ts +19 -0
  80. package/src/constants.ts +10 -0
  81. package/src/controls-options-manager/index.ts +26 -5
  82. package/src/index.ts +4 -1
  83. package/src/interceptors/index.ts +2 -1
  84. package/src/interceptors/locusRouteToken.ts +80 -0
  85. package/src/locus-info/controlsUtils.ts +18 -0
  86. package/src/locus-info/index.ts +99 -17
  87. package/src/locus-info/parser.ts +5 -1
  88. package/src/media/index.ts +6 -0
  89. package/src/media/properties.ts +43 -0
  90. package/src/meeting/in-meeting-actions.ts +16 -0
  91. package/src/meeting/index.ts +207 -25
  92. package/src/meeting/muteState.ts +2 -6
  93. package/src/meeting/request.ts +141 -0
  94. package/src/meeting/util.ts +50 -20
  95. package/src/meeting-info/meeting-info-v2.ts +24 -5
  96. package/src/meetings/index.ts +9 -3
  97. package/src/member/index.ts +10 -0
  98. package/src/member/types.ts +1 -0
  99. package/src/member/util.ts +14 -0
  100. package/src/members/collection.ts +11 -0
  101. package/src/members/index.ts +38 -5
  102. package/src/members/util.ts +18 -2
  103. package/src/metrics/constants.ts +1 -0
  104. package/src/reachability/index.ts +3 -3
  105. package/test/unit/spec/common/browser-detection.js +0 -24
  106. package/test/unit/spec/controls-options-manager/index.js +47 -0
  107. package/test/unit/spec/fixture/locus.js +1 -0
  108. package/test/unit/spec/interceptors/locusRouteToken.ts +87 -0
  109. package/test/unit/spec/locus-info/index.js +91 -15
  110. package/test/unit/spec/locus-info/parser.js +3 -2
  111. package/test/unit/spec/media/index.ts +140 -9
  112. package/test/unit/spec/media/properties.ts +137 -0
  113. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
  114. package/test/unit/spec/meeting/index.js +469 -88
  115. package/test/unit/spec/meeting/muteState.js +32 -6
  116. package/test/unit/spec/meeting/request.js +21 -0
  117. package/test/unit/spec/meeting/utils.js +48 -16
  118. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  119. package/test/unit/spec/meetings/index.js +10 -7
  120. package/test/unit/spec/member/util.js +24 -0
  121. package/test/unit/spec/members/collection.js +120 -0
  122. package/test/unit/spec/members/index.js +72 -3
  123. package/test/unit/spec/members/request.js +55 -0
  124. package/test/unit/spec/members/utils.js +116 -14
  125. package/test/unit/spec/reachability/index.ts +158 -3
  126. package/test/unit/spec/roap/turnDiscovery.ts +3 -3
@@ -28,6 +28,9 @@ import {
28
28
  StatsAnalyzerEventNames,
29
29
  NetworkQualityEventNames,
30
30
  NetworkQualityMonitor,
31
+ StatsMonitor,
32
+ StatsMonitorEventNames,
33
+ InboundAudioIssueSubTypes,
31
34
  } from '@webex/internal-media-core';
32
35
 
33
36
  import {
@@ -55,6 +58,7 @@ import {
55
58
  NoMediaEstablishedYetError,
56
59
  UserNotJoinedError,
57
60
  AddMediaFailed,
61
+ SdpResponseTimeoutError,
58
62
  } from '../common/errors/webex-errors';
59
63
 
60
64
  import LoggerProxy from '../common/logs/logger-proxy';
@@ -269,6 +273,7 @@ export enum ScreenShareFloorStatus {
269
273
  type FetchMeetingInfoParams = {
270
274
  password?: string;
271
275
  registrationId?: string;
276
+ classificationId?: string;
272
277
  captchaCode?: string;
273
278
  extraParams?: Record<string, any>;
274
279
  sendCAevents?: boolean;
@@ -633,6 +638,7 @@ export default class Meeting extends StatelessWebexPlugin {
633
638
  shareStatus: string;
634
639
  screenShareFloorState: ScreenShareFloorStatus;
635
640
  statsAnalyzer: StatsAnalyzer;
641
+ statsMonitor: StatsMonitor;
636
642
  transcription: Transcription;
637
643
  updateMediaConnections: (mediaConnections: any[]) => void;
638
644
  userDisplayHints: any;
@@ -1286,6 +1292,13 @@ export default class Meeting extends StatelessWebexPlugin {
1286
1292
  * @memberof Meeting
1287
1293
  */
1288
1294
  this.networkQualityMonitor = null;
1295
+ /**
1296
+ * @instance
1297
+ * @type {StatsMonitor}
1298
+ * @private
1299
+ * @memberof Meeting
1300
+ */
1301
+ this.statsMonitor = null;
1289
1302
  /**
1290
1303
  * Indicates network status of the webrtc media connection
1291
1304
  * @instance
@@ -1902,6 +1915,7 @@ export default class Meeting extends StatelessWebexPlugin {
1902
1915
  extraParams = {},
1903
1916
  sendCAevents = false,
1904
1917
  registrationId = null,
1918
+ classificationId = null,
1905
1919
  }): Promise<void> {
1906
1920
  try {
1907
1921
  const captchaInfo = captchaCode
@@ -1918,7 +1932,9 @@ export default class Meeting extends StatelessWebexPlugin {
1918
1932
  this.locusId,
1919
1933
  extraParams,
1920
1934
  {meetingId: this.id, sendCAevents},
1921
- registrationId
1935
+ registrationId,
1936
+ null,
1937
+ classificationId
1922
1938
  );
1923
1939
 
1924
1940
  this.parseMeetingInfo(info?.body, this.destination, info?.errors);
@@ -2962,6 +2978,18 @@ export default class Meeting extends StatelessWebexPlugin {
2962
2978
  );
2963
2979
  });
2964
2980
 
2981
+ this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_AUTO_END_MEETING_WARNING_CHANGED, ({state}) => {
2982
+ Trigger.trigger(
2983
+ this,
2984
+ {
2985
+ file: 'meeting/index',
2986
+ function: 'setupLocusControlsListener',
2987
+ },
2988
+ EVENT_TRIGGERS.MEETING_CONTROLS_AUTO_END_MEETING_WARNING_UPDATED,
2989
+ {state}
2990
+ );
2991
+ });
2992
+
2965
2993
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED, ({state}) => {
2966
2994
  Trigger.trigger(
2967
2995
  this,
@@ -3149,6 +3177,23 @@ export default class Meeting extends StatelessWebexPlugin {
3149
3177
  },
3150
3178
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD
3151
3179
  );
3180
+ // @ts-ignore
3181
+ this.webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp({
3182
+ key: 'internal.client.share.stopped',
3183
+ });
3184
+ // @ts-ignore
3185
+ this.webex.internal.newMetrics.submitClientEvent({
3186
+ name: 'client.share.stopped',
3187
+ payload: {
3188
+ mediaType: 'whiteboard',
3189
+ shareDuration:
3190
+ // @ts-ignore
3191
+ this.webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration(),
3192
+ },
3193
+ options: {
3194
+ meetingId: this.id,
3195
+ },
3196
+ });
3152
3197
  break;
3153
3198
 
3154
3199
  case SHARE_STATUS.NO_SHARE:
@@ -3167,6 +3212,14 @@ export default class Meeting extends StatelessWebexPlugin {
3167
3212
  this.shareCAEventSentStatus.receiveStart = false;
3168
3213
  this.shareCAEventSentStatus.receiveStop = false;
3169
3214
 
3215
+ let finalBeneficiaryId = contentShare.beneficiaryId;
3216
+ // In case of attendee in webinar, the whiteboard is shared by other participants
3217
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3218
+ if (!finalBeneficiaryId && whiteboardShare.beneficiaryId) {
3219
+ finalBeneficiaryId = whiteboardShare.beneficiaryId;
3220
+ }
3221
+ }
3222
+
3170
3223
  Trigger.trigger(
3171
3224
  this,
3172
3225
  {
@@ -3175,7 +3228,7 @@ export default class Meeting extends StatelessWebexPlugin {
3175
3228
  },
3176
3229
  EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
3177
3230
  {
3178
- memberId: contentShare.beneficiaryId,
3231
+ memberId: finalBeneficiaryId,
3179
3232
  url: contentShare.url,
3180
3233
  shareInstanceId: this.remoteShareInstanceId,
3181
3234
  annotationInfo: contentShare.annotation,
@@ -3317,27 +3370,31 @@ export default class Meeting extends StatelessWebexPlugin {
3317
3370
  * @memberof Meeting
3318
3371
  */
3319
3372
  private setUpLocusUrlListener() {
3320
- this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
3321
- this.members.locusUrlUpdate(payload);
3322
- this.breakouts.locusUrlUpdate(payload);
3323
- this.simultaneousInterpretation.locusUrlUpdate(payload);
3324
- this.annotation.locusUrlUpdate(payload);
3325
- this.locusUrl = payload;
3326
- this.locusId = this.locusUrl?.split('/').pop();
3327
- this.recordingController.setLocusUrl(this.locusUrl);
3328
- this.controlsOptionsManager.setLocusUrl(this.locusUrl);
3329
- this.webinar.locusUrlUpdate(payload);
3373
+ this.locusInfo.on(
3374
+ EVENTS.LOCUS_INFO_UPDATE_URL,
3375
+ (payload: {url: string; isMainLocus?: boolean}) => {
3376
+ const {url, isMainLocus} = payload;
3377
+ this.members.locusUrlUpdate(url);
3378
+ this.breakouts.locusUrlUpdate(url);
3379
+ this.simultaneousInterpretation.locusUrlUpdate(url);
3380
+ this.annotation.locusUrlUpdate(url);
3381
+ this.locusUrl = url;
3382
+ this.locusId = this.locusUrl?.split('/').pop();
3383
+ this.recordingController.setLocusUrl(this.locusUrl);
3384
+ this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3385
+ this.webinar.locusUrlUpdate(url);
3330
3386
 
3331
- Trigger.trigger(
3332
- this,
3333
- {
3334
- file: 'meeting/index',
3335
- function: 'setUpLocusSelfListener',
3336
- },
3337
- EVENT_TRIGGERS.MEETING_LOCUS_URL_UPDATE,
3338
- {locusUrl: payload}
3339
- );
3340
- });
3387
+ Trigger.trigger(
3388
+ this,
3389
+ {
3390
+ file: 'meeting/index',
3391
+ function: 'setUpLocusSelfListener',
3392
+ },
3393
+ EVENT_TRIGGERS.MEETING_LOCUS_URL_UPDATE,
3394
+ {locusUrl: url}
3395
+ );
3396
+ }
3397
+ );
3341
3398
  }
3342
3399
 
3343
3400
  /**
@@ -4180,6 +4237,7 @@ export default class Meeting extends StatelessWebexPlugin {
4180
4237
  this.userDisplayHints,
4181
4238
  this.selfUserPolicies
4182
4239
  ),
4240
+ showAutoEndMeetingWarning: MeetingUtil.showAutoEndMeetingWarning(this.userDisplayHints),
4183
4241
  canRaiseHand: MeetingUtil.canUserRaiseHand(this.userDisplayHints),
4184
4242
  canLowerAllHands: MeetingUtil.canUserLowerAllHands(this.userDisplayHints),
4185
4243
  canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(this.userDisplayHints),
@@ -4195,8 +4253,13 @@ export default class Meeting extends StatelessWebexPlugin {
4195
4253
  isLocalRecordingStarted: MeetingUtil.isLocalRecordingStarted(this.userDisplayHints),
4196
4254
  isLocalRecordingStopped: MeetingUtil.isLocalRecordingStopped(this.userDisplayHints),
4197
4255
  isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
4256
+ isLocalStreamingStarted: MeetingUtil.isLocalStreamingStarted(this.userDisplayHints),
4257
+ isLocalStreamingStopped: MeetingUtil.isLocalStreamingStopped(this.userDisplayHints),
4198
4258
  isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
4199
4259
  isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
4260
+ isSpokenLanguageAutoDetectionEnabled: MeetingUtil.isSpokenLanguageAutoDetectionEnabled(
4261
+ this.userDisplayHints
4262
+ ),
4200
4263
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
4201
4264
  canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(this.userDisplayHints),
4202
4265
  isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
@@ -6779,6 +6842,10 @@ export default class Meeting extends StatelessWebexPlugin {
6779
6842
  // @ts-ignore
6780
6843
  this.webex.internal.newMetrics.submitClientEvent({
6781
6844
  name: 'client.ice.start',
6845
+ payload: {
6846
+ // @ts-ignore
6847
+ labels: MeetingUtil.getCaEventLabelsForIpVersion(this.webex),
6848
+ },
6782
6849
  options: {
6783
6850
  meetingId: this.id,
6784
6851
  },
@@ -6948,10 +7015,10 @@ export default class Meeting extends StatelessWebexPlugin {
6948
7015
  }
6949
7016
  }
6950
7017
 
6951
- // Count members that are in the meeting.
7018
+ // Count members that are in the meeting or in the lobby.
6952
7019
  const {members} = this.getMembers().membersCollection;
6953
7020
  event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6954
- (member: Member) => member.isInMeeting
7021
+ (member: Member) => member.isInMeeting || member.isInLobby
6955
7022
  ).length;
6956
7023
 
6957
7024
  // @ts-ignore
@@ -7310,10 +7377,12 @@ export default class Meeting extends StatelessWebexPlugin {
7310
7377
  if (this.config.stats.enableStatsAnalyzer) {
7311
7378
  // @ts-ignore - config coming from registerPlugin
7312
7379
  this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
7380
+ this.statsMonitor = new StatsMonitor();
7313
7381
  this.statsAnalyzer = new StatsAnalyzer({
7314
7382
  // @ts-ignore - config coming from registerPlugin
7315
7383
  config: this.config.stats,
7316
7384
  networkQualityMonitor: this.networkQualityMonitor,
7385
+ statsMonitor: this.statsMonitor,
7317
7386
  isMultistream: this.isMultistream,
7318
7387
  });
7319
7388
  this.shareCAEventSentStatus = {
@@ -7327,6 +7396,33 @@ export default class Meeting extends StatelessWebexPlugin {
7327
7396
  NetworkQualityEventNames.NETWORK_QUALITY,
7328
7397
  this.sendNetworkQualityEvent.bind(this)
7329
7398
  );
7399
+
7400
+ this.statsMonitor.on(StatsMonitorEventNames.INBOUND_AUDIO_ISSUE, (data) => {
7401
+ // Before forwarding any inbound audio issues to the app, make sure that we have at least one other
7402
+ // participant in the meeting with unmuted audio.
7403
+ // We don't check this.mediaProperties.mediaDirection here, because that's already handled in statsAnalyzer,
7404
+ // so we won't get this event if we are not setup to receive any audio
7405
+ const atLeastOneUnmutedOtherMember = Object.values(
7406
+ this.members.membersCollection.getAll()
7407
+ ).find((member) => {
7408
+ return !member.isSelf && !member.isPairedWithSelf && !member.isAudioMuted;
7409
+ });
7410
+
7411
+ if (atLeastOneUnmutedOtherMember) {
7412
+ this.mediaProperties.sendMediaIssueMetric(
7413
+ 'inbound_audio',
7414
+ data.issueSubType,
7415
+ this.correlationId
7416
+ );
7417
+
7418
+ Trigger.trigger(
7419
+ this,
7420
+ {file: 'meeting/index', function: 'createStatsAnalyzer'},
7421
+ EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
7422
+ data
7423
+ );
7424
+ }
7425
+ });
7330
7426
  }
7331
7427
  }
7332
7428
 
@@ -7357,7 +7453,7 @@ export default class Meeting extends StatelessWebexPlugin {
7357
7453
  } seconds`
7358
7454
  );
7359
7455
 
7360
- const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7456
+ const error = new SdpResponseTimeoutError();
7361
7457
 
7362
7458
  // @ts-ignore
7363
7459
  this.webex.internal.newMetrics.submitClientEvent({
@@ -7625,6 +7721,10 @@ export default class Meeting extends StatelessWebexPlugin {
7625
7721
  }
7626
7722
 
7627
7723
  this.statsAnalyzer = null;
7724
+ this.networkQualityMonitor?.removeAllListeners();
7725
+ this.networkQualityMonitor = null;
7726
+ this.statsMonitor?.removeAllListeners();
7727
+ this.statsMonitor = null;
7628
7728
 
7629
7729
  // when media fails, we want to upload a webrtc dump to see whats going on
7630
7730
  // this function is async, but returns once the stats have been gathered
@@ -7648,6 +7748,10 @@ export default class Meeting extends StatelessWebexPlugin {
7648
7748
  await this.statsAnalyzer.stopAnalyzer();
7649
7749
  }
7650
7750
  this.statsAnalyzer = null;
7751
+ this.networkQualityMonitor?.removeAllListeners();
7752
+ this.networkQualityMonitor = null;
7753
+ this.statsMonitor?.removeAllListeners();
7754
+ this.statsMonitor = null;
7651
7755
 
7652
7756
  this.isMultistream = false;
7653
7757
 
@@ -7819,6 +7923,9 @@ export default class Meeting extends StatelessWebexPlugin {
7819
7923
 
7820
7924
  this.allowMediaInLobby = options?.allowMediaInLobby;
7821
7925
 
7926
+ // @ts-ignore
7927
+ const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
7928
+
7822
7929
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
7823
7930
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
7824
7931
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
@@ -7917,6 +8024,7 @@ export default class Meeting extends StatelessWebexPlugin {
7917
8024
  locus_id: this.locusUrl.split('/').pop(),
7918
8025
  connectionType,
7919
8026
  ipVersion,
8027
+ ipver,
7920
8028
  selectedCandidatePairChanges,
7921
8029
  numTransports,
7922
8030
  isMultistream: this.isMultistream,
@@ -7985,6 +8093,7 @@ export default class Meeting extends StatelessWebexPlugin {
7985
8093
  ...reachabilityMetrics,
7986
8094
  ...iceCandidateErrors,
7987
8095
  iceCandidatesCount: this.iceCandidatesCount,
8096
+ ipver,
7988
8097
  });
7989
8098
 
7990
8099
  await this.cleanUpOnAddMediaFailure();
@@ -9361,6 +9470,36 @@ export default class Meeting extends StatelessWebexPlugin {
9361
9470
  return Promise.reject(new Error('Error sending reaction, service url not found.'));
9362
9471
  }
9363
9472
 
9473
+ /**
9474
+ * Extend the current meeting duration.
9475
+ *
9476
+ * @param {number} extensionMinutes - how many minutes to extend
9477
+ * @returns {Promise}
9478
+ * @public
9479
+ * @memberof Meeting
9480
+ */
9481
+ public extendMeeting({
9482
+ meetingPolicyUrl,
9483
+ meetingInstanceId,
9484
+ participantId,
9485
+ extensionMinutes = 30,
9486
+ }) {
9487
+ if (!meetingInstanceId || !participantId) {
9488
+ return Promise.reject(new Error('Missing meetingInstanceId or participantId'));
9489
+ }
9490
+
9491
+ if (!meetingPolicyUrl) {
9492
+ return Promise.reject(new Error('Missing meetingPolicyUrl'));
9493
+ }
9494
+
9495
+ return this.meetingRequest.extendMeeting({
9496
+ meetingInstanceId,
9497
+ participantId,
9498
+ extensionMinutes,
9499
+ meetingPolicyUrl,
9500
+ });
9501
+ }
9502
+
9364
9503
  /**
9365
9504
  * Method to enable or disable reactions inside the meeting.
9366
9505
  *
@@ -9890,4 +10029,47 @@ export default class Meeting extends StatelessWebexPlugin {
9890
10029
 
9891
10030
  return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9892
10031
  }
10032
+
10033
+ /**
10034
+ * Notifies the host with the given meeting UUID and display names.
10035
+ *
10036
+ * @param {string} meetingUuid - The UUID of the meeting.
10037
+ * @param {string[]} displayName - An array of display names to notify the host with.
10038
+ * @returns {Promise<any>} The result of the notifyHost request.
10039
+ */
10040
+ notifyHost(meetingUuid: string, displayName: string[]) {
10041
+ return this.meetingRequest.notifyHost(
10042
+ this.meetingInfo.siteFullUrl,
10043
+ this.locusId,
10044
+ meetingUuid,
10045
+ displayName
10046
+ );
10047
+ }
10048
+
10049
+ /**
10050
+ * Call out a SIP participant to a meeting
10051
+ * @param {string} address - The SIP address or phone number
10052
+ * @param {string} displayName - The display name for the participant
10053
+ * @param {string} [correlationId] - Optional correlation ID
10054
+ * @returns {Promise} Promise that resolves when the call-out is initiated
10055
+ */
10056
+ sipCallOut(address: string, displayName: string) {
10057
+ return this.meetingRequest.sipCallOut(
10058
+ this.meetingInfo.meetingId,
10059
+ this.meetingInfo.meetingId,
10060
+ address,
10061
+ displayName
10062
+ );
10063
+ }
10064
+
10065
+ /**
10066
+ * Cancel an ongoing SIP call-out
10067
+ * @param {string} participantId - The participant ID to cancel
10068
+ * @returns {Promise} Promise that resolves when the call-out is cancelled
10069
+ * @public
10070
+ * @memberof Meetings
10071
+ */
10072
+ cancelSipCallOut(participantId: string) {
10073
+ return this.meetingRequest.cancelSipCallOut(participantId);
10074
+ }
9893
10075
  }
@@ -291,18 +291,14 @@ export class MuteState {
291
291
  );
292
292
 
293
293
  return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
294
- .then((locus) => {
294
+ .then((response) => {
295
295
  LoggerProxy.logger.info(
296
296
  `Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
297
297
  );
298
298
 
299
299
  this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;
300
300
 
301
- if (locus) {
302
- meeting.locusInfo.handleLocusDelta(locus, meeting);
303
- }
304
-
305
- return locus;
301
+ return MeetingUtil.updateLocusFromApiResponse(meeting, response);
306
302
  })
307
303
  .catch((remoteUpdateError) => {
308
304
  LoggerProxy.logger.warn(
@@ -886,6 +886,44 @@ export default class MeetingRequest extends StatelessWebexPlugin {
886
886
  });
887
887
  }
888
888
 
889
+ /**
890
+ * Extend the current meeting duration.
891
+ *
892
+ * @param {Object} params - Parameters for extending the meeting.
893
+ * @param {string} params.meetingInstanceId - The unique ID of the meeting instance.
894
+ * @param {string} params.participantId - The ID of the participant requesting the extension.
895
+ * @param {number} params.extensionMinutes - The number of minutes to extend the meeting by.
896
+ * @param {string} params.meetingPolicyUrl - The base URL for meeting policy service (dynamic, from locus links)
897
+ * @returns {Promise<any>} A promise that resolves with the server response.
898
+ */
899
+ extendMeeting({
900
+ meetingInstanceId,
901
+ participantId,
902
+ extensionMinutes,
903
+ meetingPolicyUrl,
904
+ }: {
905
+ meetingInstanceId: string;
906
+ participantId: string;
907
+ extensionMinutes: number;
908
+ meetingPolicyUrl: string;
909
+ }) {
910
+ if (!meetingPolicyUrl) {
911
+ return Promise.reject(new Error('meetingPolicyUrl is required'));
912
+ }
913
+ const uri = `${meetingPolicyUrl}/continueMeeting`;
914
+
915
+ // @ts-ignore
916
+ return this.request({
917
+ method: HTTP_VERBS.POST,
918
+ uri,
919
+ body: {
920
+ meetingInstanceId,
921
+ requestParticipantId: participantId,
922
+ extensionMinutes,
923
+ },
924
+ });
925
+ }
926
+
889
927
  /**
890
928
  * Make a network request to enable or disable reactions.
891
929
  * @param {boolean} options.enable - determines if we need to enable or disable.
@@ -985,4 +1023,107 @@ export default class MeetingRequest extends StatelessWebexPlugin {
985
1023
  body: {videoLayout},
986
1024
  });
987
1025
  }
1026
+
1027
+ /**
1028
+ * Sends a request to notify the host of a meeting.
1029
+ * @param {string} siteFullUrl - The site URL.
1030
+ * @param {string} locusId - The locus ID.
1031
+ * @param {string} meetingUuid - The meeting UUID.
1032
+ * @param {Array<string>} displayName - The display names to notify the host about.
1033
+ * @returns {Promise}
1034
+ */
1035
+ notifyHost(siteFullUrl: string, locusId: string, meetingUuid: string, displayName: string[]) {
1036
+ // @ts-ignore
1037
+ return this.request({
1038
+ method: HTTP_VERBS.POST,
1039
+ uri: `https://${siteFullUrl}/wbxappapi/v1/meetings/${meetingUuid}/notifyhost`,
1040
+ body: {
1041
+ displayName,
1042
+ size: displayName?.length,
1043
+ },
1044
+ headers: {
1045
+ locusId,
1046
+ },
1047
+ });
1048
+ }
1049
+
1050
+ /**
1051
+ * Call out to a SIP participant
1052
+ *
1053
+ * @param {any} meetingId - The meeting ID.
1054
+ * @param {any} meetingNumber - The meeting number.
1055
+ * @param {string} address - The SIP address to call out.
1056
+ * @param {string} displayName - The display name for the participant.
1057
+ * @returns {Promise} The API response
1058
+ */
1059
+ public async sipCallOut(meetingId, meetingNumber, address, displayName) {
1060
+ const body: any = {
1061
+ meetingId,
1062
+ meetingNumber,
1063
+ address,
1064
+ displayName,
1065
+ };
1066
+ try {
1067
+ // @ts-ignore
1068
+ const response = await this.request({
1069
+ method: HTTP_VERBS.POST,
1070
+ service: 'hydra',
1071
+ resource: 'meetingParticipants/callout',
1072
+ body,
1073
+ headers: {
1074
+ Accept: 'application/json',
1075
+ },
1076
+ });
1077
+
1078
+ LoggerProxy.logger.info('Meetings:request#sipCallOut --> SIP call-out successful', response);
1079
+
1080
+ return response.body;
1081
+ } catch (err) {
1082
+ LoggerProxy.logger.error(
1083
+ `Meetings:request#sipCallOut --> Error calling out SIP participant, error ${JSON.stringify(
1084
+ err
1085
+ )}`
1086
+ );
1087
+ throw err;
1088
+ }
1089
+ }
1090
+
1091
+ /**
1092
+ * Cancel an ongoing SIP call-out
1093
+ *
1094
+ * @param {string} participantId - The ID of the participant whose SIP call-out should be cancelled.
1095
+ * @returns {Promise} The API response
1096
+ */
1097
+ public async cancelSipCallOut(participantId) {
1098
+ const body = {
1099
+ participantId,
1100
+ };
1101
+
1102
+ try {
1103
+ // @ts-ignore
1104
+ const response = await this.request({
1105
+ method: HTTP_VERBS.POST,
1106
+ service: 'hydra',
1107
+ resource: 'meetingParticipants/cancelCallout',
1108
+ body,
1109
+ headers: {
1110
+ Accept: 'application/json',
1111
+ },
1112
+ });
1113
+
1114
+ LoggerProxy.logger.info(
1115
+ 'Meetings:request#cancelSipCallOut --> SIP call-out cancelled successfully',
1116
+ response
1117
+ );
1118
+
1119
+ return response.body;
1120
+ } catch (err) {
1121
+ LoggerProxy.logger.error(
1122
+ `Meetings:request#cancelSipCallOut --> Error cancelling SIP participant call-out, error ${JSON.stringify(
1123
+ err
1124
+ )}`
1125
+ );
1126
+ throw err;
1127
+ }
1128
+ }
988
1129
  }