@webex/plugin-meetings 3.11.0 → 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 (170) 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 +1138 -866
  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/reachability/index.js +18 -10
  69. package/dist/reachability/index.js.map +1 -1
  70. package/dist/reactions/reactions.type.js.map +1 -1
  71. package/dist/reconnection-manager/index.js +0 -1
  72. package/dist/reconnection-manager/index.js.map +1 -1
  73. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  74. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  75. package/dist/types/config.d.ts +4 -0
  76. package/dist/types/constants.d.ts +23 -1
  77. package/dist/types/hashTree/constants.d.ts +1 -0
  78. package/dist/types/hashTree/hashTree.d.ts +7 -0
  79. package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
  80. package/dist/types/hashTree/types.d.ts +3 -0
  81. package/dist/types/hashTree/utils.d.ts +6 -0
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interceptors/constant.d.ts +5 -0
  84. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  85. package/dist/types/interceptors/index.d.ts +2 -1
  86. package/dist/types/interceptors/utils.d.ts +1 -0
  87. package/dist/types/locus-info/index.d.ts +60 -8
  88. package/dist/types/locus-info/types.d.ts +7 -0
  89. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  90. package/dist/types/media/properties.d.ts +2 -1
  91. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  92. package/dist/types/meeting/index.d.ts +48 -6
  93. package/dist/types/meeting/request.d.ts +16 -1
  94. package/dist/types/meeting/request.type.d.ts +5 -0
  95. package/dist/types/meeting/util.d.ts +31 -0
  96. package/dist/types/meetings/index.d.ts +4 -2
  97. package/dist/types/member/index.d.ts +1 -0
  98. package/dist/types/member/util.d.ts +5 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  101. package/dist/types/reactions/reactions.type.d.ts +1 -0
  102. package/dist/types/webinar/utils.d.ts +6 -0
  103. package/dist/webinar/index.js +260 -90
  104. package/dist/webinar/index.js.map +1 -1
  105. package/dist/webinar/utils.js +25 -0
  106. package/dist/webinar/utils.js.map +1 -0
  107. package/package.json +24 -23
  108. package/src/aiEnableRequest/README.md +84 -0
  109. package/src/aiEnableRequest/index.ts +170 -0
  110. package/src/aiEnableRequest/utils.ts +25 -0
  111. package/src/annotation/index.ts +27 -7
  112. package/src/config.ts +4 -0
  113. package/src/constants.ts +29 -1
  114. package/src/hashTree/constants.ts +1 -0
  115. package/src/hashTree/hashTree.ts +17 -0
  116. package/src/hashTree/hashTreeParser.ts +745 -252
  117. package/src/hashTree/types.ts +4 -0
  118. package/src/hashTree/utils.ts +9 -0
  119. package/src/index.ts +8 -1
  120. package/src/interceptors/constant.ts +6 -0
  121. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  122. package/src/interceptors/index.ts +2 -1
  123. package/src/interceptors/utils.ts +16 -0
  124. package/src/interpretation/index.ts +2 -2
  125. package/src/locus-info/controlsUtils.ts +11 -0
  126. package/src/locus-info/index.ts +579 -113
  127. package/src/locus-info/selfUtils.ts +1 -0
  128. package/src/locus-info/types.ts +8 -0
  129. package/src/media/MediaConnectionAwaiter.ts +41 -1
  130. package/src/media/properties.ts +3 -1
  131. package/src/meeting/in-meeting-actions.ts +12 -0
  132. package/src/meeting/index.ts +238 -44
  133. package/src/meeting/request.ts +42 -0
  134. package/src/meeting/request.type.ts +6 -0
  135. package/src/meeting/util.ts +160 -2
  136. package/src/meetings/index.ts +157 -44
  137. package/src/member/index.ts +10 -0
  138. package/src/member/util.ts +12 -0
  139. package/src/metrics/constants.ts +1 -0
  140. package/src/multistream/mediaRequestManager.ts +4 -54
  141. package/src/multistream/remoteMediaManager.ts +13 -0
  142. package/src/reachability/index.ts +9 -0
  143. package/src/reactions/reactions.type.ts +1 -0
  144. package/src/reconnection-manager/index.ts +0 -1
  145. package/src/webinar/index.ts +162 -5
  146. package/src/webinar/utils.ts +16 -0
  147. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  148. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  149. package/test/unit/spec/annotation/index.ts +69 -7
  150. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  151. package/test/unit/spec/hashTree/hashTreeParser.ts +2225 -189
  152. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  153. package/test/unit/spec/interceptors/utils.ts +75 -0
  154. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  155. package/test/unit/spec/locus-info/index.js +1134 -55
  156. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  157. package/test/unit/spec/media/properties.ts +12 -3
  158. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  159. package/test/unit/spec/meeting/index.js +716 -115
  160. package/test/unit/spec/meeting/request.js +70 -0
  161. package/test/unit/spec/meeting/utils.js +438 -26
  162. package/test/unit/spec/meetings/index.js +653 -32
  163. package/test/unit/spec/member/index.js +28 -4
  164. package/test/unit/spec/member/util.js +65 -27
  165. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  166. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  167. package/test/unit/spec/reachability/index.ts +23 -0
  168. package/test/unit/spec/reconnection-manager/index.js +4 -8
  169. package/test/unit/spec/webinar/index.ts +348 -36
  170. 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,
