@webex/plugin-meetings 3.11.0-webex-services-ready.1 → 3.12.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 (166) hide show
  1. package/dist/aiEnableRequest/index.js +184 -0
  2. package/dist/aiEnableRequest/index.js.map +1 -0
  3. package/dist/aiEnableRequest/utils.js +36 -0
  4. package/dist/aiEnableRequest/utils.js.map +1 -0
  5. package/dist/annotation/index.js +14 -5
  6. package/dist/annotation/index.js.map +1 -1
  7. package/dist/breakouts/breakout.js +1 -1
  8. package/dist/breakouts/index.js +1 -1
  9. package/dist/config.js +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +28 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +3 -1
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +709 -380
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +10 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +290 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +217 -79
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1071 -862
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +100 -45
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +2 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/reactions/reactions.type.js.map +1 -1
  69. package/dist/reconnection-manager/index.js +0 -1
  70. package/dist/reconnection-manager/index.js.map +1 -1
  71. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  72. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  73. package/dist/types/config.d.ts +3 -0
  74. package/dist/types/constants.d.ts +23 -1
  75. package/dist/types/hashTree/constants.d.ts +1 -0
  76. package/dist/types/hashTree/hashTree.d.ts +7 -0
  77. package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
  78. package/dist/types/hashTree/types.d.ts +3 -0
  79. package/dist/types/hashTree/utils.d.ts +6 -0
  80. package/dist/types/index.d.ts +1 -0
  81. package/dist/types/interceptors/constant.d.ts +5 -0
  82. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  83. package/dist/types/interceptors/index.d.ts +2 -1
  84. package/dist/types/interceptors/utils.d.ts +1 -0
  85. package/dist/types/locus-info/index.d.ts +21 -2
  86. package/dist/types/locus-info/types.d.ts +1 -0
  87. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  88. package/dist/types/media/properties.d.ts +2 -1
  89. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  90. package/dist/types/meeting/index.d.ts +38 -6
  91. package/dist/types/meeting/request.d.ts +16 -1
  92. package/dist/types/meeting/request.type.d.ts +5 -0
  93. package/dist/types/meeting/util.d.ts +31 -0
  94. package/dist/types/meetings/index.d.ts +4 -2
  95. package/dist/types/member/index.d.ts +1 -0
  96. package/dist/types/member/util.d.ts +5 -0
  97. package/dist/types/metrics/constants.d.ts +1 -0
  98. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  99. package/dist/types/reactions/reactions.type.d.ts +1 -0
  100. package/dist/types/webinar/utils.d.ts +6 -0
  101. package/dist/webinar/index.js +260 -90
  102. package/dist/webinar/index.js.map +1 -1
  103. package/dist/webinar/utils.js +25 -0
  104. package/dist/webinar/utils.js.map +1 -0
  105. package/package.json +24 -23
  106. package/src/aiEnableRequest/README.md +84 -0
  107. package/src/aiEnableRequest/index.ts +170 -0
  108. package/src/aiEnableRequest/utils.ts +25 -0
  109. package/src/annotation/index.ts +27 -7
  110. package/src/config.ts +3 -0
  111. package/src/constants.ts +29 -1
  112. package/src/hashTree/constants.ts +1 -0
  113. package/src/hashTree/hashTree.ts +17 -0
  114. package/src/hashTree/hashTreeParser.ts +627 -249
  115. package/src/hashTree/types.ts +4 -0
  116. package/src/hashTree/utils.ts +9 -0
  117. package/src/index.ts +8 -1
  118. package/src/interceptors/constant.ts +6 -0
  119. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  120. package/src/interceptors/index.ts +2 -1
  121. package/src/interceptors/utils.ts +16 -0
  122. package/src/interpretation/index.ts +2 -2
  123. package/src/locus-info/controlsUtils.ts +11 -0
  124. package/src/locus-info/index.ts +231 -61
  125. package/src/locus-info/selfUtils.ts +1 -0
  126. package/src/locus-info/types.ts +1 -0
  127. package/src/media/MediaConnectionAwaiter.ts +41 -1
  128. package/src/media/properties.ts +3 -1
  129. package/src/meeting/in-meeting-actions.ts +12 -0
  130. package/src/meeting/index.ts +188 -43
  131. package/src/meeting/request.ts +42 -0
  132. package/src/meeting/request.type.ts +6 -0
  133. package/src/meeting/util.ts +160 -2
  134. package/src/meetings/index.ts +135 -41
  135. package/src/member/index.ts +10 -0
  136. package/src/member/util.ts +12 -0
  137. package/src/metrics/constants.ts +1 -0
  138. package/src/multistream/mediaRequestManager.ts +4 -54
  139. package/src/multistream/remoteMediaManager.ts +13 -0
  140. package/src/reactions/reactions.type.ts +1 -0
  141. package/src/reconnection-manager/index.ts +0 -1
  142. package/src/webinar/index.ts +162 -5
  143. package/src/webinar/utils.ts +16 -0
  144. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  145. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  146. package/test/unit/spec/annotation/index.ts +69 -7
  147. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  148. package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
  149. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  150. package/test/unit/spec/interceptors/utils.ts +75 -0
  151. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  152. package/test/unit/spec/locus-info/index.js +383 -46
  153. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  154. package/test/unit/spec/media/properties.ts +12 -3
  155. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  156. package/test/unit/spec/meeting/index.js +662 -85
  157. package/test/unit/spec/meeting/request.js +70 -0
  158. package/test/unit/spec/meeting/utils.js +438 -26
  159. package/test/unit/spec/meetings/index.js +652 -31
  160. package/test/unit/spec/member/index.js +28 -4
  161. package/test/unit/spec/member/util.js +65 -27
  162. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  163. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  164. package/test/unit/spec/reconnection-manager/index.js +4 -8
  165. package/test/unit/spec/webinar/index.ts +348 -36
  166. package/test/unit/spec/webinar/utils.ts +39 -0
