@webex/plugin-meetings 3.11.0 → 3.12.0-next.2

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 +1173 -877
  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 +61 -7
  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 +291 -91
  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 +291 -76
  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 +191 -6
  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 +829 -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 +474 -37
  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: {
@@ -5802,37 +5907,35 @@ export default class Meeting extends StatelessWebexPlugin {
5802
5907
  * @returns {void}
5803
5908
  */
5804
5909
  stopTranscription() {
5805
- if (this.transcription) {
5806
- // @ts-ignore
5807
- this.webex.internal.voicea.off(
5808
- VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
5809
- this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
5810
- );
5910
+ // @ts-ignore
5911
+ this.webex.internal.voicea.off(
5912
+ VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
5913
+ this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
5914
+ );
5811
5915
 
5812
- // @ts-ignore
5813
- this.webex.internal.voicea.off(
5814
- VOICEAEVENTS.CAPTIONS_TURNED_ON,
5815
- this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
5816
- );
5916
+ // @ts-ignore
5917
+ this.webex.internal.voicea.off(
5918
+ VOICEAEVENTS.CAPTIONS_TURNED_ON,
5919
+ this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
5920
+ );
5817
5921
 
5818
- // @ts-ignore
5819
- this.webex.internal.voicea.off(
5820
- VOICEAEVENTS.EVA_COMMAND,
5821
- this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
5822
- );
5922
+ // @ts-ignore
5923
+ this.webex.internal.voicea.off(
5924
+ VOICEAEVENTS.EVA_COMMAND,
5925
+ this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
5926
+ );
5823
5927
 
5824
- // @ts-ignore
5825
- this.webex.internal.voicea.off(
5826
- VOICEAEVENTS.NEW_CAPTION,
5827
- this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5828
- );
5928
+ // @ts-ignore
5929
+ this.webex.internal.voicea.off(
5930
+ VOICEAEVENTS.NEW_CAPTION,
5931
+ this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
5932
+ );
5829
5933
 
5830
- // @ts-ignore
5831
- this.webex.internal.voicea.deregisterEvents();
5934
+ // @ts-ignore
5935
+ this.webex.internal.voicea.deregisterEvents();
5832
5936
 
5833
- this.areVoiceaEventsSetup = false;
5834
- this.triggerStopReceivingTranscriptionEvent();
5835
- }
5937
+ this.areVoiceaEventsSetup = false;
5938
+ this.triggerStopReceivingTranscriptionEvent();
5836
5939
  }
5837
5940
 
5838
5941
  /**
@@ -6093,6 +6196,7 @@ export default class Meeting extends StatelessWebexPlugin {
6093
6196
  return Promise.reject(error);
6094
6197
  })
6095
6198
  .then((join) => {
6199
+ this.saveDataChannelToken(join);
6096
6200
  // @ts-ignore - config coming from registerPlugin
6097
6201
  if (this.config.enableAutomaticLLM) {
6098
6202
  // @ts-ignore
@@ -6161,23 +6265,100 @@ export default class Meeting extends StatelessWebexPlugin {
6161
6265
  }
6162
6266
  }
6163
6267
 
6268
+ /**
6269
+ * Disconnects and cleans up the default LLM session listeners/timers.
6270
+ * @param {Object} options
6271
+ * @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
6272
+ * @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
6273
+ * @returns {Promise<void>}
6274
+ */
6275
+ private cleanupLLMConneciton = async ({
6276
+ removeOnlineListener = true,
6277
+ throwOnError = true,
6278
+ }: {
6279
+ removeOnlineListener?: boolean;
6280
+ throwOnError?: boolean;
6281
+ } = {}): Promise<void> => {
6282
+ try {
6283
+ // @ts-ignore - Fix type
6284
+ await this.webex.internal.llm.disconnectLLM({
6285
+ code: 3050,
6286
+ reason: 'done (permanent)',
6287
+ });
6288
+ } catch (error) {
6289
+ LoggerProxy.logger.error(
6290
+ 'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
6291
+ error
6292
+ );
6293
+
6294
+ if (throwOnError) {
6295
+ throw error;
6296
+ }
6297
+ } finally {
6298
+ if (removeOnlineListener) {
6299
+ // @ts-ignore - Fix type
6300
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6301
+ }
6302
+ // @ts-ignore - fix types
6303
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6304
+ // @ts-ignore - Fix type
6305
+ this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6306
+
6307
+ this.clearLLMHealthCheckTimer();
6308
+ }
6309
+ };
6310
+
6311
+ /**
6312
+ * Clears all data channel tokens stored in LLM.
6313
+ * Called during meeting cleanup to ensure stale tokens are not reused.
6314
+ * @returns {void}
6315
+ */
6316
+ clearDataChannelToken(): void {
6317
+ // @ts-ignore
6318
+ this.webex.internal.llm.resetDatachannelTokens();
6319
+ }
6320
+
6321
+ /**
6322
+ * Saves the data channel tokens from the join response into LLM so that
6323
+ * updateLLMConnection / updatePSDataChannel don't need to fetch them from locusInfo.
6324
+ * @param {Object} join - The parsed join response (from MeetingUtil.parseLocusJoin)
6325
+ * @returns {void}
6326
+ */
6327
+ saveDataChannelToken(join: any): void {
6328
+ const datachannelToken = join?.locus?.self?.datachannelToken;
6329
+ const practiceSessionDatachannelToken = join?.locus?.self?.practiceSessionDatachannelToken;
6330
+
6331
+ if (datachannelToken) {
6332
+ // @ts-ignore
6333
+ this.webex.internal.llm.setDatachannelToken(datachannelToken, DataChannelTokenType.Default);
6334
+ }
6335
+
6336
+ if (practiceSessionDatachannelToken) {
6337
+ // @ts-ignore
6338
+ this.webex.internal.llm.setDatachannelToken(
6339
+ practiceSessionDatachannelToken,
6340
+ DataChannelTokenType.PracticeSession
6341
+ );
6342
+ }
6343
+ }
6344
+
6164
6345
  /**
6165
6346
  * Connects to low latency mercury and reconnects if the address has changed
6166
6347
  * It will also disconnect if called when the meeting has ended
6167
- * @param {String} datachannelUrl
6168
6348
  * @returns {Promise}
6169
6349
  */
6170
6350
  async updateLLMConnection() {
6171
6351
  // @ts-ignore - Fix type
6172
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6352
+ const {url = undefined, info: {datachannelUrl = undefined} = {}} = this.locusInfo || {};
6173
6353
 
6174
6354
  const isJoined = this.isJoined();
6175
6355
 
6176
- // webinar panelist should use new data channel in practice session
6177
- const dataChannelUrl =
6178
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6179
- ? practiceSessionDatachannelUrl
6180
- : datachannelUrl;
6356
+ // @ts-ignore
6357
+ const datachannelToken = this.webex.internal.llm.getDatachannelToken(
6358
+ DataChannelTokenType.Default
6359
+ );
6360
+
6361
+ const dataChannelUrl = datachannelUrl;
6181
6362
 
6182
6363
  // @ts-ignore - Fix type
6183
6364
  if (this.webex.internal.llm.isConnected()) {
@@ -6190,21 +6371,7 @@ export default class Meeting extends StatelessWebexPlugin {
6190
6371
  ) {
6191
6372
  return undefined;
6192
6373
  }
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();
6374
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6208
6375
  }
6209
6376
 
6210
6377
  if (!isJoined) {
@@ -6213,7 +6380,7 @@ export default class Meeting extends StatelessWebexPlugin {
6213
6380
 
6214
6381
  // @ts-ignore - Fix type
6215
6382
  return this.webex.internal.llm
6216
- .registerAndConnect(url, dataChannelUrl)
6383
+ .registerAndConnect(url, dataChannelUrl, datachannelToken)
6217
6384
  .then((registerAndConnectResult) => {
6218
6385
  // @ts-ignore - Fix type
6219
6386
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7417,7 +7584,7 @@ export default class Meeting extends StatelessWebexPlugin {
7417
7584
  */
7418
7585
  private async waitForMediaConnectionConnected(): Promise<void> {
7419
7586
  try {
7420
- await this.mediaProperties.waitForMediaConnectionConnected();
7587
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7421
7588
  } catch (error) {
7422
7589
  const {iceConnected} = error;
7423
7590
 
@@ -8010,6 +8177,7 @@ export default class Meeting extends StatelessWebexPlugin {
8010
8177
  remoteMediaManagerConfig,
8011
8178
  bundlePolicy = 'max-bundle',
8012
8179
  additionalMediaOptions = {},
8180
+ allowPublishMediaInLobby = false,
8013
8181
  } = options;
8014
8182
 
8015
8183
  const {
@@ -8030,7 +8198,6 @@ export default class Meeting extends StatelessWebexPlugin {
8030
8198
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8031
8199
 
8032
8200
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8033
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8034
8201
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8035
8202
  throw new UserInLobbyError();
8036
8203
  }
@@ -8075,7 +8242,13 @@ export default class Meeting extends StatelessWebexPlugin {
8075
8242
  this.brbState = createBrbState(this, false);
8076
8243
 
8077
8244
  try {
8078
- await this.setUpLocalStreamReferences(localStreams);
8245
+ // if we're in a lobby and allowPublishMediaInLobby==false, we don't want to
8246
+ // setup local streams for publishing, because if we ever end up admitted to the meeting
8247
+ // but Locus event about it for us is delayed or missed, others could see/hear our user's video/audio
8248
+ // while the user would still think they're in the lobby
8249
+ if (allowPublishMediaInLobby || !this.isUserUnadmitted) {
8250
+ await this.setUpLocalStreamReferences(localStreams);
8251
+ }
8079
8252
 
8080
8253
  this.setMercuryListener();
8081
8254
 
@@ -8543,12 +8716,12 @@ export default class Meeting extends StatelessWebexPlugin {
8543
8716
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8544
8717
 
8545
8718
  return MeetingUtil.leaveMeeting(this, options)
8546
- .then((leave) => {
8719
+ .then(async (leave) => {
8547
8720
  // CA team recommends submitting this *after* locus /leave
8548
8721
  submitLeaveMetric();
8549
8722
 
8550
8723
  this.meetingFiniteStateMachine.leave();
8551
- this.clearMeetingData();
8724
+ await this.clearMeetingData();
8552
8725
 
8553
8726
  // upload logs on leave irrespective of meeting delete
8554
8727
  Trigger.trigger(
@@ -9407,10 +9580,10 @@ export default class Meeting extends StatelessWebexPlugin {
9407
9580
  });
9408
9581
 
9409
9582
  return MeetingUtil.endMeetingForAll(this)
9410
- .then((end) => {
9583
+ .then(async (end) => {
9411
9584
  this.meetingFiniteStateMachine.end();
9412
9585
 
9413
- this.clearMeetingData();
9586
+ await this.clearMeetingData();
9414
9587
  // upload logs on leave irrespective of meeting delete
9415
9588
  Trigger.trigger(
9416
9589
  this,
@@ -9458,7 +9631,7 @@ export default class Meeting extends StatelessWebexPlugin {
9458
9631
  * @public
9459
9632
  * @memberof Meeting
9460
9633
  */
9461
- clearMeetingData = () => {
9634
+ clearMeetingData = async () => {
9462
9635
  this.audio = null;
9463
9636
  this.video = null;
9464
9637
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9467,19 +9640,13 @@ export default class Meeting extends StatelessWebexPlugin {
9467
9640
  }
9468
9641
  this.queuedMediaUpdates = [];
9469
9642
 
9470
- if (this.transcription) {
9471
- this.stopTranscription();
9472
- this.transcription = undefined;
9473
- }
9643
+ this.stopTranscription();
9644
+ this.transcription = undefined;
9474
9645
 
9475
9646
  this.annotation.deregisterEvents();
9476
9647
 
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();
9648
+ this.clearDataChannelToken();
9649
+ await this.cleanupLLMConneciton({throwOnError: false});
9483
9650
  };
9484
9651
 
9485
9652
  /**
@@ -10179,4 +10346,52 @@ export default class Meeting extends StatelessWebexPlugin {
10179
10346
  cancelSipCallOut(participantId: string) {
10180
10347
  return this.meetingRequest.cancelSipCallOut(participantId);
10181
10348
  }
10349
+
10350
+ /**
10351
+ * Method to get new data
10352
+ * @returns {Promise}
10353
+ */
10354
+ public async refreshDataChannelToken() {
10355
+ const isPracticeSession = this.webinar.isJoinPracticeSessionDataChannel();
10356
+ const dataChannelTokenType = this.getDataChannelTokenType();
10357
+
10358
+ try {
10359
+ const res = await this.meetingRequest.fetchDatachannelToken({
10360
+ locusUrl: this.locusUrl,
10361
+ requestingParticipantId: this.members.selfId,
10362
+ isPracticeSession,
10363
+ });
10364
+
10365
+ return {
10366
+ body: {
10367
+ datachannelToken: res.body.datachannelToken,
10368
+ dataChannelTokenType,
10369
+ },
10370
+ };
10371
+ } catch (e) {
10372
+ const msg = e?.message || String(e);
10373
+
10374
+ LoggerProxy.logger.warn(
10375
+ `Meeting:index#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${msg}`,
10376
+ {statusCode: e?.statusCode}
10377
+ );
10378
+
10379
+ return null;
10380
+ }
10381
+ }
10382
+
10383
+ /**
10384
+ * Determines the current data channel token type based on the meeting state.
10385
+ *
10386
+ * variant should be used when connecting to the LLM data channel.
10387
+ *
10388
+ * @returns {DataChannelTokenType} The token type representing the current session mode.
10389
+ */
10390
+ public getDataChannelTokenType(): DataChannelTokenType {
10391
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
10392
+ return DataChannelTokenType.PracticeSession;
10393
+ }
10394
+
10395
+ return DataChannelTokenType.Default;
10396
+ }
10182
10397
  }