@@ -55,6 +57,7 @@ import {
55
57
  isBrowserMediaError,
56
58
  isBrowserMediaErrorName,
57
59
  } from '@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util';
60
+ import {CapabilityState, WebCapabilities} from '@webex/web-capabilities';
58
61
  import {processNewCaptions} from './voicea-meeting';
59
62
 
60
63
  import {
@@ -178,8 +181,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
178
181
  import {ReachabilityMetrics} from '../reachability/reachability.types';
179
182
  import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
180
183
  import {Invitee} from './type';
181
- import {DataSet} from '../hashTree/hashTreeParser';
184
+ import {DataSet, HashTreeMessage, Metadata} from '../hashTree/hashTreeParser';
182
185
  import {LocusDTO} from '../locus-info/types';
186
+ import AIEnableRequest from '../aiEnableRequest';
183
187
 
184
188
  // default callback so we don't call an undefined function, but in practice it should never be used
185
189
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -249,6 +253,7 @@ export type AddMediaOptions = {
249
253
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
250
254
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
251
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
252
257
  additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
253
258
  };
254
259
 
@@ -563,6 +568,34 @@ type MediaReachabilityMetrics = ReachabilityMetrics & {
563
568
  * @memberof Meeting
564
569
  */
565
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
+
566
599
  /**
567
600
  * @description Meeting is the crux of the plugin
568
601
  * @export
@@ -574,6 +607,7 @@ export default class Meeting extends StatelessWebexPlugin {
574
607
  breakouts: any;
575
608
  simultaneousInterpretation: any;
576
609
  annotation: any;
610
+ aiEnableRequest: any;
577
611
  webinar: any;
578
612
  conversationUrl: string;
579
613
  callStateForMetrics: CallStateForMetrics;
@@ -622,6 +656,13 @@ export default class Meeting extends StatelessWebexPlugin {
622
656
  keepAliveTimerId: NodeJS.Timeout;
623
657
  lastVideoLayoutInfo: any;
624
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
625
666
  locusMediaRequest?: LocusMediaRequest;
626
667
  mediaProperties: MediaProperties;
627
668
  mediaRequestManagers: {
@@ -656,7 +697,6 @@ export default class Meeting extends StatelessWebexPlugin {
656
697
  endCallInitJoinReq: any;
657
698
  endJoinReqResp: any;
658
699
  endLocalSDPGenRemoteSDPRecvDelay: any;
659
- joinedWith: any;
660
700
  locusId: any;
661
701
  startCallInitJoinReq: any;
662
702
  startJoinReqResp: any;
@@ -671,12 +711,11 @@ export default class Meeting extends StatelessWebexPlugin {
671
711
  permissionTokenReceivedLocalTime: number;
672
712
  resourceId: any;
673
713
  resourceUrl: string;
674
- selfId: string;
675
714
  state: any;
676
715
  localAudioStreamMuteStateHandler: () => void;
677
716
  localVideoStreamMuteStateHandler: () => void;
678
717
  localOutputTrackChangeHandler: () => void;
679
- roles: any[];
718
+ localConstraintsChangeHandler: () => void;
680
719
  environment: string;
681
720
  namespace = MEETINGS;
682
721
  allowMediaInLobby: boolean;
@@ -893,6 +932,10 @@ export default class Meeting extends StatelessWebexPlugin {
893
932
  */
894
933
  // @ts-ignore
895
934
  this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
935
+
936
+ // @ts-ignore
937
+ this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
938
+
896
939
  /**
897
940
  * @instance
898
941
  * @type {Annotation}
@@ -1542,6 +1585,12 @@ export default class Meeting extends StatelessWebexPlugin {
1542
1585
  }
1543
1586
  };
1544
1587
 
1588
+ this.localConstraintsChangeHandler = () => {
1589
+ if (!this.isMultistream) {
1590
+ this.mediaProperties.webrtcMediaConnection?.updatePreferredBitrateKbps();
1591
+ }
1592
+ };
1593
+
1545
1594
  /**
1546
1595
  * Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
1547
1596
  * @instance
@@ -2951,6 +3000,18 @@ export default class Meeting extends StatelessWebexPlugin {
2951
3000
  );
2952
3001
  });
2953
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
+
2954
3015
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2955
3016
  Trigger.trigger(
2956
3017
  this,
@@ -3402,6 +3463,8 @@ export default class Meeting extends StatelessWebexPlugin {
3402
3463
  this.recordingController.setLocusUrl(this.locusUrl);
3403
3464
  this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3404
3465
  this.webinar.locusUrlUpdate(url);
3466
+ // @ts-ignore
3467
+ this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
3405
3468
 
3406
3469
  Trigger.trigger(
3407
3470
  this,
@@ -3432,6 +3495,7 @@ export default class Meeting extends StatelessWebexPlugin {
3432
3495
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
3433
3496
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
3434
3497
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
3498
+ this.aiEnableRequest.approvalUrlUpdate(payload?.services?.approval?.url);
3435
3499
  });
3436
3500
  }
3437
3501
 
@@ -3529,6 +3593,9 @@ export default class Meeting extends StatelessWebexPlugin {
3529
3593
  // @ts-ignore - config coming from registerPlugin
3530
3594
  if (datachannelUrl && this.config.enableAutomaticLLM) {
3531
3595
  this.updateLLMConnection();
3596
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
3597
+ this.webinar.updatePSDataChannel();
3598
+ }
3532
3599
  }
3533
3600
  }
3534
3601
 
@@ -3761,6 +3828,10 @@ export default class Meeting extends StatelessWebexPlugin {
3761
3828
  );
3762
3829
  });
3763
3830
 
3831
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ID_CHANGED, (payload) => {
3832
+ this.aiEnableRequest.selfParticipantIdUpdate(payload.selfId);
3833
+ });
3834
+
3764
3835
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3765
3836
  const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3766
3837
  Trigger.trigger(
@@ -4263,6 +4334,9 @@ export default class Meeting extends StatelessWebexPlugin {
4263
4334
  bothLeaveAndEndMeetingAvailable: MeetingUtil.bothLeaveAndEndMeetingAvailable(
4264
4335
  this.userDisplayHints
4265
4336
  ),
4337
+ requireHostEndMeetingBeforeLeave: MeetingUtil.requireHostEndMeetingBeforeLeave(
4338
+ this.userDisplayHints
4339
+ ),
4266
4340
  canEnableClosedCaption: MeetingUtil.canEnableClosedCaption(this.userDisplayHints),
4267
4341
  canStartTranscribing: MeetingUtil.canStartTranscribing(this.userDisplayHints),
4268
4342
  canStopTranscribing: MeetingUtil.canStopTranscribing(this.userDisplayHints),
@@ -4521,6 +4595,12 @@ export default class Meeting extends StatelessWebexPlugin {
4521
4595
  requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
4522
4596
  displayHints: this.userDisplayHints,
4523
4597
  }),
4598
+ canAttendeeRequestAiAssistantEnabled: MeetingUtil.canAttendeeRequestAiAssistantEnabled(
4599
+ this.userDisplayHints,
4600
+ this.roles
4601
+ ),
4602
+ isAttendeeRequestAiAssistantDeclinedAll:
4603
+ MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
4524
4604
  }) || changed;
4525
4605
  }
4526
4606
  if (changed) {
@@ -4592,7 +4672,8 @@ export default class Meeting extends StatelessWebexPlugin {
4592
4672
  mediaId: string;
4593
4673
  host: object;
4594
4674
  selfId: string;
4595
- 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
4596
4677
  }) {
4597
4678
  const mtgLocus: any = data.locus;
4598
4679
 
@@ -4608,6 +4689,7 @@ export default class Meeting extends StatelessWebexPlugin {
4608
4689
  trigger: 'join-response',
4609
4690
  locus: mtgLocus,
4610
4691
  dataSets: data.dataSets,
4692
+ metadata: data.metadata,
4611
4693
  });
4612
4694
  }
4613
4695
 
@@ -4817,6 +4899,7 @@ export default class Meeting extends StatelessWebexPlugin {
4817
4899
  this.localVideoStreamMuteStateHandler
4818
4900
  );
4819
4901
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4902
+ oldStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4820
4903
 
4821
4904
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
4822
4905
  this.mediaProperties.setLocalVideoStream(localStream);
@@ -4832,6 +4915,7 @@ export default class Meeting extends StatelessWebexPlugin {
4832
4915
  this.localVideoStreamMuteStateHandler
4833
4916
  );
4834
4917
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4918
+ localStream?.on(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4835
4919
 
4836
4920
  if (!this.isMultistream || !localStream) {
4837
4921
  // for multistream WCME automatically un-publishes the old stream when we publish a new one
@@ -4966,6 +5050,7 @@ export default class Meeting extends StatelessWebexPlugin {
4966
5050
  this.localVideoStreamMuteStateHandler
4967
5051
  );
4968
5052
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
5053
+ videoStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4969
5054
 
4970
5055
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
4971
5056
  shareAudioStream?.off(
@@ -5379,6 +5464,18 @@ export default class Meeting extends StatelessWebexPlugin {
5379
5464
  let joined = false;
5380
5465
  let joinResponse = prevJoinResponse;
5381
5466
 
5467
+ /* Before we do anything, check if RTCPeerConnection is available. Normally this is checked
5468
+ by addMediaInternal() itself when creating the media connection, but since joinWithMedia()
5469
+ is a convenience method that does both join() and addMedia(), we want to fail fast here
5470
+ in case WebRTC is not available at all.
5471
+ */
5472
+ if (WebCapabilities.supportsRTCPeerConnection() === CapabilityState.NOT_CAPABLE) {
5473
+ // throw the same error that would be thrown by addMediaInternal()
5474
+ throw new Errors.WebrtcApiNotAvailableError(
5475
+ 'RTCPeerConnection API is not available in this environment'
5476
+ );
5477
+ }
5478
+
5382
5479
  try {
5383
5480
  let turnServerInfo;
5384
5481
  let turnDiscoverySkippedReason;
@@ -5449,7 +5546,10 @@ export default class Meeting extends StatelessWebexPlugin {
5449
5546
  // if this was the first attempt, let's do a retry
5450
5547
  let shouldRetry = !isRetry;
5451
5548
 
5452
- if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
5549
+ if (
5550
+ CallDiagnosticUtils.isSdpOfferCreationError(error) ||
5551
+ CallDiagnosticUtils.isWebrtcApiNotAvailableError(error)
5552
+ ) {
5453
5553
  // errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
5454
5554
  // so there is no point doing a retry
5455
5555
  shouldRetry = false;
@@ -5742,6 +5842,11 @@ export default class Meeting extends StatelessWebexPlugin {
5742
5842
  */
5743
5843
  private processLocusLLMEvent = (event: LocusLLMEvent): void => {
5744
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
+
5745
5850
  this.locusInfo.parse(this, event.data);
5746
5851
  } else {
5747
5852
  LoggerProxy.logger.warn(
@@ -5765,7 +5870,7 @@ export default class Meeting extends StatelessWebexPlugin {
5765
5870
  this.isReactionsSupported()
5766
5871
  ) {
5767
5872
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5768
- if (!member) {
5873
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5769
5874
  // @ts-ignore -- fix type
5770
5875
  LoggerProxy.logger.warn(
5771
5876
  `Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
@@ -5773,7 +5878,7 @@ export default class Meeting extends StatelessWebexPlugin {
5773
5878
  break;
5774
5879
  }
5775
5880
 
5776
- const {name} = member;
5881
+ const name = (member && member.name) || e.data.sender.displayName;
5777
5882
  const processedReaction: ProcessedReaction = {
5778
5883
  reaction: e.data.reaction,
5779
5884
  sender: {
@@ -6161,6 +6266,49 @@ export default class Meeting extends StatelessWebexPlugin {
6161
6266
  }
6162
6267
  }
6163
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
+
6164
6312
  /**
6165
6313
  * Connects to low latency mercury and reconnects if the address has changed
6166
6314
  * It will also disconnect if called when the meeting has ended
@@ -6169,15 +6317,26 @@ export default class Meeting extends StatelessWebexPlugin {
6169
6317
  */
6170
6318
  async updateLLMConnection() {
6171
6319
  // @ts-ignore - Fix type
6172
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6320
+ const {
6321
+ url = undefined,
6322
+ info: {datachannelUrl = undefined} = {},
6323
+ self: {datachannelToken = undefined} = {},
6324
+ } = this.locusInfo || {};
6173
6325
 
6174
6326
  const isJoined = this.isJoined();
6175
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
+
6176
6338
  // webinar panelist should use new data channel in practice session
6177
- const dataChannelUrl =
6178
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6179
- ? practiceSessionDatachannelUrl
6180
- : datachannelUrl;
6339
+ const dataChannelUrl = datachannelUrl;
6181
6340
 
6182
6341
  // @ts-ignore - Fix type
6183
6342
  if (this.webex.internal.llm.isConnected()) {
@@ -6190,21 +6349,7 @@ export default class Meeting extends StatelessWebexPlugin {
6190
6349
  ) {
6191
6350
  return undefined;
6192
6351
  }
6193
- // @ts-ignore - Fix type
6194
- await this.webex.internal.llm.disconnectLLM(
6195
- isJoined
6196
- ? {
6197
- code: 3050,
6198
- reason: 'done (permanent)',
6199
- }
6200
- : undefined
6201
- );
6202
- // @ts-ignore - Fix type
6203
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6204
- // @ts-ignore - Fix type
6205
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6206
-
6207
- this.clearLLMHealthCheckTimer();
6352
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6208
6353
  }
6209
6354
 
6210
6355
  if (!isJoined) {
@@ -6213,7 +6358,7 @@ export default class Meeting extends StatelessWebexPlugin {
6213
6358
 
6214
6359
  // @ts-ignore - Fix type
6215
6360
  return this.webex.internal.llm
6216
- .registerAndConnect(url, dataChannelUrl)
6361
+ .registerAndConnect(url, dataChannelUrl, finalToken)
6217
6362
  .then((registerAndConnectResult) => {
6218
6363
  // @ts-ignore - Fix type
6219
6364
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7417,7 +7562,7 @@ export default class Meeting extends StatelessWebexPlugin {
7417
7562
  */
7418
7563
  private async waitForMediaConnectionConnected(): Promise<void> {
7419
7564
  try {
7420
- await this.mediaProperties.waitForMediaConnectionConnected();
7565
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7421
7566
  } catch (error) {
7422
7567
  const {iceConnected} = error;
7423
7568
 
@@ -8010,6 +8155,7 @@ export default class Meeting extends StatelessWebexPlugin {
8010
8155
  remoteMediaManagerConfig,
8011
8156
  bundlePolicy = 'max-bundle',
8012
8157
  additionalMediaOptions = {},
8158
+ allowPublishMediaInLobby = false,
8013
8159
  } = options;
8014
8160
 
8015
8161
  const {
@@ -8030,7 +8176,6 @@ export default class Meeting extends StatelessWebexPlugin {
8030
8176
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8031
8177
 
8032
8178
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8033
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8034
8179
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8035
8180
  throw new UserInLobbyError();
8036
8181
  }
@@ -8075,7 +8220,13 @@ export default class Meeting extends StatelessWebexPlugin {
8075
8220
  this.brbState = createBrbState(this, false);
8076
8221
 
8077
8222
  try {
8078
- 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
+ }
8079
8230
 
8080
8231
  this.setMercuryListener();
8081
8232
 
@@ -8543,12 +8694,12 @@ export default class Meeting extends StatelessWebexPlugin {
8543
8694
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8544
8695
 
8545
8696
  return MeetingUtil.leaveMeeting(this, options)
8546
- .then((leave) => {
8697
+ .then(async (leave) => {
8547
8698
  // CA team recommends submitting this *after* locus /leave
8548
8699
  submitLeaveMetric();
8549
8700
 
8550
8701
  this.meetingFiniteStateMachine.leave();
8551
- this.clearMeetingData();
8702
+ await this.clearMeetingData();
8552
8703
 
8553
8704
  // upload logs on leave irrespective of meeting delete
8554
8705
  Trigger.trigger(
@@ -9407,10 +9558,10 @@ export default class Meeting extends StatelessWebexPlugin {
9407
9558
  });
9408
9559
 
9409
9560
  return MeetingUtil.endMeetingForAll(this)
9410
- .then((end) => {
9561
+ .then(async (end) => {
9411
9562
  this.meetingFiniteStateMachine.end();
9412
9563
 
9413
- this.clearMeetingData();
9564
+ await this.clearMeetingData();
9414
9565
  // upload logs on leave irrespective of meeting delete
9415
9566
  Trigger.trigger(
9416
9567
  this,
@@ -9458,7 +9609,7 @@ export default class Meeting extends StatelessWebexPlugin {
9458
9609
  * @public
9459
9610
  * @memberof Meeting
9460
9611
  */
9461
- clearMeetingData = () => {
9612
+ clearMeetingData = async () => {
9462
9613
  this.audio = null;
9463
9614
  this.video = null;
9464
9615
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9474,12 +9625,7 @@ export default class Meeting extends StatelessWebexPlugin {
9474
9625
 
9475
9626
  this.annotation.deregisterEvents();
9476
9627
 
9477
- // @ts-ignore - fix types
9478
- this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
9479
- // @ts-ignore - Fix type
9480
- this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
9481
-
9482
- this.clearLLMHealthCheckTimer();
9628
+ await this.cleanupLLMConneciton({throwOnError: false});
9483
9629
  };
9484
9630
 
9485
9631
  /**
@@ -10179,4 +10325,52 @@ export default class Meeting extends StatelessWebexPlugin {
10179
10325
  cancelSipCallOut(participantId: string) {
10180
10326
  return this.meetingRequest.cancelSipCallOut(participantId);
10181
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
+ }
10182
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;