@@ -32,6 +32,7 @@ interface IInMeetingActions {
32
32
  canLowerAllHands?: boolean;
33
33
  canLowerSomeoneElsesHand?: boolean;
34
34
  bothLeaveAndEndMeetingAvailable?: boolean;
35
+ requireHostEndMeetingBeforeLeave?: boolean;
35
36
  canEnableClosedCaption?: boolean;
36
37
  canStartTranscribing?: boolean;
37
38
  canStopTranscribing?: boolean;
@@ -117,6 +118,8 @@ interface IInMeetingActions {
117
118
  canMoveToLobby?: boolean;
118
119
  canEnablePollingQA?: boolean;
119
120
  canDisablePollingQA?: boolean;
121
+ canAttendeeRequestAiAssistantEnabled?: boolean;
122
+ isAttendeeRequestAiAssistantDeclinedAll?: boolean;
120
123
  }
121
124
 
122
125
  /**
@@ -169,6 +172,8 @@ export default class InMeetingActions implements IInMeetingActions {
169
172
 
170
173
  bothLeaveAndEndMeetingAvailable = null;
171
174
 
175
+ requireHostEndMeetingBeforeLeave = null;
176
+
172
177
  canEnableClosedCaption = null;
173
178
 
174
179
  canStartTranscribing = null;
@@ -337,6 +342,10 @@ export default class InMeetingActions implements IInMeetingActions {
337
342
 
338
343
  canDisablePollingQA = null;
339
344
 
345
+ canAttendeeRequestAiAssistantEnabled = null;
346
+
347
+ isAttendeeRequestAiAssistantDeclinedAll = null;
348
+
340
349
  /**
341
350
  * Returns all meeting action options
342
351
  * @returns {Object}
@@ -364,6 +373,7 @@ export default class InMeetingActions implements IInMeetingActions {
364
373
  canLowerAllHands: this.canLowerAllHands,
365
374
  canLowerSomeoneElsesHand: this.canLowerSomeoneElsesHand,
366
375
  bothLeaveAndEndMeetingAvailable: this.bothLeaveAndEndMeetingAvailable,
376
+ requireHostEndMeetingBeforeLeave: this.requireHostEndMeetingBeforeLeave,
367
377
  canEnableClosedCaption: this.canEnableClosedCaption,
368
378
  canStartTranscribing: this.canStartTranscribing,
369
379
  canStopTranscribing: this.canStopTranscribing,
@@ -448,6 +458,8 @@ export default class InMeetingActions implements IInMeetingActions {
448
458
  canMoveToLobby: this.canMoveToLobby,
449
459
  canEnablePollingQA: this.canEnablePollingQA,
450
460
  canDisablePollingQA: this.canDisablePollingQA,
461
+ canAttendeeRequestAiAssistantEnabled: this.canAttendeeRequestAiAssistantEnabled,
462
+ isAttendeeRequestAiAssistantDeclinedAll: this.isAttendeeRequestAiAssistantDeclinedAll,
451
463
  });
452
464
 
453
465
  /**
@@ -13,7 +13,7 @@ import {
13
13
  CALL_DIAGNOSTIC_CONFIG,
14
14
  RtcMetrics,
15
15
  } from '@webex/internal-plugin-metrics';
16
- import {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
16
+ import type {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
17
17
 
18
18
  import {
19
19
  ConnectionState,
@@ -33,6 +33,8 @@ import {
33
33
  InboundAudioIssueSubTypes,
34
34
  } from '@webex/internal-media-core';
35
35
 
36
+ import {DataChannelTokenType} from '@webex/internal-plugin-llm';
37
+
36
38
  import {
37
39
  LocalStream,
38
40
  LocalCameraStream,
@@ -179,8 +181,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
179
181
  import {ReachabilityMetrics} from '../reachability/reachability.types';
180
182
  import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
181
183
  import {Invitee} from './type';
182
- import {DataSet} from '../hashTree/hashTreeParser';
184
+ import {DataSet, Metadata} from '../hashTree/hashTreeParser';
183
185
  import {LocusDTO} from '../locus-info/types';
186
+ import AIEnableRequest from '../aiEnableRequest';
184
187
 
185
188
  // default callback so we don't call an undefined function, but in practice it should never be used
186
189
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -250,6 +253,7 @@ export type AddMediaOptions = {
250
253
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
251
254
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
252
255
  allowMediaInLobby?: boolean; // allows adding media when in the lobby
256
+ allowPublishMediaInLobby?: boolean; // allows publishing media when in the lobby, if not specified, default value false is used
253
257
  additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
254
258
  };
255
259
 
@@ -575,6 +579,7 @@ export default class Meeting extends StatelessWebexPlugin {
575
579
  breakouts: any;
576
580
  simultaneousInterpretation: any;
577
581
  annotation: any;
582
+ aiEnableRequest: any;
578
583
  webinar: any;
579
584
  conversationUrl: string;
580
585
  callStateForMetrics: CallStateForMetrics;
@@ -623,6 +628,13 @@ export default class Meeting extends StatelessWebexPlugin {
623
628
  keepAliveTimerId: NodeJS.Timeout;
624
629
  lastVideoLayoutInfo: any;
625
630
  locusInfo: any;
631
+ // this group of properties is populated via updateMeetingObject() that's registered as a callback with LocusInfo
632
+ isUserUnadmitted?: boolean;
633
+ joinedWith?: any;
634
+ selfId?: string;
635
+ roles: any[];
636
+ // ... there is more ... see SelfUtils.parse()
637
+ // end of the group
626
638
  locusMediaRequest?: LocusMediaRequest;
627
639
  mediaProperties: MediaProperties;
628
640
  mediaRequestManagers: {
@@ -657,7 +669,6 @@ export default class Meeting extends StatelessWebexPlugin {
657
669
  endCallInitJoinReq: any;
658
670
  endJoinReqResp: any;
659
671
  endLocalSDPGenRemoteSDPRecvDelay: any;
660
- joinedWith: any;
661
672
  locusId: any;
662
673
  startCallInitJoinReq: any;
663
674
  startJoinReqResp: any;
@@ -672,12 +683,11 @@ export default class Meeting extends StatelessWebexPlugin {
672
683
  permissionTokenReceivedLocalTime: number;
673
684
  resourceId: any;
674
685
  resourceUrl: string;
675
- selfId: string;
676
686
  state: any;
677
687
  localAudioStreamMuteStateHandler: () => void;
678
688
  localVideoStreamMuteStateHandler: () => void;
679
689
  localOutputTrackChangeHandler: () => void;
680
- roles: any[];
690
+ localConstraintsChangeHandler: () => void;
681
691
  environment: string;
682
692
  namespace = MEETINGS;
683
693
  allowMediaInLobby: boolean;
@@ -894,6 +904,10 @@ export default class Meeting extends StatelessWebexPlugin {
894
904
  */
895
905
  // @ts-ignore
896
906
  this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
907
+
908
+ // @ts-ignore
909
+ this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
910
+
897
911
  /**
898
912
  * @instance
899
913
  * @type {Annotation}
@@ -1543,6 +1557,12 @@ export default class Meeting extends StatelessWebexPlugin {
1543
1557
  }
1544
1558
  };
1545
1559
 
1560
+ this.localConstraintsChangeHandler = () => {
1561
+ if (!this.isMultistream) {
1562
+ this.mediaProperties.webrtcMediaConnection?.updatePreferredBitrateKbps();
1563
+ }
1564
+ };
1565
+
1546
1566
  /**
1547
1567
  * Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
1548
1568
  * @instance
@@ -2952,6 +2972,18 @@ export default class Meeting extends StatelessWebexPlugin {
2952
2972
  );
2953
2973
  });
2954
2974
 
2975
+ this.locusInfo.on(
2976
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
2977
+ ({aiSummaryNotification}) => {
2978
+ Trigger.trigger(
2979
+ this,
2980
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2981
+ EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
2982
+ {aiSummaryNotification}
2983
+ );
2984
+ }
2985
+ );
2986
+
2955
2987
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2956
2988
  Trigger.trigger(
2957
2989
  this,
@@ -3403,6 +3435,8 @@ export default class Meeting extends StatelessWebexPlugin {
3403
3435
  this.recordingController.setLocusUrl(this.locusUrl);
3404
3436
  this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3405
3437
  this.webinar.locusUrlUpdate(url);
3438
+ // @ts-ignore
3439
+ this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
3406
3440
 
3407
3441
  Trigger.trigger(
3408
3442
  this,
@@ -3433,6 +3467,7 @@ export default class Meeting extends StatelessWebexPlugin {
3433
3467
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
3434
3468
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
3435
3469
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
3470
+ this.aiEnableRequest.approvalUrlUpdate(payload?.services?.approval?.url);
3436
3471
  });
3437
3472
  }
3438
3473
 
@@ -3530,6 +3565,9 @@ export default class Meeting extends StatelessWebexPlugin {
3530
3565
  // @ts-ignore - config coming from registerPlugin
3531
3566
  if (datachannelUrl && this.config.enableAutomaticLLM) {
3532
3567
  this.updateLLMConnection();
3568
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
3569
+ this.webinar.updatePSDataChannel();
3570
+ }
3533
3571
  }
3534
3572
  }
3535
3573
 
@@ -3762,6 +3800,10 @@ export default class Meeting extends StatelessWebexPlugin {
3762
3800
  );
3763
3801
  });
3764
3802
 
3803
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ID_CHANGED, (payload) => {
3804
+ this.aiEnableRequest.selfParticipantIdUpdate(payload.selfId);
3805
+ });
3806
+
3765
3807
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3766
3808
  const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3767
3809
  Trigger.trigger(
@@ -4264,6 +4306,9 @@ export default class Meeting extends StatelessWebexPlugin {
4264
4306
  bothLeaveAndEndMeetingAvailable: MeetingUtil.bothLeaveAndEndMeetingAvailable(
4265
4307
  this.userDisplayHints
4266
4308
  ),
4309
+ requireHostEndMeetingBeforeLeave: MeetingUtil.requireHostEndMeetingBeforeLeave(
4310
+ this.userDisplayHints
4311
+ ),
4267
4312
  canEnableClosedCaption: MeetingUtil.canEnableClosedCaption(this.userDisplayHints),
4268
4313
  canStartTranscribing: MeetingUtil.canStartTranscribing(this.userDisplayHints),
4269
4314
  canStopTranscribing: MeetingUtil.canStopTranscribing(this.userDisplayHints),
@@ -4522,6 +4567,12 @@ export default class Meeting extends StatelessWebexPlugin {
4522
4567
  requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
4523
4568
  displayHints: this.userDisplayHints,
4524
4569
  }),
4570
+ canAttendeeRequestAiAssistantEnabled: MeetingUtil.canAttendeeRequestAiAssistantEnabled(
4571
+ this.userDisplayHints,
4572
+ this.roles
4573
+ ),
4574
+ isAttendeeRequestAiAssistantDeclinedAll:
4575
+ MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
4525
4576
  }) || changed;
4526
4577
  }
4527
4578
  if (changed) {
@@ -4593,7 +4644,8 @@ export default class Meeting extends StatelessWebexPlugin {
4593
4644
  mediaId: string;
4594
4645
  host: object;
4595
4646
  selfId: string;
4596
- dataSets: DataSet[];
4647
+ dataSets: DataSet[]; // only sent by Locus when hash trees are used
4648
+ metadata: Metadata; // only sent by Locus when hash trees are used
4597
4649
  }) {
4598
4650
  const mtgLocus: any = data.locus;
4599
4651
 
@@ -4609,6 +4661,7 @@ export default class Meeting extends StatelessWebexPlugin {
4609
4661
  trigger: 'join-response',
4610
4662
  locus: mtgLocus,
4611
4663
  dataSets: data.dataSets,
4664
+ metadata: data.metadata,
4612
4665
  });
4613
4666
  }
4614
4667
 
@@ -4818,6 +4871,7 @@ export default class Meeting extends StatelessWebexPlugin {
4818
4871
  this.localVideoStreamMuteStateHandler
4819
4872
  );
4820
4873
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4874
+ oldStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4821
4875
 
4822
4876
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
4823
4877
  this.mediaProperties.setLocalVideoStream(localStream);
@@ -4833,6 +4887,7 @@ export default class Meeting extends StatelessWebexPlugin {
4833
4887
  this.localVideoStreamMuteStateHandler
4834
4888
  );
4835
4889
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4890
+ localStream?.on(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4836
4891
 
4837
4892
  if (!this.isMultistream || !localStream) {
4838
4893
  // for multistream WCME automatically un-publishes the old stream when we publish a new one
@@ -4967,6 +5022,7 @@ export default class Meeting extends StatelessWebexPlugin {
4967
5022
  this.localVideoStreamMuteStateHandler
4968
5023
  );
4969
5024
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
5025
+ videoStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4970
5026
 
4971
5027
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
4972
5028
  shareAudioStream?.off(
@@ -5781,7 +5837,7 @@ export default class Meeting extends StatelessWebexPlugin {
5781
5837
  this.isReactionsSupported()
5782
5838
  ) {
5783
5839
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5784
- if (!member) {
5840
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5785
5841
  // @ts-ignore -- fix type
5786
5842
  LoggerProxy.logger.warn(
5787
5843
  `Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
@@ -5789,7 +5845,7 @@ export default class Meeting extends StatelessWebexPlugin {
5789
5845
  break;
5790
5846
  }
5791
5847
 
5792
- const {name} = member;
5848
+ const name = (member && member.name) || e.data.sender.displayName;
5793
5849
  const processedReaction: ProcessedReaction = {
5794
5850
  reaction: e.data.reaction,
5795
5851
  sender: {
@@ -6177,6 +6233,49 @@ export default class Meeting extends StatelessWebexPlugin {
6177
6233
  }
6178
6234
  }
6179
6235
 
6236
+ /**
6237
+ * Disconnects and cleans up the default LLM session listeners/timers.
6238
+ * @param {Object} options
6239
+ * @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
6240
+ * @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
6241
+ * @returns {Promise<void>}
6242
+ */
6243
+ private cleanupLLMConneciton = async ({
6244
+ removeOnlineListener = true,
6245
+ throwOnError = true,
6246
+ }: {
6247
+ removeOnlineListener?: boolean;
6248
+ throwOnError?: boolean;
6249
+ } = {}): Promise<void> => {
6250
+ try {
6251
+ // @ts-ignore - Fix type
6252
+ await this.webex.internal.llm.disconnectLLM({
6253
+ code: 3050,
6254
+ reason: 'done (permanent)',
6255
+ });
6256
+ } catch (error) {
6257
+ LoggerProxy.logger.error(
6258
+ 'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
6259
+ error
6260
+ );
6261
+
6262
+ if (throwOnError) {
6263
+ throw error;
6264
+ }
6265
+ } finally {
6266
+ if (removeOnlineListener) {
6267
+ // @ts-ignore - Fix type
6268
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6269
+ }
6270
+ // @ts-ignore - fix types
6271
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6272
+ // @ts-ignore - Fix type
6273
+ this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6274
+
6275
+ this.clearLLMHealthCheckTimer();
6276
+ }
6277
+ };
6278
+
6180
6279
  /**
6181
6280
  * Connects to low latency mercury and reconnects if the address has changed
6182
6281
  * It will also disconnect if called when the meeting has ended
@@ -6185,15 +6284,26 @@ export default class Meeting extends StatelessWebexPlugin {
6185
6284
  */
6186
6285
  async updateLLMConnection() {
6187
6286
  // @ts-ignore - Fix type
6188
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6287
+ const {
6288
+ url = undefined,
6289
+ info: {datachannelUrl = undefined} = {},
6290
+ self: {datachannelToken = undefined} = {},
6291
+ } = this.locusInfo || {};
6189
6292
 
6190
6293
  const isJoined = this.isJoined();
6191
6294
 
6295
+ // @ts-ignore
6296
+ const currentToken = this.webex.internal.llm.getDatachannelToken(DataChannelTokenType.Default);
6297
+
6298
+ const finalToken = currentToken ?? datachannelToken;
6299
+
6300
+ if (!currentToken && datachannelToken) {
6301
+ // @ts-ignore
6302
+ this.webex.internal.llm.setDatachannelToken(datachannelToken, DataChannelTokenType.Default);
6303
+ }
6304
+
6192
6305
  // webinar panelist should use new data channel in practice session
6193
- const dataChannelUrl =
6194
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6195
- ? practiceSessionDatachannelUrl
6196
- : datachannelUrl;
6306
+ const dataChannelUrl = datachannelUrl;
6197
6307
 
6198
6308
  // @ts-ignore - Fix type
6199
6309
  if (this.webex.internal.llm.isConnected()) {
@@ -6206,21 +6316,7 @@ export default class Meeting extends StatelessWebexPlugin {
6206
6316
  ) {
6207
6317
  return undefined;
6208
6318
  }
6209
- // @ts-ignore - Fix type
6210
- await this.webex.internal.llm.disconnectLLM(
6211
- isJoined
6212
- ? {
6213
- code: 3050,
6214
- reason: 'done (permanent)',
6215
- }
6216
- : undefined
6217
- );
6218
- // @ts-ignore - Fix type
6219
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6220
- // @ts-ignore - Fix type
6221
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6222
-
6223
- this.clearLLMHealthCheckTimer();
6319
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6224
6320
  }
6225
6321
 
6226
6322
  if (!isJoined) {
@@ -6229,7 +6325,7 @@ export default class Meeting extends StatelessWebexPlugin {
6229
6325
 
6230
6326
  // @ts-ignore - Fix type
6231
6327
  return this.webex.internal.llm
6232
- .registerAndConnect(url, dataChannelUrl)
6328
+ .registerAndConnect(url, dataChannelUrl, finalToken)
6233
6329
  .then((registerAndConnectResult) => {
6234
6330
  // @ts-ignore - Fix type
6235
6331
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7433,7 +7529,7 @@ export default class Meeting extends StatelessWebexPlugin {
7433
7529
  */
7434
7530
  private async waitForMediaConnectionConnected(): Promise<void> {
7435
7531
  try {
7436
- await this.mediaProperties.waitForMediaConnectionConnected();
7532
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7437
7533
  } catch (error) {
7438
7534
  const {iceConnected} = error;
7439
7535
 
@@ -8026,6 +8122,7 @@ export default class Meeting extends StatelessWebexPlugin {
8026
8122
  remoteMediaManagerConfig,
8027
8123
  bundlePolicy = 'max-bundle',
8028
8124
  additionalMediaOptions = {},
8125
+ allowPublishMediaInLobby = false,
8029
8126
  } = options;
8030
8127
 
8031
8128
  const {
@@ -8046,7 +8143,6 @@ export default class Meeting extends StatelessWebexPlugin {
8046
8143
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8047
8144
 
8048
8145
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8049
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8050
8146
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8051
8147
  throw new UserInLobbyError();
8052
8148
  }
@@ -8091,7 +8187,13 @@ export default class Meeting extends StatelessWebexPlugin {
8091
8187
  this.brbState = createBrbState(this, false);
8092
8188
 
8093
8189
  try {
8094
- await this.setUpLocalStreamReferences(localStreams);
8190
+ // if we're in a lobby and allowPublishMediaInLobby==false, we don't want to
8191
+ // setup local streams for publishing, because if we ever end up admitted to the meeting
8192
+ // but Locus event about it for us is delayed or missed, others could see/hear our user's video/audio
8193
+ // while the user would still think they're in the lobby
8194
+ if (allowPublishMediaInLobby || !this.isUserUnadmitted) {
8195
+ await this.setUpLocalStreamReferences(localStreams);
8196
+ }
8095
8197
 
8096
8198
  this.setMercuryListener();
8097
8199
 
@@ -8559,12 +8661,12 @@ export default class Meeting extends StatelessWebexPlugin {
8559
8661
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8560
8662
 
8561
8663
  return MeetingUtil.leaveMeeting(this, options)
8562
- .then((leave) => {
8664
+ .then(async (leave) => {
8563
8665
  // CA team recommends submitting this *after* locus /leave
8564
8666
  submitLeaveMetric();
8565
8667
 
8566
8668
  this.meetingFiniteStateMachine.leave();
8567
- this.clearMeetingData();
8669
+ await this.clearMeetingData();
8568
8670
 
8569
8671
  // upload logs on leave irrespective of meeting delete
8570
8672
  Trigger.trigger(
@@ -9423,10 +9525,10 @@ export default class Meeting extends StatelessWebexPlugin {
9423
9525
  });
9424
9526
 
9425
9527
  return MeetingUtil.endMeetingForAll(this)
9426
- .then((end) => {
9528
+ .then(async (end) => {
9427
9529
  this.meetingFiniteStateMachine.end();
9428
9530
 
9429
- this.clearMeetingData();
9531
+ await this.clearMeetingData();
9430
9532
  // upload logs on leave irrespective of meeting delete
9431
9533
  Trigger.trigger(
9432
9534
  this,
@@ -9474,7 +9576,7 @@ export default class Meeting extends StatelessWebexPlugin {
9474
9576
  * @public
9475
9577
  * @memberof Meeting
9476
9578
  */
9477
- clearMeetingData = () => {
9579
+ clearMeetingData = async () => {
9478
9580
  this.audio = null;
9479
9581
  this.video = null;
9480
9582
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9490,12 +9592,7 @@ export default class Meeting extends StatelessWebexPlugin {
9490
9592
 
9491
9593
  this.annotation.deregisterEvents();
9492
9594
 
9493
- // @ts-ignore - fix types
9494
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
9495
- // @ts-ignore - Fix type
9496
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
9497
-
9498
- this.clearLLMHealthCheckTimer();
9595
+ await this.cleanupLLMConneciton({throwOnError: false});
9499
9596
  };
9500
9597
 
9501
9598
  /**
@@ -10195,4 +10292,52 @@ export default class Meeting extends StatelessWebexPlugin {
10195
10292
  cancelSipCallOut(participantId: string) {
10196
10293
  return this.meetingRequest.cancelSipCallOut(participantId);
10197
10294
  }
10295
+
10296
+ /**
10297
+ * Method to get new data
10298
+ * @returns {Promise}
10299
+ */
10300
+ public async refreshDataChannelToken() {
10301
+ const isPracticeSession = this.webinar.isJoinPracticeSessionDataChannel();
10302
+ const dataChannelTokenType = this.getDataChannelTokenType();
10303
+
10304
+ try {
10305
+ const res = await this.meetingRequest.fetchDatachannelToken({
10306
+ locusUrl: this.locusUrl,
10307
+ requestingParticipantId: this.members.selfId,
10308
+ isPracticeSession,
10309
+ });
10310
+
10311
+ return {
10312
+ body: {
10313
+ datachannelToken: res.body.datachannelToken,
10314
+ dataChannelTokenType,
10315
+ },
10316
+ };
10317
+ } catch (e) {
10318
+ const msg = e?.message || String(e);
10319
+
10320
+ LoggerProxy.logger.warn(
10321
+ `Meeting:index#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${msg}`,
10322
+ {statusCode: e?.statusCode}
10323
+ );
10324
+
10325
+ return null;
10326
+ }
10327
+ }
10328
+
10329
+ /**
10330
+ * Determines the current data channel token type based on the meeting state.
10331
+ *
10332
+ * variant should be used when connecting to the LLM data channel.
10333
+ *
10334
+ * @returns {DataChannelTokenType} The token type representing the current session mode.
10335
+ */
10336
+ public getDataChannelTokenType(): DataChannelTokenType {
10337
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
10338
+ return DataChannelTokenType.PracticeSession;
10339
+ }
10340
+
10341
+ return DataChannelTokenType.Default;
10342
+ }
10198
10343
  }
@@ -33,6 +33,7 @@ import {
33
33
  ToggleReactionsOptions,
34
34
  PostMeetingDataConsentOptions,
35
35
  SynchronizeVideoLayout,
36
+ fetchDataChannelTokenOptions,
36
37
  } from './request.type';
37
38
  import MeetingUtil from './util';
38
39
  import {AnnotationInfo} from '../annotation/annotation.types';
@@ -1126,4 +1127,45 @@ export default class MeetingRequest extends StatelessWebexPlugin {
1126
1127
  throw err;
1127
1128
  }
1128
1129
  }
1130
+
1131
+ /**
1132
+ * Sends a request to retrieve the datachannel authorization token for a participant.
1133
+ *
1134
+ * For regular meeting data channel:
1135
+ * GET /locus/api/v1/loci/{uuid:lid}/participant/{uuid:pid}/datachannel/token
1136
+ *
1137
+ * For practice session data channel:
1138
+ * GET /locus/api/v1/loci/{uuid:lid}/participant/{uuid:pid}/practiceSession/datachannel/token
1139
+ *
1140
+ * @param {string} locusUrl - The locus url.
1141
+ * @param {string} requestingParticipantId - The participant UUID.
1142
+ * @param {boolean} [isPracticeSession=false] - Whether to get the practice session token.
1143
+ * @returns {Promise<{datachannelToken: string}>}
1144
+ */
1145
+ public async fetchDatachannelToken({
1146
+ locusUrl,
1147
+ requestingParticipantId,
1148
+ isPracticeSession = false,
1149
+ }: fetchDataChannelTokenOptions) {
1150
+ if (!locusUrl || !requestingParticipantId) {
1151
+ return Promise.reject(new Error('locusUrl and participantId are required'));
1152
+ }
1153
+ const practicePrefix = isPracticeSession ? '/practiceSession' : '';
1154
+
1155
+ const uri = `${locusUrl}/${PARTICIPANT}/${requestingParticipantId}${practicePrefix}/datachannel/token`;
1156
+
1157
+ // @ts-ignore
1158
+ return this.locusDeltaRequest({
1159
+ method: HTTP_VERBS.GET,
1160
+ uri,
1161
+ }).catch((err) => {
1162
+ LoggerProxy.logger.warn(
1163
+ `Meeting:request#fetchDatachannelToken --> Failed to retrieve ${
1164
+ isPracticeSession ? 'practice session ' : ''
1165
+ }datachannel token: ${err?.message || err}`
1166
+ );
1167
+
1168
+ return null;
1169
+ });
1170
+ }
1129
1171
  }
@@ -88,4 +88,10 @@ export type UnsetStageVideoLayout = {
88
88
  overrideDefault: false;
89
89
  };
90
90
 
91
+ export type fetchDataChannelTokenOptions = {
92
+ locusUrl: string;
93
+ requestingParticipantId: string;
94
+ isPracticeSession: boolean;
95
+ };
96
+
91
97
  export type SynchronizeVideoLayout = SetStageVideoLayout | UnsetStageVideoLayout;