@webex/plugin-meetings 3.11.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +28 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +3 -1
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +709 -380
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +10 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +290 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +217 -79
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1082 -861
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +100 -45
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +2 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/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 +3 -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 +99 -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 +21 -2
  88. package/dist/types/locus-info/types.d.ts +1 -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 +38 -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 +3 -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 +627 -249
  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 +231 -61
  127. package/src/locus-info/selfUtils.ts +1 -0
  128. package/src/locus-info/types.ts +1 -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 +205 -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 +135 -41
  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 +1869 -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 +383 -46
  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 +652 -31
  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
@@ -32,6 +32,7 @@ interface IInMeetingActions {
32
32
  canLowerAllHands?: boolean;
33
33
  canLowerSomeoneElsesHand?: boolean;
34
34
  bothLeaveAndEndMeetingAvailable?: boolean;
35
+ requireHostEndMeetingBeforeLeave?: boolean;
35
36
  canEnableClosedCaption?: boolean;
36
37
  canStartTranscribing?: boolean;
37
38
  canStopTranscribing?: boolean;
@@ -117,6 +118,8 @@ interface IInMeetingActions {
117
118
  canMoveToLobby?: boolean;
118
119
  canEnablePollingQA?: boolean;
119
120
  canDisablePollingQA?: boolean;
121
+ canAttendeeRequestAiAssistantEnabled?: boolean;
122
+ isAttendeeRequestAiAssistantDeclinedAll?: boolean;
120
123
  }
121
124
 
122
125
  /**
@@ -169,6 +172,8 @@ export default class InMeetingActions implements IInMeetingActions {
169
172
 
170
173
  bothLeaveAndEndMeetingAvailable = null;
171
174
 
175
+ requireHostEndMeetingBeforeLeave = null;
176
+
172
177
  canEnableClosedCaption = null;
173
178
 
174
179
  canStartTranscribing = null;
@@ -337,6 +342,10 @@ export default class InMeetingActions implements IInMeetingActions {
337
342
 
338
343
  canDisablePollingQA = null;
339
344
 
345
+ canAttendeeRequestAiAssistantEnabled = null;
346
+
347
+ isAttendeeRequestAiAssistantDeclinedAll = null;
348
+
340
349
  /**
341
350
  * Returns all meeting action options
342
351
  * @returns {Object}
@@ -364,6 +373,7 @@ export default class InMeetingActions implements IInMeetingActions {
364
373
  canLowerAllHands: this.canLowerAllHands,
365
374
  canLowerSomeoneElsesHand: this.canLowerSomeoneElsesHand,
366
375
  bothLeaveAndEndMeetingAvailable: this.bothLeaveAndEndMeetingAvailable,
376
+ requireHostEndMeetingBeforeLeave: this.requireHostEndMeetingBeforeLeave,
367
377
  canEnableClosedCaption: this.canEnableClosedCaption,
368
378
  canStartTranscribing: this.canStartTranscribing,
369
379
  canStopTranscribing: this.canStopTranscribing,
@@ -448,6 +458,8 @@ export default class InMeetingActions implements IInMeetingActions {
448
458
  canMoveToLobby: this.canMoveToLobby,
449
459
  canEnablePollingQA: this.canEnablePollingQA,
450
460
  canDisablePollingQA: this.canDisablePollingQA,
461
+ canAttendeeRequestAiAssistantEnabled: this.canAttendeeRequestAiAssistantEnabled,
462
+ isAttendeeRequestAiAssistantDeclinedAll: this.isAttendeeRequestAiAssistantDeclinedAll,
451
463
  });
452
464
 
453
465
  /**
@@ -13,7 +13,7 @@ import {
13
13
  CALL_DIAGNOSTIC_CONFIG,
14
14
  RtcMetrics,
15
15
  } from '@webex/internal-plugin-metrics';
16
- import {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
16
+ import type {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
17
17
 
18
18
  import {
19
19
  ConnectionState,
@@ -33,6 +33,8 @@ import {
33
33
  InboundAudioIssueSubTypes,
34
34
  } from '@webex/internal-media-core';
35
35
 
36
+ import {DataChannelTokenType} from '@webex/internal-plugin-llm';
37
+
36
38
  import {
37
39
  LocalStream,
38
40
  LocalCameraStream,
@@ -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, 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
 
@@ -574,6 +579,7 @@ export default class Meeting extends StatelessWebexPlugin {
574
579
  breakouts: any;
575
580
  simultaneousInterpretation: any;
576
581
  annotation: any;
582
+ aiEnableRequest: any;
577
583
  webinar: any;
578
584
  conversationUrl: string;
579
585
  callStateForMetrics: CallStateForMetrics;
@@ -622,6 +628,13 @@ export default class Meeting extends StatelessWebexPlugin {
622
628
  keepAliveTimerId: NodeJS.Timeout;
623
629
  lastVideoLayoutInfo: any;
624
630
  locusInfo: any;
631
+ // this group of properties is populated via updateMeetingObject() that's registered as a callback with LocusInfo
632
+ isUserUnadmitted?: boolean;
633
+ joinedWith?: any;
634
+ selfId?: string;
635
+ roles: any[];
636
+ // ... there is more ... see SelfUtils.parse()
637
+ // end of the group
625
638
  locusMediaRequest?: LocusMediaRequest;
626
639
  mediaProperties: MediaProperties;
627
640
  mediaRequestManagers: {
@@ -656,7 +669,6 @@ export default class Meeting extends StatelessWebexPlugin {
656
669
  endCallInitJoinReq: any;
657
670
  endJoinReqResp: any;
658
671
  endLocalSDPGenRemoteSDPRecvDelay: any;
659
- joinedWith: any;
660
672
  locusId: any;
661
673
  startCallInitJoinReq: any;
662
674
  startJoinReqResp: any;
@@ -671,12 +683,11 @@ export default class Meeting extends StatelessWebexPlugin {
671
683
  permissionTokenReceivedLocalTime: number;
672
684
  resourceId: any;
673
685
  resourceUrl: string;
674
- selfId: string;
675
686
  state: any;
676
687
  localAudioStreamMuteStateHandler: () => void;
677
688
  localVideoStreamMuteStateHandler: () => void;
678
689
  localOutputTrackChangeHandler: () => void;
679
- roles: any[];
690
+ localConstraintsChangeHandler: () => void;
680
691
  environment: string;
681
692
  namespace = MEETINGS;
682
693
  allowMediaInLobby: boolean;
@@ -893,6 +904,10 @@ export default class Meeting extends StatelessWebexPlugin {
893
904
  */
894
905
  // @ts-ignore
895
906
  this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
907
+
908
+ // @ts-ignore
909
+ this.aiEnableRequest = new AIEnableRequest({}, {parent: this.webex});
910
+
896
911
  /**
897
912
  * @instance
898
913
  * @type {Annotation}
@@ -1542,6 +1557,12 @@ export default class Meeting extends StatelessWebexPlugin {
1542
1557
  }
1543
1558
  };
1544
1559
 
1560
+ this.localConstraintsChangeHandler = () => {
1561
+ if (!this.isMultistream) {
1562
+ this.mediaProperties.webrtcMediaConnection?.updatePreferredBitrateKbps();
1563
+ }
1564
+ };
1565
+
1545
1566
  /**
1546
1567
  * Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
1547
1568
  * @instance
@@ -2951,6 +2972,18 @@ export default class Meeting extends StatelessWebexPlugin {
2951
2972
  );
2952
2973
  });
2953
2974
 
2975
+ this.locusInfo.on(
2976
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
2977
+ ({aiSummaryNotification}) => {
2978
+ Trigger.trigger(
2979
+ this,
2980
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
2981
+ EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
2982
+ {aiSummaryNotification}
2983
+ );
2984
+ }
2985
+ );
2986
+
2954
2987
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_WEBCAST_CHANGED, ({state}) => {
2955
2988
  Trigger.trigger(
2956
2989
  this,
@@ -3402,6 +3435,8 @@ export default class Meeting extends StatelessWebexPlugin {
3402
3435
  this.recordingController.setLocusUrl(this.locusUrl);
3403
3436
  this.controlsOptionsManager.setLocusUrl(this.locusUrl, !!isMainLocus);
3404
3437
  this.webinar.locusUrlUpdate(url);
3438
+ // @ts-ignore
3439
+ this.webex.internal.llm.setRefreshHandler(() => this.refreshDataChannelToken());
3405
3440
 
3406
3441
  Trigger.trigger(
3407
3442
  this,
@@ -3432,6 +3467,7 @@ export default class Meeting extends StatelessWebexPlugin {
3432
3467
  this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
3433
3468
  this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
3434
3469
  this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
3470
+ this.aiEnableRequest.approvalUrlUpdate(payload?.services?.approval?.url);
3435
3471
  });
3436
3472
  }
3437
3473
 
@@ -3529,6 +3565,9 @@ export default class Meeting extends StatelessWebexPlugin {
3529
3565
  // @ts-ignore - config coming from registerPlugin
3530
3566
  if (datachannelUrl && this.config.enableAutomaticLLM) {
3531
3567
  this.updateLLMConnection();
3568
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
3569
+ this.webinar.updatePSDataChannel();
3570
+ }
3532
3571
  }
3533
3572
  }
3534
3573
 
@@ -3761,6 +3800,10 @@ export default class Meeting extends StatelessWebexPlugin {
3761
3800
  );
3762
3801
  });
3763
3802
 
3803
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ID_CHANGED, (payload) => {
3804
+ this.aiEnableRequest.selfParticipantIdUpdate(payload.selfId);
3805
+ });
3806
+
3764
3807
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
3765
3808
  const targetChanged = this.simultaneousInterpretation.updateSelfInterpretation(payload);
3766
3809
  Trigger.trigger(
@@ -4263,6 +4306,9 @@ export default class Meeting extends StatelessWebexPlugin {
4263
4306
  bothLeaveAndEndMeetingAvailable: MeetingUtil.bothLeaveAndEndMeetingAvailable(
4264
4307
  this.userDisplayHints
4265
4308
  ),
4309
+ requireHostEndMeetingBeforeLeave: MeetingUtil.requireHostEndMeetingBeforeLeave(
4310
+ this.userDisplayHints
4311
+ ),
4266
4312
  canEnableClosedCaption: MeetingUtil.canEnableClosedCaption(this.userDisplayHints),
4267
4313
  canStartTranscribing: MeetingUtil.canStartTranscribing(this.userDisplayHints),
4268
4314
  canStopTranscribing: MeetingUtil.canStopTranscribing(this.userDisplayHints),
@@ -4521,6 +4567,12 @@ export default class Meeting extends StatelessWebexPlugin {
4521
4567
  requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
4522
4568
  displayHints: this.userDisplayHints,
4523
4569
  }),
4570
+ canAttendeeRequestAiAssistantEnabled: MeetingUtil.canAttendeeRequestAiAssistantEnabled(
4571
+ this.userDisplayHints,
4572
+ this.roles
4573
+ ),
4574
+ isAttendeeRequestAiAssistantDeclinedAll:
4575
+ MeetingUtil.attendeeRequestAiAssistantDeclinedAll(this.userDisplayHints),
4524
4576
  }) || changed;
4525
4577
  }
4526
4578
  if (changed) {
@@ -4592,7 +4644,8 @@ export default class Meeting extends StatelessWebexPlugin {
4592
4644
  mediaId: string;
4593
4645
  host: object;
4594
4646
  selfId: string;
4595
- dataSets: DataSet[];
4647
+ dataSets: DataSet[]; // only sent by Locus when hash trees are used
4648
+ metadata: Metadata; // only sent by Locus when hash trees are used
4596
4649
  }) {
4597
4650
  const mtgLocus: any = data.locus;
4598
4651
 
@@ -4608,6 +4661,7 @@ export default class Meeting extends StatelessWebexPlugin {
4608
4661
  trigger: 'join-response',
4609
4662
  locus: mtgLocus,
4610
4663
  dataSets: data.dataSets,
4664
+ metadata: data.metadata,
4611
4665
  });
4612
4666
  }
4613
4667
 
@@ -4817,6 +4871,7 @@ export default class Meeting extends StatelessWebexPlugin {
4817
4871
  this.localVideoStreamMuteStateHandler
4818
4872
  );
4819
4873
  oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4874
+ oldStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4820
4875
 
4821
4876
  // we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
4822
4877
  this.mediaProperties.setLocalVideoStream(localStream);
@@ -4832,6 +4887,7 @@ export default class Meeting extends StatelessWebexPlugin {
4832
4887
  this.localVideoStreamMuteStateHandler
4833
4888
  );
4834
4889
  localStream?.on(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
4890
+ localStream?.on(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4835
4891
 
4836
4892
  if (!this.isMultistream || !localStream) {
4837
4893
  // for multistream WCME automatically un-publishes the old stream when we publish a new one
@@ -4966,6 +5022,7 @@ export default class Meeting extends StatelessWebexPlugin {
4966
5022
  this.localVideoStreamMuteStateHandler
4967
5023
  );
4968
5024
  videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
5025
+ videoStream?.off(LocalStreamEventNames.ConstraintsChange, this.localConstraintsChangeHandler);
4969
5026
 
4970
5027
  shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
4971
5028
  shareAudioStream?.off(
@@ -5379,6 +5436,18 @@ export default class Meeting extends StatelessWebexPlugin {
5379
5436
  let joined = false;
5380
5437
  let joinResponse = prevJoinResponse;
5381
5438
 
5439
+ /* Before we do anything, check if RTCPeerConnection is available. Normally this is checked
5440
+ by addMediaInternal() itself when creating the media connection, but since joinWithMedia()
5441
+ is a convenience method that does both join() and addMedia(), we want to fail fast here
5442
+ in case WebRTC is not available at all.
5443
+ */
5444
+ if (WebCapabilities.supportsRTCPeerConnection() === CapabilityState.NOT_CAPABLE) {
5445
+ // throw the same error that would be thrown by addMediaInternal()
5446
+ throw new Errors.WebrtcApiNotAvailableError(
5447
+ 'RTCPeerConnection API is not available in this environment'
5448
+ );
5449
+ }
5450
+
5382
5451
  try {
5383
5452
  let turnServerInfo;
5384
5453
  let turnDiscoverySkippedReason;
@@ -5449,7 +5518,10 @@ export default class Meeting extends StatelessWebexPlugin {
5449
5518
  // if this was the first attempt, let's do a retry
5450
5519
  let shouldRetry = !isRetry;
5451
5520
 
5452
- if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
5521
+ if (
5522
+ CallDiagnosticUtils.isSdpOfferCreationError(error) ||
5523
+ CallDiagnosticUtils.isWebrtcApiNotAvailableError(error)
5524
+ ) {
5453
5525
  // errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
5454
5526
  // so there is no point doing a retry
5455
5527
  shouldRetry = false;
@@ -5765,7 +5837,7 @@ export default class Meeting extends StatelessWebexPlugin {
5765
5837
  this.isReactionsSupported()
5766
5838
  ) {
5767
5839
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5768
- if (!member) {
5840
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5769
5841
  // @ts-ignore -- fix type
5770
5842
  LoggerProxy.logger.warn(
5771
5843
  `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 +5845,7 @@ export default class Meeting extends StatelessWebexPlugin {
5773
5845
  break;
5774
5846
  }
5775
5847
 
5776
- const {name} = member;
5848
+ const name = (member && member.name) || e.data.sender.displayName;
5777
5849
  const processedReaction: ProcessedReaction = {
5778
5850
  reaction: e.data.reaction,
5779
5851
  sender: {
@@ -6161,6 +6233,49 @@ export default class Meeting extends StatelessWebexPlugin {
6161
6233
  }
6162
6234
  }
6163
6235
 
6236
+ /**
6237
+ * Disconnects and cleans up the default LLM session listeners/timers.
6238
+ * @param {Object} options
6239
+ * @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
6240
+ * @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
6241
+ * @returns {Promise<void>}
6242
+ */
6243
+ private cleanupLLMConneciton = async ({
6244
+ removeOnlineListener = true,
6245
+ throwOnError = true,
6246
+ }: {
6247
+ removeOnlineListener?: boolean;
6248
+ throwOnError?: boolean;
6249
+ } = {}): Promise<void> => {
6250
+ try {
6251
+ // @ts-ignore - Fix type
6252
+ await this.webex.internal.llm.disconnectLLM({
6253
+ code: 3050,
6254
+ reason: 'done (permanent)',
6255
+ });
6256
+ } catch (error) {
6257
+ LoggerProxy.logger.error(
6258
+ 'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
6259
+ error
6260
+ );
6261
+
6262
+ if (throwOnError) {
6263
+ throw error;
6264
+ }
6265
+ } finally {
6266
+ if (removeOnlineListener) {
6267
+ // @ts-ignore - Fix type
6268
+ this.webex.internal.llm.off('online', this.handleLLMOnline);
6269
+ }
6270
+ // @ts-ignore - fix types
6271
+ this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
6272
+ // @ts-ignore - Fix type
6273
+ this.webex.internal.llm.off(LOCUS_LLM_EVENT, this.processLocusLLMEvent);
6274
+
6275
+ this.clearLLMHealthCheckTimer();
6276
+ }
6277
+ };
6278
+
6164
6279
  /**
6165
6280
  * Connects to low latency mercury and reconnects if the address has changed
6166
6281
  * It will also disconnect if called when the meeting has ended
@@ -6169,15 +6284,26 @@ export default class Meeting extends StatelessWebexPlugin {
6169
6284
  */
6170
6285
  async updateLLMConnection() {
6171
6286
  // @ts-ignore - Fix type
6172
- const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
6287
+ const {
6288
+ url = undefined,
6289
+ info: {datachannelUrl = undefined} = {},
6290
+ self: {datachannelToken = undefined} = {},
6291
+ } = this.locusInfo || {};
6173
6292
 
6174
6293
  const isJoined = this.isJoined();
6175
6294
 
6295
+ // @ts-ignore
6296
+ const currentToken = this.webex.internal.llm.getDatachannelToken(DataChannelTokenType.Default);
6297
+
6298
+ const finalToken = currentToken ?? datachannelToken;
6299
+
6300
+ if (!currentToken && datachannelToken) {
6301
+ // @ts-ignore
6302
+ this.webex.internal.llm.setDatachannelToken(datachannelToken, DataChannelTokenType.Default);
6303
+ }
6304
+
6176
6305
  // webinar panelist should use new data channel in practice session
6177
- const dataChannelUrl =
6178
- this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
6179
- ? practiceSessionDatachannelUrl
6180
- : datachannelUrl;
6306
+ const dataChannelUrl = datachannelUrl;
6181
6307
 
6182
6308
  // @ts-ignore - Fix type
6183
6309
  if (this.webex.internal.llm.isConnected()) {
@@ -6190,21 +6316,7 @@ export default class Meeting extends StatelessWebexPlugin {
6190
6316
  ) {
6191
6317
  return undefined;
6192
6318
  }
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();
6319
+ await this.cleanupLLMConneciton({removeOnlineListener: false});
6208
6320
  }
6209
6321
 
6210
6322
  if (!isJoined) {
@@ -6213,7 +6325,7 @@ export default class Meeting extends StatelessWebexPlugin {
6213
6325
 
6214
6326
  // @ts-ignore - Fix type
6215
6327
  return this.webex.internal.llm
6216
- .registerAndConnect(url, dataChannelUrl)
6328
+ .registerAndConnect(url, dataChannelUrl, finalToken)
6217
6329
  .then((registerAndConnectResult) => {
6218
6330
  // @ts-ignore - Fix type
6219
6331
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
@@ -7417,7 +7529,7 @@ export default class Meeting extends StatelessWebexPlugin {
7417
7529
  */
7418
7530
  private async waitForMediaConnectionConnected(): Promise<void> {
7419
7531
  try {
7420
- await this.mediaProperties.waitForMediaConnectionConnected();
7532
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7421
7533
  } catch (error) {
7422
7534
  const {iceConnected} = error;
7423
7535
 
@@ -8010,6 +8122,7 @@ export default class Meeting extends StatelessWebexPlugin {
8010
8122
  remoteMediaManagerConfig,
8011
8123
  bundlePolicy = 'max-bundle',
8012
8124
  additionalMediaOptions = {},
8125
+ allowPublishMediaInLobby = false,
8013
8126
  } = options;
8014
8127
 
8015
8128
  const {
@@ -8030,7 +8143,6 @@ export default class Meeting extends StatelessWebexPlugin {
8030
8143
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8031
8144
 
8032
8145
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8033
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8034
8146
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8035
8147
  throw new UserInLobbyError();
8036
8148
  }
@@ -8075,7 +8187,13 @@ export default class Meeting extends StatelessWebexPlugin {
8075
8187
  this.brbState = createBrbState(this, false);
8076
8188
 
8077
8189
  try {
8078
- await this.setUpLocalStreamReferences(localStreams);
8190
+ // if we're in a lobby and allowPublishMediaInLobby==false, we don't want to
8191
+ // setup local streams for publishing, because if we ever end up admitted to the meeting
8192
+ // but Locus event about it for us is delayed or missed, others could see/hear our user's video/audio
8193
+ // while the user would still think they're in the lobby
8194
+ if (allowPublishMediaInLobby || !this.isUserUnadmitted) {
8195
+ await this.setUpLocalStreamReferences(localStreams);
8196
+ }
8079
8197
 
8080
8198
  this.setMercuryListener();
8081
8199
 
@@ -8543,12 +8661,12 @@ export default class Meeting extends StatelessWebexPlugin {
8543
8661
  LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
8544
8662
 
8545
8663
  return MeetingUtil.leaveMeeting(this, options)
8546
- .then((leave) => {
8664
+ .then(async (leave) => {
8547
8665
  // CA team recommends submitting this *after* locus /leave
8548
8666
  submitLeaveMetric();
8549
8667
 
8550
8668
  this.meetingFiniteStateMachine.leave();
8551
- this.clearMeetingData();
8669
+ await this.clearMeetingData();
8552
8670
 
8553
8671
  // upload logs on leave irrespective of meeting delete
8554
8672
  Trigger.trigger(
@@ -9407,10 +9525,10 @@ export default class Meeting extends StatelessWebexPlugin {
9407
9525
  });
9408
9526
 
9409
9527
  return MeetingUtil.endMeetingForAll(this)
9410
- .then((end) => {
9528
+ .then(async (end) => {
9411
9529
  this.meetingFiniteStateMachine.end();
9412
9530
 
9413
- this.clearMeetingData();
9531
+ await this.clearMeetingData();
9414
9532
  // upload logs on leave irrespective of meeting delete
9415
9533
  Trigger.trigger(
9416
9534
  this,
@@ -9458,7 +9576,7 @@ export default class Meeting extends StatelessWebexPlugin {
9458
9576
  * @public
9459
9577
  * @memberof Meeting
9460
9578
  */
9461
- clearMeetingData = () => {
9579
+ clearMeetingData = async () => {
9462
9580
  this.audio = null;
9463
9581
  this.video = null;
9464
9582
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
@@ -9474,12 +9592,7 @@ export default class Meeting extends StatelessWebexPlugin {
9474
9592
 
9475
9593
  this.annotation.deregisterEvents();
9476
9594
 
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();
9595
+ await this.cleanupLLMConneciton({throwOnError: false});
9483
9596
  };
9484
9597
 
9485
9598
  /**
@@ -10179,4 +10292,52 @@ export default class Meeting extends StatelessWebexPlugin {
10179
10292
  cancelSipCallOut(participantId: string) {
10180
10293
  return this.meetingRequest.cancelSipCallOut(participantId);
10181
10294
  }
10295
+
10296
+ /**
10297
+ * Method to get new data
10298
+ * @returns {Promise}
10299
+ */
10300
+ public async refreshDataChannelToken() {
10301
+ const isPracticeSession = this.webinar.isJoinPracticeSessionDataChannel();
10302
+ const dataChannelTokenType = this.getDataChannelTokenType();
10303
+
10304
+ try {
10305
+ const res = await this.meetingRequest.fetchDatachannelToken({
10306
+ locusUrl: this.locusUrl,
10307
+ requestingParticipantId: this.members.selfId,
10308
+ isPracticeSession,
10309
+ });
10310
+
10311
+ return {
10312
+ body: {
10313
+ datachannelToken: res.body.datachannelToken,
10314
+ dataChannelTokenType,
10315
+ },
10316
+ };
10317
+ } catch (e) {
10318
+ const msg = e?.message || String(e);
10319
+
10320
+ LoggerProxy.logger.warn(
10321
+ `Meeting:index#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${msg}`,
10322
+ {statusCode: e?.statusCode}
10323
+ );
10324
+
10325
+ return null;
10326
+ }
10327
+ }
10328
+
10329
+ /**
10330
+ * Determines the current data channel token type based on the meeting state.
10331
+ *
10332
+ * variant should be used when connecting to the LLM data channel.
10333
+ *
10334
+ * @returns {DataChannelTokenType} The token type representing the current session mode.
10335
+ */
10336
+ public getDataChannelTokenType(): DataChannelTokenType {
10337
+ if (this.webinar.isJoinPracticeSessionDataChannel()) {
10338
+ return DataChannelTokenType.PracticeSession;
10339
+ }
10340
+
10341
+ return DataChannelTokenType.Default;
10342
+ }
10182
10343
  }
@@ -33,6 +33,7 @@ import {
33
33
  ToggleReactionsOptions,
34
34
  PostMeetingDataConsentOptions,
35
35
  SynchronizeVideoLayout,
36
+ fetchDataChannelTokenOptions,
36
37
  } from './request.type';
37
38
  import MeetingUtil from './util';
38
39
  import {AnnotationInfo} from '../annotation/annotation.types';
@@ -1126,4 +1127,45 @@ export default class MeetingRequest extends StatelessWebexPlugin {
1126
1127
  throw err;
1127
1128
  }
1128
1129
  }
1130
+
1131
+ /**
1132
+ * Sends a request to retrieve the datachannel authorization token for a participant.
1133
+ *
1134
+ * For regular meeting data channel:
1135
+ * GET /locus/api/v1/loci/{uuid:lid}/participant/{uuid:pid}/datachannel/token
1136
+ *
1137
+ * For practice session data channel:
1138
+ * GET /locus/api/v1/loci/{uuid:lid}/participant/{uuid:pid}/practiceSession/datachannel/token
1139
+ *
1140
+ * @param {string} locusUrl - The locus url.
1141
+ * @param {string} requestingParticipantId - The participant UUID.
1142
+ * @param {boolean} [isPracticeSession=false] - Whether to get the practice session token.
1143
+ * @returns {Promise<{datachannelToken: string}>}
1144
+ */
1145
+ public async fetchDatachannelToken({
1146
+ locusUrl,
1147
+ requestingParticipantId,
1148
+ isPracticeSession = false,
1149
+ }: fetchDataChannelTokenOptions) {
1150
+ if (!locusUrl || !requestingParticipantId) {
1151
+ return Promise.reject(new Error('locusUrl and participantId are required'));
1152
+ }
1153
+ const practicePrefix = isPracticeSession ? '/practiceSession' : '';
1154
+
1155
+ const uri = `${locusUrl}/${PARTICIPANT}/${requestingParticipantId}${practicePrefix}/datachannel/token`;
1156
+
1157
+ // @ts-ignore
1158
+ return this.locusDeltaRequest({
1159
+ method: HTTP_VERBS.GET,
1160
+ uri,
1161
+ }).catch((err) => {
1162
+ LoggerProxy.logger.warn(
1163
+ `Meeting:request#fetchDatachannelToken --> Failed to retrieve ${
1164
+ isPracticeSession ? 'practice session ' : ''
1165
+ }datachannel token: ${err?.message || err}`
1166
+ );
1167
+
1168
+ return null;
1169
+ });
1170
+ }
1129
1171
  }
@@ -88,4 +88,10 @@ export type UnsetStageVideoLayout = {
88
88
  overrideDefault: false;
89
89
  };
90
90
 
91
+ export type fetchDataChannelTokenOptions = {
92
+ locusUrl: string;
93
+ requestingParticipantId: string;
94
+ isPracticeSession: boolean;
95
+ };
96
+
91
97
  export type SynchronizeVideoLayout = SetStageVideoLayout | UnsetStageVideoLayout;