@webex/plugin-meetings 3.11.0-webex-services-ready.1 → 3.12.0-next.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 (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 +7 -2
  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 +850 -410
  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 +522 -131
  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 +1128 -868
  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 +117 -48
  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 +4 -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 +122 -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 +60 -8
  86. package/dist/types/locus-info/types.d.ts +7 -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 +48 -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 +4 -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 +745 -252
  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 +579 -113
  125. package/src/locus-info/selfUtils.ts +1 -0
  126. package/src/locus-info/types.ts +8 -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 +221 -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 +157 -44
  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 +2225 -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 +1134 -55
  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 +653 -32
  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
@@ -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, HashTreeMessage, 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
 
@@ -564,6 +568,34 @@ type MediaReachabilityMetrics = ReachabilityMetrics & {
564
568
  * @memberof Meeting
565
569
  */
566
570
 
571
+ /**
572
+ * Stores an event so all events can be later retrieved via a console command for debugging.
573
+ * @param {string} type
574
+ * @param {Object} data
575
+ * @returns {void}
576
+ */
577
+ export function storeEventForDebugging(
578
+ type: string,
579
+ data: {
580
+ eventType: any;
581
+ stateElementsMessage?: HashTreeMessage;
582
+ }
583
+ ) {
584
+ if ((window as any)?.locusEvents) {
585
+ // only store non-heartbeat hash tree messages
586
+ if (
587
+ data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED &&
588
+ data.stateElementsMessage?.locusStateElements
589
+ ) {
590
+ (window as any).locusEvents.push({
591
+ ...data,
592
+ timestamp: new Date().toLocaleString(),
593
+ type,
594
+ });
595
+ }
596
+ }
597
+ }
598
+
567
599
  /**
568
600
  * @description Meeting is the crux of the plugin
569
601
  * @export
@@ -575,6 +607,7 @@ export default class Meeting extends StatelessWebexPlugin {
575
607
  breakouts: any;
576
608
  simultaneousInterpretation: any;
577
609
  annotation: any;
610
+ aiEnableRequest: any;
578
611
  webinar: any;
579
612
  conversationUrl: string;
580
613
  callStateForMetrics: CallStateForMetrics;
@@ -623,6 +656,13 @@ export default class Meeting extends StatelessWebexPlugin {
623
656
  keepAliveTimerId: NodeJS.Timeout;
624
657
  lastVideoLayoutInfo: any;
625
658
  locusInfo: any;
659
+ // this group of properties is populated via updateMeetingObject() that's registered as a callback with LocusInfo
660
+ isUserUnadmitted?: boolean;
661
+ joinedWith?: any;
662
+ selfId?: string;
663
+ roles: any[];
664
+ // ... there is more ... see SelfUtils.parse()
665
+ // end of the group
626
666
  locusMediaRequest?: LocusMediaRequest;
627
667
  mediaProperties: MediaProperties;
628
668
  mediaRequestManagers: {
@@ -657,7 +697,6 @@ export default class Meeting extends StatelessWebexPlugin {
657
697
  endCallInitJoinReq: any;
658
698
  endJoinReqResp: any;
659
699
  endLocalSDPGenRemoteSDPRecvDelay: any;
660
- joinedWith: any;
661
700
  locusId: any;
662
701
  startCallInitJoinReq: any;
663
702
  startJoinReqResp: any;
@@ -672,12 +711,11 @@ export default class Meeting extends StatelessWebexPlugin {
672
711
  permissionTokenReceivedLocalTime: number;
673
712
  resourceId: any;
674
713
  resourceUrl: string;
675
- selfId: string;
676
714
  state: any;
677
715
  localAudioStreamMuteStateHandler: () => void;
678
716
  localVideoStreamMuteStateHandler: () => void;
679
717
  localOutputTrackChangeHandler: () => void;
680
- roles: any[];
718
+ localConstraintsChangeHandler: () => void;
681
719
  environment: string;
682
720
  namespace = MEETINGS;
683
721
  allowMediaInLobby: boolean;
@@ -894,6 +932,10 @@ export default class Meeting extends StatelessWebexPlugin {
894
932
  */
895
933
  // @ts-ignore
896
934
  this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
935
+
936
+ // @ts-ignore
937
+ this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
938
+
897
939
  /**
898
940
  * @instance
899
941
  * @type {Annotation}
@@ -1543,6 +1585,12 @@ export default class Meeting extends StatelessWebexPlugin {
1543
1585
  }
1544
1586
  };
1545
1587
 
1588
+ this.localConstraintsChangeHandler = () => {
1589
+ if (!this.isMultistream) {
1590
+ this.mediaProperties.webrtcMediaConnection?.updatePreferredBitrateKbps();
1591
+ }
1592
+ };
1593
+
1546
1594
  /**
1547
1595
  * Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
1548
1596
  * @instance
@@ -2952,6 +3000,18 @@ export default class Meeting extends StatelessWebexPlugin {
2952
3000
  );
2953
3001
  });
2954
3002
 
3003
+ this.locusInfo.on(
3004
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
3005
+ ({aiSummaryNotification}) => {
3006
+ Trigger.trigger(
3007
+ this,
3008
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
3009
+ EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
3010
+ {aiSummaryNotification}
3011
+ );
3012
+ }
3013
+ );
3014
+
2955
3015
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2956
3016
  Trigger.trigger(
2957
3017
  this,
@@ -3403,6 +3463,8 @@ export default class Meeting extends StatelessWebexPlugin {
3403
3463
  this.recordingController.setLocusUrl(this.locusUrl);
3404
3464
  this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3405
3465
  this.webinar.locusUrlUpdate(url);
3466
+ // @ts-ignore
3467
+ this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
3406
3468
 
3407
3469
  Trigger.trigger(
3408
3470
  this,
@@ -3433,6 +3495,7 @@ export default class Meeting extends StatelessWebexPlugin {
3433
3495
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
3434
3496
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
3435
3497
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
3498
+ this.aiEnableRequest.approvalUrlUpdate(payload?.services?.approval?.url);
3436
3499
  });
3437
3500
  }
3438
3501
 
@@ -3530,6 +3593,9 @@ export default class Meeting extends StatelessWebexPlugin {
3530
3593
  // @ts-ignore - config coming from registerPlugin
3531
3594
  if (datachannelUrl && this.config.enableAutomaticLLM) {
3532
3595
  this.updateLLMConnection();
3596
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
3597
+ this.webinar.updatePSDataChannel();
3598
+ }
3533
3599
  }
3534
3600
  }
3535
3601
 
@@ -3762,6 +3828,10 @@ export default class Meeting extends StatelessWebexPlugin {
3762
3828
  );
3763
3829
  });
3764
3830
 
3831
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ID_CHANGED, (payload) => {
3832
+ this.aiEnableRequest.selfParticipantIdUpdate(payload.selfId);
3833
+ });
3834
+
3765
3835
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3766
3836
  const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3767
3837
  Trigger.trigger(
@@ -4264,6 +4334,9 @@ export default class Meeting extends StatelessWebexPlugin {
4264
4334
  bothLeaveAndEndMeetingAvailable: MeetingUtil.bothLeaveAndEndMeetingAvailable(
4265
4335
  this.userDisplayHints
4266
4336
  ),
4337
+ requireHostEndMeetingBeforeLeave: MeetingUtil.requireHostEndMeetingBeforeLeave(
4338
+ this.userDisplayHints
4339
+ ),
4267
4340
  canEnableClosedCaption: MeetingUtil.canEnableClosedCaption(this.userDisplayHints),
4268
4341
  canStartTranscribing: MeetingUtil.canStartTranscribing(this.userDisplayHints),
4269
4342
  canStopTranscribing: MeetingUtil.canStopTranscribing(this.userDisplayHints),
@@ -4522,6 +4595,12 @@ export default class Meeting extends StatelessWebexPlugin {
4522
4595
  requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
4523
4596
  displayHints: this.userDisplayHints,
4524
4597
  }),
4598
+ canAttendeeRequestAiAssistantEnabled: MeetingUtil.canAttendeeRequestAiAssistantEnabled(
4599
+ this.userDisplayHints,
4600
+ this.roles
4601
+ ),
4602
+ isAttendeeRequestAiAssistantDeclinedAll:
4603
+ MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
4525
4604
  }) || changed;
4526
4605
  }
4527
4606
  if (changed) {
@@ -4593,7 +4672,8 @@ export default class Meeting extends StatelessWebexPlugin {
4593
4672
  mediaId: string;
4594
4673
  host: object;
4595
4674
  selfId: string;
4596
- dataSets: DataSet[];
4675
+ dataSets: DataSet[]; // only sent by Locus when hash trees are used
4676
+ metadata: Metadata; // only sent by Locus when hash trees are used
4597
4677
  }) {
4598
4678
  const mtgLocus: any = data.locus;
4599
4679
 
@@ -4609,6 +4689,7 @@ export default class Meeting extends StatelessWebexPlugin {
4609
4689
  trigger: 'join-response',
4610
4690
  locus: mtgLocus,
4611
4691
  dataSets: data.dataSets,
4692
+ metadata: data.metadata,
4612
4693
  });
4613
4694
  }
4614
4695
 
@@ -4818,6 +4899,7 @@ export default class Meeting extends StatelessWebexPlugin {
4818
4899
  this.localVideoStreamMuteStateHandler
4819
4900
  );
4820
4901
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4902
+ oldStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4821
4903
 
4822
4904
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
4823
4905
  this.mediaProperties.setLocalVideoStream(localStream);
@@ -4833,6 +4915,7 @@ export default class Meeting extends StatelessWebexPlugin {
4833
4915
  this.localVideoStreamMuteStateHandler
4834
4916
  );
4835
4917
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4918
+ localStream?.on(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4836
4919
 
4837
4920
  if (!this.isMultistream || !localStream) {
4838
4921
  // for multistream WCME automatically un-publishes the old stream when we publish a new one
@@ -4967,6 +5050,7 @@ export default class Meeting extends StatelessWebexPlugin {
4967
5050
  this.localVideoStreamMuteStateHandler
4968
5051
  );
4969
5052
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
5053
+ videoStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4970
5054
 
4971
5055
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
4972
5056
  shareAudioStream?.off(
@@ -5758,6 +5842,11 @@ export default class Meeting extends StatelessWebexPlugin {
5758
5842
  */
5759
5843
  private processLocusLLMEvent = (event: LocusLLMEvent): void => {
5760
5844
  if (event.data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
5845
+ // @ts-ignore
5846
+ if (this.config.experimental.storeLocusHashTreeEventsForDebugging) {
5847
+ storeEventForDebugging('llm', event.data);
5848
+ }
5849
+
5761
5850
  this.locusInfo.parse(this, event.data);
5762
5851
  } else {
5763
5852
  LoggerProxy.logger.warn(
@@ -5781,7 +5870,7 @@ export default class Meeting extends StatelessWebexPlugin {
5781
5870
  this.isReactionsSupported()
5782
5871
  ) {
5783
5872
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5784
- if (!member) {
5873
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5785
5874
  // @ts-ignore -- fix type
5786
5875
  LoggerProxy.logger.warn(
5787
5876
  `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 +5878,7 @@ export default class Meeting extends StatelessWebexPlugin {
5789
5878
  break;
5790
5879
  }
5791
5880
 
5792
- const {name} = member;
5881
+ const name = (member && member.name) || e.data.sender.displayName;
5793
5882
  const processedReaction: ProcessedReaction = {
5794
5883
  reaction: e.data.reaction,
5795
5884
  sender: {
@@ -6177,6 +6266,49 @@ export default class Meeting extends StatelessWebexPlugin {
6177
6266
  }
6178
6267
  }
6179
6268
 
6269
+ /**
6270
+ * Disconnects and cleans up the default LLM session listeners/timers.
6271
+ * @param {Object} options
6272
+ * @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
6273
+ * @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
6274
+ * @returns {Promise<void>}
6275
+ */
6276
+ private cleanupLLMConneciton = async ({
6277
+ removeOnlineListener = true,
6278
+ throwOnError = true,
6279
+ }: {
6280
+ removeOnlineListener?: boolean;
6281
+ throwOnError?: boolean;
6282
+ } = {}): Promise<void> => {
6283
+ try {
6284
+ // @ts-ignore - Fix type
6285
+ await this.webex.internal.llm.disconnectLLM({
6286
+ code: 3050,
6287
+ reason: 'done (permanent)',
6288
+ });
6289
+ } catch (error) {
6290
+ LoggerProxy.logger.error(
6291
+ 'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
6292
+ error
6293
+ );
6294
+
6295
+ if (throwOnError) {
6296
+ throw error;
6297
+ }
6298
+ } finally {
6299
+ if (removeOnlineListener) {
6300
+ // @ts-ignore - Fix type
6301
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6302
+ }
6303
+ // @ts-ignore - fix types
6304
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6305
+ // @ts-ignore - Fix type
6306
+ this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6307
+
6308
+ this.clearLLMHealthCheckTimer();
6309
+ }
6310
+ };
6311
+
6180
6312
  /**
6181
6313
  * Connects to low latency mercury and reconnects if the address has changed
6182
6314
  * It will also disconnect if called when the meeting has ended
@@ -6185,15 +6317,26 @@ export default class Meeting extends StatelessWebexPlugin {
6185
6317
  */
6186
6318
  async updateLLMConnection() {
6187
6319
  // @ts-ignore - Fix type
6188
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6320
+ const {
6321
+ url = undefined,
6322
+ info: {datachannelUrl = undefined} = {},
6323
+ self: {datachannelToken = undefined} = {},
6324
+ } = this.locusInfo || {};
6189
6325
 
6190
6326
  const isJoined = this.isJoined();
6191
6327
 
6328
+ // @ts-ignore
6329
+ const currentToken = this.webex.internal.llm.getDatachannelToken(DataChannelTokenType.Default);
6330
+
6331
+ const finalToken = currentToken ?? datachannelToken;
6332
+
6333
+ if (!currentToken && datachannelToken) {
6334
+ // @ts-ignore
6335
+ this.webex.internal.llm.setDatachannelToken(datachannelToken, DataChannelTokenType.Default);
6336
+ }
6337
+
6192
6338
  // webinar panelist should use new data channel in practice session
6193
- const dataChannelUrl =
6194
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6195
- ? practiceSessionDatachannelUrl
6196
- : datachannelUrl;
6339
+ const dataChannelUrl = datachannelUrl;
6197
6340
 
6198
6341
  // @ts-ignore - Fix type
6199
6342
  if (this.webex.internal.llm.isConnected()) {
@@ -6206,21 +6349,7 @@ export default class Meeting extends StatelessWebexPlugin {
6206
6349
  ) {
6207
6350
  return undefined;
6208
6351
  }
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();
6352
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6224
6353
  }
6225
6354
 
6226
6355
  if (!isJoined) {
@@ -6229,7 +6358,7 @@ export default class Meeting extends StatelessWebexPlugin {
6229
6358
 
6230
6359
  // @ts-ignore - Fix type
6231
6360
  return this.webex.internal.llm
6232
- .registerAndConnect(url, dataChannelUrl)
6361
+ .registerAndConnect(url, dataChannelUrl, finalToken)
6233
6362
  .then((registerAndConnectResult) => {
6234
6363
  // @ts-ignore - Fix type
6235
6364
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7433,7 +7562,7 @@ export default class Meeting extends StatelessWebexPlugin {
7433
7562
  */
7434
7563
  private async waitForMediaConnectionConnected(): Promise<void> {
7435
7564
  try {
7436
- await this.mediaProperties.waitForMediaConnectionConnected();
7565
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7437
7566
  } catch (error) {
7438
7567
  const {iceConnected} = error;
7439
7568
 
@@ -8026,6 +8155,7 @@ export default class Meeting extends StatelessWebexPlugin {
8026
8155
  remoteMediaManagerConfig,
8027
8156
  bundlePolicy = 'max-bundle',
8028
8157
  additionalMediaOptions = {},
8158
+ allowPublishMediaInLobby = false,
8029
8159
  } = options;
8030
8160
 
8031
8161
  const {
@@ -8046,7 +8176,6 @@ export default class Meeting extends StatelessWebexPlugin {
8046
8176
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8047
8177
 
8048
8178
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8049
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8050
8179
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8051
8180
  throw new UserInLobbyError();
8052
8181
  }
@@ -8091,7 +8220,13 @@ export default class Meeting extends StatelessWebexPlugin {
8091
8220
  this.brbState = createBrbState(this, false);
8092
8221
 
8093
8222
  try {
8094
- await this.setUpLocalStreamReferences(localStreams);
8223
+ // if we're in a lobby and allowPublishMediaInLobby==false, we don't want to
8224
+ // setup local streams for publishing, because if we ever end up admitted to the meeting
8225
+ // but Locus event about it for us is delayed or missed, others could see/hear our user's video/audio
8226
+ // while the user would still think they're in the lobby
8227
+ if (allowPublishMediaInLobby || !this.isUserUnadmitted) {
8228
+ await this.setUpLocalStreamReferences(localStreams);
8229
+ }
8095
8230
 
8096
8231
  this.setMercuryListener();
8097
8232
 
@@ -8559,12 +8694,12 @@ export default class Meeting extends StatelessWebexPlugin {
8559
8694
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8560
8695
 
8561
8696
  return MeetingUtil.leaveMeeting(this, options)
8562
- .then((leave) => {
8697
+ .then(async (leave) => {
8563
8698
  // CA team recommends submitting this *after* locus /leave
8564
8699
  submitLeaveMetric();
8565
8700
 
8566
8701
  this.meetingFiniteStateMachine.leave();
8567
- this.clearMeetingData();
8702
+ await this.clearMeetingData();
8568
8703
 
8569
8704
  // upload logs on leave irrespective of meeting delete
8570
8705
  Trigger.trigger(
@@ -9423,10 +9558,10 @@ export default class Meeting extends StatelessWebexPlugin {
9423
9558
  });
9424
9559
 
9425
9560
  return MeetingUtil.endMeetingForAll(this)
9426
- .then((end) => {
9561
+ .then(async (end) => {
9427
9562
  this.meetingFiniteStateMachine.end();
9428
9563
 
9429
- this.clearMeetingData();
9564
+ await this.clearMeetingData();
9430
9565
  // upload logs on leave irrespective of meeting delete
9431
9566
  Trigger.trigger(
9432
9567
  this,
@@ -9474,7 +9609,7 @@ export default class Meeting extends StatelessWebexPlugin {
9474
9609
  * @public
9475
9610
  * @memberof Meeting
9476
9611
  */
9477
- clearMeetingData = () => {
9612
+ clearMeetingData = async () => {
9478
9613
  this.audio = null;
9479
9614
  this.video = null;
9480
9615
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9490,12 +9625,7 @@ export default class Meeting extends StatelessWebexPlugin {
9490
9625
 
9491
9626
  this.annotation.deregisterEvents();
9492
9627
 
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();
9628
+ await this.cleanupLLMConneciton({throwOnError: false});
9499
9629
  };
9500
9630
 
9501
9631
  /**
@@ -10195,4 +10325,52 @@ export default class Meeting extends StatelessWebexPlugin {
10195
10325
  cancelSipCallOut(participantId: string) {
10196
10326
  return this.meetingRequest.cancelSipCallOut(participantId);
10197
10327
  }
10328
+
10329
+ /**
10330
+ * Method to get new data
10331
+ * @returns {Promise}
10332
+ */
10333
+ public async refreshDataChannelToken() {
10334
+ const isPracticeSession = this.webinar.isJoinPracticeSessionDataChannel();
10335
+ const dataChannelTokenType = this.getDataChannelTokenType();
10336
+
10337
+ try {
10338
+ const res = await this.meetingRequest.fetchDatachannelToken({
10339
+ locusUrl: this.locusUrl,
10340
+ requestingParticipantId: this.members.selfId,
10341
+ isPracticeSession,
10342
+ });
10343
+
10344
+ return {
10345
+ body: {
10346
+ datachannelToken: res.body.datachannelToken,
10347
+ dataChannelTokenType,
10348
+ },
10349
+ };
10350
+ } catch (e) {
10351
+ const msg = e?.message || String(e);
10352
+
10353
+ LoggerProxy.logger.warn(
10354
+ `Meeting:index#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${msg}`,
10355
+ {statusCode: e?.statusCode}
10356
+ );
10357
+
10358
+ return null;
10359
+ }
10360
+ }
10361
+
10362
+ /**
10363
+ * Determines the current data channel token type based on the meeting state.
10364
+ *
10365
+ * variant should be used when connecting to the LLM data channel.
10366
+ *
10367
+ * @returns {DataChannelTokenType} The token type representing the current session mode.
10368
+ */
10369
+ public getDataChannelTokenType(): DataChannelTokenType {
10370
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
10371
+ return DataChannelTokenType.PracticeSession;
10372
+ }
10373
+
10374
+ return DataChannelTokenType.Default;
10375
+ }
10198
10376
  }
@@ -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;