@webex/plugin-meetings 3.1.0 → 3.2.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 (205) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/reconnection-not-started.js +46 -0
  4. package/dist/common/errors/reconnection-not-started.js.map +1 -0
  5. package/dist/constants.js +16 -5
  6. package/dist/constants.js.map +1 -1
  7. package/dist/index.js +80 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/interpretation/index.js +1 -1
  10. package/dist/interpretation/siLanguage.js +1 -1
  11. package/dist/locus-info/controlsUtils.js +7 -1
  12. package/dist/locus-info/controlsUtils.js.map +1 -1
  13. package/dist/locus-info/index.js +10 -0
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/media/properties.js +102 -57
  16. package/dist/media/properties.js.map +1 -1
  17. package/dist/mediaQualityMetrics/config.js +10 -10
  18. package/dist/mediaQualityMetrics/config.js.map +1 -1
  19. package/dist/meeting/in-meeting-actions.js +6 -0
  20. package/dist/meeting/in-meeting-actions.js.map +1 -1
  21. package/dist/meeting/index.js +564 -475
  22. package/dist/meeting/index.js.map +1 -1
  23. package/dist/meeting/locusMediaRequest.js +27 -0
  24. package/dist/meeting/locusMediaRequest.js.map +1 -1
  25. package/dist/meeting/util.js +9 -16
  26. package/dist/meeting/util.js.map +1 -1
  27. package/dist/meeting/voicea-meeting.js +37 -49
  28. package/dist/meeting/voicea-meeting.js.map +1 -1
  29. package/dist/meeting-info/util.js +304 -267
  30. package/dist/meeting-info/util.js.map +1 -1
  31. package/dist/meeting-info/utilv2.js +334 -298
  32. package/dist/meeting-info/utilv2.js.map +1 -1
  33. package/dist/meetings/index.js +12 -28
  34. package/dist/meetings/index.js.map +1 -1
  35. package/dist/reachability/index.js +88 -9
  36. package/dist/reachability/index.js.map +1 -1
  37. package/dist/reconnection-manager/index.js +138 -109
  38. package/dist/reconnection-manager/index.js.map +1 -1
  39. package/dist/roap/request.js +3 -27
  40. package/dist/roap/request.js.map +1 -1
  41. package/dist/statsAnalyzer/index.js +8 -2
  42. package/dist/statsAnalyzer/index.js.map +1 -1
  43. package/dist/statsAnalyzer/mqaUtil.js +17 -0
  44. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  45. package/dist/types/annotation/annotation.types.d.ts +42 -0
  46. package/dist/types/annotation/constants.d.ts +31 -0
  47. package/dist/types/annotation/index.d.ts +117 -0
  48. package/dist/types/breakouts/breakout.d.ts +8 -0
  49. package/dist/types/breakouts/collection.d.ts +5 -0
  50. package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
  51. package/dist/types/breakouts/events.d.ts +8 -0
  52. package/dist/types/breakouts/index.d.ts +5 -0
  53. package/dist/types/breakouts/request.d.ts +22 -0
  54. package/dist/types/breakouts/utils.d.ts +15 -0
  55. package/dist/types/common/browser-detection.d.ts +9 -0
  56. package/dist/types/common/collection.d.ts +48 -0
  57. package/dist/types/common/config.d.ts +2 -0
  58. package/dist/types/common/errors/captcha-error.d.ts +15 -0
  59. package/dist/types/common/errors/intent-to-join.d.ts +16 -0
  60. package/dist/types/common/errors/join-meeting.d.ts +17 -0
  61. package/dist/types/common/errors/media.d.ts +15 -0
  62. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  63. package/dist/types/common/errors/parameter.d.ts +15 -0
  64. package/dist/types/common/errors/password-error.d.ts +15 -0
  65. package/dist/types/common/errors/permission.d.ts +14 -0
  66. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  67. package/dist/types/common/errors/reconnection-not-started.d.ts +13 -0
  68. package/dist/types/common/errors/reconnection.d.ts +15 -0
  69. package/dist/types/common/errors/stats.d.ts +15 -0
  70. package/dist/types/common/errors/webex-errors.d.ts +93 -0
  71. package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
  72. package/dist/types/common/events/events-scope.d.ts +17 -0
  73. package/dist/types/common/events/events.d.ts +12 -0
  74. package/dist/types/common/events/trigger-proxy.d.ts +2 -0
  75. package/dist/types/common/events/util.d.ts +2 -0
  76. package/dist/types/common/logs/logger-config.d.ts +2 -0
  77. package/dist/types/common/logs/logger-proxy.d.ts +2 -0
  78. package/dist/types/common/logs/request.d.ts +36 -0
  79. package/dist/types/common/queue.d.ts +34 -0
  80. package/dist/types/config.d.ts +73 -0
  81. package/dist/types/constants.d.ts +1098 -0
  82. package/dist/types/controls-options-manager/constants.d.ts +4 -0
  83. package/dist/types/controls-options-manager/enums.d.ts +15 -0
  84. package/dist/types/controls-options-manager/index.d.ts +136 -0
  85. package/dist/types/controls-options-manager/types.d.ts +43 -0
  86. package/dist/types/controls-options-manager/util.d.ts +1 -0
  87. package/dist/types/index.d.ts +19 -0
  88. package/dist/types/interceptors/index.d.ts +2 -0
  89. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  90. package/dist/types/interpretation/collection.d.ts +5 -0
  91. package/dist/types/interpretation/index.d.ts +5 -0
  92. package/dist/types/interpretation/siLanguage.d.ts +5 -0
  93. package/dist/types/locus-info/controlsUtils.d.ts +2 -0
  94. package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
  95. package/dist/types/locus-info/fullState.d.ts +2 -0
  96. package/dist/types/locus-info/hostUtils.d.ts +2 -0
  97. package/dist/types/locus-info/index.d.ts +322 -0
  98. package/dist/types/locus-info/infoUtils.d.ts +2 -0
  99. package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
  100. package/dist/types/locus-info/parser.d.ts +272 -0
  101. package/dist/types/locus-info/selfUtils.d.ts +2 -0
  102. package/dist/types/media/MediaConnectionAwaiter.d.ts +61 -0
  103. package/dist/types/media/index.d.ts +34 -0
  104. package/dist/types/media/properties.d.ts +117 -0
  105. package/dist/types/media/util.d.ts +2 -0
  106. package/dist/types/mediaQualityMetrics/config.d.ts +247 -0
  107. package/dist/types/meeting/in-meeting-actions.d.ts +173 -0
  108. package/dist/types/meeting/index.d.ts +1832 -0
  109. package/dist/types/meeting/locusMediaRequest.d.ts +75 -0
  110. package/dist/types/meeting/muteState.d.ts +178 -0
  111. package/dist/types/meeting/request.d.ts +295 -0
  112. package/dist/types/meeting/request.type.d.ts +11 -0
  113. package/dist/types/meeting/state.d.ts +9 -0
  114. package/dist/types/meeting/util.d.ts +122 -0
  115. package/dist/types/meeting/voicea-meeting.d.ts +17 -0
  116. package/dist/types/meeting-info/collection.d.ts +20 -0
  117. package/dist/types/meeting-info/index.d.ts +69 -0
  118. package/dist/types/meeting-info/meeting-info-v2.d.ts +123 -0
  119. package/dist/types/meeting-info/request.d.ts +22 -0
  120. package/dist/types/meeting-info/util.d.ts +49 -0
  121. package/dist/types/meeting-info/utilv2.d.ts +65 -0
  122. package/dist/types/meetings/collection.d.ts +40 -0
  123. package/dist/types/meetings/index.d.ts +383 -0
  124. package/dist/types/meetings/meetings.types.d.ts +4 -0
  125. package/dist/types/meetings/request.d.ts +27 -0
  126. package/dist/types/meetings/util.d.ts +18 -0
  127. package/dist/types/member/index.d.ts +160 -0
  128. package/dist/types/member/types.d.ts +32 -0
  129. package/dist/types/member/util.d.ts +2 -0
  130. package/dist/types/members/collection.d.ts +29 -0
  131. package/dist/types/members/index.d.ts +353 -0
  132. package/dist/types/members/request.d.ts +114 -0
  133. package/dist/types/members/types.d.ts +25 -0
  134. package/dist/types/members/util.d.ts +215 -0
  135. package/dist/types/metrics/constants.d.ts +70 -0
  136. package/dist/types/metrics/index.d.ts +45 -0
  137. package/dist/types/multistream/mediaRequestManager.d.ts +119 -0
  138. package/dist/types/multistream/receiveSlot.d.ts +68 -0
  139. package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
  140. package/dist/types/multistream/remoteMedia.d.ts +72 -0
  141. package/dist/types/multistream/remoteMediaGroup.d.ts +49 -0
  142. package/dist/types/multistream/remoteMediaManager.d.ts +300 -0
  143. package/dist/types/multistream/sendSlotManager.d.ts +69 -0
  144. package/dist/types/networkQualityMonitor/index.d.ts +70 -0
  145. package/dist/types/personal-meeting-room/index.d.ts +47 -0
  146. package/dist/types/personal-meeting-room/request.d.ts +14 -0
  147. package/dist/types/personal-meeting-room/util.d.ts +2 -0
  148. package/dist/types/reachability/clusterReachability.d.ts +110 -0
  149. package/dist/types/reachability/index.d.ts +120 -0
  150. package/dist/types/reachability/request.d.ts +39 -0
  151. package/dist/types/reachability/util.d.ts +15 -0
  152. package/dist/types/reactions/constants.d.ts +3 -0
  153. package/dist/types/reactions/reactions.d.ts +4 -0
  154. package/dist/types/reactions/reactions.type.d.ts +52 -0
  155. package/dist/types/reconnection-manager/index.d.ts +126 -0
  156. package/dist/types/recording-controller/enums.d.ts +7 -0
  157. package/dist/types/recording-controller/index.d.ts +207 -0
  158. package/dist/types/recording-controller/util.d.ts +14 -0
  159. package/dist/types/roap/index.d.ts +86 -0
  160. package/dist/types/roap/request.d.ts +39 -0
  161. package/dist/types/roap/turnDiscovery.d.ts +155 -0
  162. package/dist/types/rtcMetrics/constants.d.ts +4 -0
  163. package/dist/types/rtcMetrics/index.d.ts +61 -0
  164. package/dist/types/statsAnalyzer/global.d.ts +36 -0
  165. package/dist/types/statsAnalyzer/index.d.ts +217 -0
  166. package/dist/types/statsAnalyzer/mqaUtil.d.ts +48 -0
  167. package/dist/types/transcription/index.d.ts +64 -0
  168. package/dist/types/webinar/collection.d.ts +16 -0
  169. package/dist/types/webinar/index.d.ts +5 -0
  170. package/dist/webinar/index.js +1 -1
  171. package/package.json +22 -22
  172. package/src/common/errors/reconnection-not-started.ts +25 -0
  173. package/src/constants.ts +14 -5
  174. package/src/index.ts +30 -0
  175. package/src/locus-info/controlsUtils.ts +11 -0
  176. package/src/locus-info/index.ts +16 -0
  177. package/src/media/properties.ts +67 -15
  178. package/src/mediaQualityMetrics/config.ts +13 -7
  179. package/src/meeting/in-meeting-actions.ts +12 -0
  180. package/src/meeting/index.ts +144 -107
  181. package/src/meeting/locusMediaRequest.ts +31 -0
  182. package/src/meeting/util.ts +9 -16
  183. package/src/meeting/voicea-meeting.ts +44 -46
  184. package/src/meeting-info/util.ts +241 -233
  185. package/src/meeting-info/utilv2.ts +250 -244
  186. package/src/meetings/index.ts +15 -27
  187. package/src/reachability/index.ts +60 -0
  188. package/src/reconnection-manager/index.ts +128 -105
  189. package/src/roap/request.ts +1 -24
  190. package/src/statsAnalyzer/index.ts +10 -3
  191. package/src/statsAnalyzer/mqaUtil.ts +23 -0
  192. package/test/unit/spec/locus-info/controlsUtils.js +20 -0
  193. package/test/unit/spec/locus-info/index.js +21 -0
  194. package/test/unit/spec/media/properties.ts +145 -140
  195. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  196. package/test/unit/spec/meeting/index.js +271 -105
  197. package/test/unit/spec/meeting/locusMediaRequest.ts +49 -0
  198. package/test/unit/spec/meeting/utils.js +3 -10
  199. package/test/unit/spec/meeting/voicea-meeting.ts +5 -14
  200. package/test/unit/spec/meetings/index.js +59 -17
  201. package/test/unit/spec/reachability/index.ts +266 -0
  202. package/test/unit/spec/reconnection-manager/index.js +127 -39
  203. package/test/unit/spec/roap/request.ts +0 -37
  204. package/test/unit/spec/stats-analyzer/index.js +100 -8
  205. package/src/common/errors/reconnection-in-progress.ts +0 -8
@@ -12,6 +12,7 @@ import {Credentials, Token, WebexPlugin} from '@webex/webex-core';
12
12
  import Support from '@webex/internal-plugin-support';
13
13
  import MockWebex from '@webex/test-helper-mock-webex';
14
14
  import StaticConfig from '@webex/plugin-meetings/src/common/config';
15
+ import ReconnectionNotStartedError from '@webex/plugin-meetings/src/common/errors/reconnection-not-started';
15
16
  import {Defer} from '@webex/common';
16
17
  import {
17
18
  FLOOR_ACTION,
@@ -658,6 +659,9 @@ describe('plugin-meetings', () => {
658
659
  assert.calledOnceWithExactly(meeting.addMedia, mediaOptions, fakeTurnServerInfo);
659
660
 
660
661
  assert.deepEqual(result, {join: fakeJoinResult, media: test4});
662
+
663
+ // resets joinWithMediaRetryInfo
664
+ assert.deepEqual(meeting.joinWithMediaRetryInfo, {isRetry: false, prevJoinResponse: undefined});
661
665
  });
662
666
 
663
667
  it("should not call handleTurnDiscoveryHttpResponse if we don't send a TURN discovery request with join", async () => {
@@ -719,8 +723,9 @@ describe('plugin-meetings', () => {
719
723
 
720
724
  await assert.isRejected(meeting.joinWithMedia({mediaOptions: {allowMediaInLobby: true}}));
721
725
 
722
- assert.calledOnceWithExactly(abortTurnDiscoveryStub);
726
+ assert.calledTwice(abortTurnDiscoveryStub);
723
727
 
728
+ assert.calledTwice(Metrics.sendBehavioralMetric);
724
729
  assert.calledWith(
725
730
  Metrics.sendBehavioralMetric,
726
731
  BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
@@ -730,11 +735,67 @@ describe('plugin-meetings', () => {
730
735
  reason: error.message,
731
736
  stack: error.stack,
732
737
  leaveErrorReason: undefined,
738
+ isRetry: false,
733
739
  },
734
740
  {
735
741
  type: error.name,
736
742
  }
737
743
  );
744
+ assert.calledWith(
745
+ Metrics.sendBehavioralMetric,
746
+ BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
747
+ {
748
+ correlation_id: meeting.correlationId,
749
+ locus_id: undefined,
750
+ reason: error.message,
751
+ stack: error.stack,
752
+ leaveErrorReason: undefined,
753
+ isRetry: true,
754
+ },
755
+ {
756
+ type: error.name,
757
+ }
758
+ );
759
+
760
+ // resets joinWithMediaRetryInfo
761
+ assert.deepEqual(meeting.joinWithMediaRetryInfo, {isRetry: false, prevJoinResponse: undefined});
762
+ });
763
+
764
+ it('should resolve if join() fails the first time but succeeds the second time', async () => {
765
+ const error = new Error('fake');
766
+ meeting.join = sinon.stub().onFirstCall().returns(Promise.reject(error)).onSecondCall().returns(Promise.resolve(fakeJoinResult));
767
+ const leaveStub = sinon.stub(meeting, 'leave').resolves();
768
+
769
+ const result = await meeting.joinWithMedia({
770
+ joinOptions,
771
+ mediaOptions,
772
+ });
773
+
774
+ assert.calledOnce(abortTurnDiscoveryStub);
775
+ assert.calledTwice(meeting.join);
776
+ assert.notCalled(leaveStub);
777
+
778
+ assert.calledOnce(Metrics.sendBehavioralMetric);
779
+ assert.calledWith(
780
+ Metrics.sendBehavioralMetric.firstCall,
781
+ BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
782
+ {
783
+ correlation_id: meeting.correlationId,
784
+ locus_id: meeting.locusUrl.split('/').pop(),
785
+ reason: error.message,
786
+ stack: error.stack,
787
+ leaveErrorReason: undefined,
788
+ isRetry: false,
789
+ },
790
+ {
791
+ type: error.name,
792
+ }
793
+ );
794
+
795
+ assert.deepEqual(result, {join: fakeJoinResult, media: test4});
796
+
797
+ // resets joinWithMediaRetryInfo
798
+ assert.deepEqual(meeting.joinWithMediaRetryInfo, {isRetry: false, prevJoinResponse: undefined});
738
799
  });
739
800
 
740
801
  it('should fail if called with allowMediaInLobby:false', async () => {
@@ -767,8 +828,26 @@ describe('plugin-meetings', () => {
767
828
  reason: 'joinWithMedia failure',
768
829
  });
769
830
 
831
+
832
+ // Behavioral metric is sent on both calls of joinWithMedia
833
+ assert.calledTwice(Metrics.sendBehavioralMetric);
770
834
  assert.calledWith(
771
- Metrics.sendBehavioralMetric,
835
+ Metrics.sendBehavioralMetric.firstCall,
836
+ BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
837
+ {
838
+ correlation_id: meeting.correlationId,
839
+ locus_id: meeting.locusUrl.split('/').pop(),
840
+ reason: addMediaError.message,
841
+ stack: addMediaError.stack,
842
+ leaveErrorReason: undefined,
843
+ isRetry: false,
844
+ },
845
+ {
846
+ type: addMediaError.name,
847
+ }
848
+ );
849
+ assert.calledWith(
850
+ Metrics.sendBehavioralMetric.secondCall,
772
851
  BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
773
852
  {
774
853
  correlation_id: meeting.correlationId,
@@ -776,6 +855,47 @@ describe('plugin-meetings', () => {
776
855
  reason: addMediaError.message,
777
856
  stack: addMediaError.stack,
778
857
  leaveErrorReason: leaveError.message,
858
+ isRetry: true,
859
+ },
860
+ {
861
+ type: addMediaError.name,
862
+ }
863
+ );
864
+ });
865
+
866
+ it('should not call leave() if addMedia fails the first time and succeeds the second time and should only call join() once', async () => {
867
+ const addMediaError = new Error('fake addMedia error');
868
+ const leaveError = new Error('leave error');
869
+ const leaveStub = sinon.stub(meeting, 'leave').rejects(leaveError);
870
+
871
+ meeting.addMedia = sinon
872
+ .stub()
873
+ .onFirstCall()
874
+ .rejects(addMediaError)
875
+ .onSecondCall()
876
+ .resolves(test4);
877
+
878
+ const result = await meeting.joinWithMedia({
879
+ joinOptions,
880
+ mediaOptions,
881
+ });
882
+
883
+ assert.deepEqual(result, {join: fakeJoinResult, media: test4});
884
+
885
+ assert.calledOnce(meeting.join);
886
+ assert.notCalled(leaveStub);
887
+
888
+ assert.calledOnce(Metrics.sendBehavioralMetric);
889
+ assert.calledWith(
890
+ Metrics.sendBehavioralMetric.firstCall,
891
+ BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
892
+ {
893
+ correlation_id: meeting.correlationId,
894
+ locus_id: meeting.locusUrl.split('/').pop(),
895
+ reason: addMediaError.message,
896
+ stack: addMediaError.stack,
897
+ leaveErrorReason: undefined,
898
+ isRetry: false,
779
899
  },
780
900
  {
781
901
  type: addMediaError.name,
@@ -786,12 +906,12 @@ describe('plugin-meetings', () => {
786
906
 
787
907
  describe('#isTranscriptionSupported', () => {
788
908
  it('should return false if the feature is not supported for the meeting', () => {
789
- meeting.locusInfo.controls = {transcribe: {transcribing: false}};
909
+ meeting.locusInfo.controls = {transcribe: {caption: false}};
790
910
 
791
911
  assert.equal(meeting.isTranscriptionSupported(), false);
792
912
  });
793
913
  it('should return true if webex assitant is enabled', () => {
794
- meeting.locusInfo.controls = {transcribe: {transcribing: true}};
914
+ meeting.locusInfo.controls = {transcribe: {caption: true}};
795
915
 
796
916
  assert.equal(meeting.isTranscriptionSupported(), true);
797
917
  });
@@ -802,7 +922,7 @@ describe('plugin-meetings', () => {
802
922
  webex.internal.voicea.on = sinon.stub();
803
923
  webex.internal.voicea.off = sinon.stub();
804
924
  webex.internal.voicea.listenToEvents = sinon.stub();
805
- webex.internal.voicea.toggleTranscribing = sinon.stub();
925
+ webex.internal.voicea.turnOnCaptions = sinon.stub();
806
926
  });
807
927
 
808
928
  it('should subscribe to events for the first time and avoid subscribing for future transcription starts', async () => {
@@ -817,17 +937,16 @@ describe('plugin-meetings', () => {
817
937
  assert.equal(webex.internal.voicea.on.callCount, 4);
818
938
  assert.equal(meeting.areVoiceaEventsSetup, true);
819
939
  assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
820
- assert.calledWith(webex.internal.voicea.toggleTranscribing, true);
940
+ assert.called(webex.internal.voicea.turnOnCaptions);
821
941
 
822
942
  await meeting.startTranscription();
823
943
  assert.equal(webex.internal.voicea.on.callCount, 4);
824
944
  assert.equal(meeting.areVoiceaEventsSetup, true);
825
945
  assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
826
- assert.calledTwice(webex.internal.voicea.toggleTranscribing);
827
- assert.calledWith(webex.internal.voicea.toggleTranscribing, true);
946
+ assert.calledTwice(webex.internal.voicea.turnOnCaptions);
828
947
  });
829
948
 
830
- it('should listen to events and not toggleTranscribing if the user is not a host', async () => {
949
+ it('should listen to events and not turnOnCaptions if the user is not a host', async () => {
831
950
  meeting.joinedWith = {
832
951
  state: 'JOINED',
833
952
  };
@@ -839,7 +958,7 @@ describe('plugin-meetings', () => {
839
958
  assert.equal(webex.internal.voicea.on.callCount, 4);
840
959
  assert.equal(meeting.areVoiceaEventsSetup, true);
841
960
  assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
842
- assert.notCalled(webex.internal.voicea.toggleTranscribing);
961
+ assert.notCalled(webex.internal.voicea.turnOnCaptions);
843
962
  });
844
963
 
845
964
  it("should throw error if request doesn't work", async () => {
@@ -858,7 +977,7 @@ describe('plugin-meetings', () => {
858
977
  webex.internal.voicea.on = sinon.stub();
859
978
  webex.internal.voicea.off = sinon.stub();
860
979
  webex.internal.voicea.listenToEvents = sinon.stub();
861
- webex.internal.voicea.toggleTranscribing = sinon.stub();
980
+ webex.internal.voicea.turnOnCaptions = sinon.stub();
862
981
  });
863
982
 
864
983
  it('should stop listening to voicea events and also trigger a stop event', () => {
@@ -1183,6 +1302,31 @@ describe('plugin-meetings', () => {
1183
1302
  );
1184
1303
  });
1185
1304
  });
1305
+
1306
+ describe('#handleLLMOnline', () => {
1307
+ beforeEach(() => {
1308
+ webex.internal.llm.off = sinon.stub();
1309
+ });
1310
+
1311
+ it('turns off llm online, emits transcription connected events', () => {
1312
+ meeting.handleLLMOnline();
1313
+ assert.calledOnceWithExactly(
1314
+ webex.internal.llm.off,
1315
+ 'online',
1316
+ meeting.handleLLMOnline
1317
+ );
1318
+ assert.calledWith(
1319
+ TriggerProxy.trigger,
1320
+ sinon.match.instanceOf(Meeting),
1321
+ {
1322
+ file: 'meeting/index',
1323
+ function: 'handleLLMOnline',
1324
+ },
1325
+ EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
1326
+ );
1327
+ });
1328
+ });
1329
+
1186
1330
  describe('#join', () => {
1187
1331
  let sandbox = null;
1188
1332
  let setCorrelationIdSpy;
@@ -1232,15 +1376,10 @@ describe('plugin-meetings', () => {
1232
1376
  assert.calledOnce(MeetingUtil.joinMeeting);
1233
1377
  assert.calledOnce(meeting.setLocus);
1234
1378
  assert.equal(result, joinMeetingResult);
1235
-
1236
1379
  assert.calledWith(
1237
- TriggerProxy.trigger,
1238
- sinon.match.instanceOf(Meeting),
1239
- {
1240
- file: 'meeting/index',
1241
- function: 'join',
1242
- },
1243
- EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
1380
+ webex.internal.llm.on,
1381
+ 'online',
1382
+ meeting.handleLLMOnline
1244
1383
  );
1245
1384
  });
1246
1385
 
@@ -1531,7 +1670,7 @@ describe('plugin-meetings', () => {
1531
1670
  };
1532
1671
  meeting.mediaProperties.setMediaDirection = sinon.stub().returns(true);
1533
1672
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
1534
- meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
1673
+ meeting.mediaProperties.getCurrentConnectionInfo = sinon.stub().resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
1535
1674
  meeting.audio = muteStateStub;
1536
1675
  meeting.video = muteStateStub;
1537
1676
  sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
@@ -1631,11 +1770,14 @@ describe('plugin-meetings', () => {
1631
1770
  turnServerUsed: true,
1632
1771
  retriedWithTurnServer: false,
1633
1772
  isMultistream: false,
1773
+ isJoinWithMediaRetry: false,
1634
1774
  signalingState: 'unknown',
1635
1775
  connectionState: 'unknown',
1636
1776
  iceConnectionState: 'unknown',
1637
1777
  someReachabilityMetric1: 'some value1',
1638
1778
  someReachabilityMetric2: 'some value2',
1779
+ selectedCandidatePairChanges: 2,
1780
+ numTransports: 1,
1639
1781
  }
1640
1782
  );
1641
1783
  });
@@ -1735,11 +1877,14 @@ describe('plugin-meetings', () => {
1735
1877
  turnServerUsed: true,
1736
1878
  retriedWithTurnServer: false,
1737
1879
  isMultistream: false,
1880
+ isJoinWithMediaRetry: false,
1738
1881
  signalingState: 'unknown',
1739
1882
  connectionState: 'unknown',
1740
1883
  iceConnectionState: 'unknown',
1741
1884
  someReachabilityMetric1: 'some value1',
1742
1885
  someReachabilityMetric2: 'some value2',
1886
+ selectedCandidatePairChanges: 2,
1887
+ numTransports: 1,
1743
1888
  }
1744
1889
  );
1745
1890
  });
@@ -2213,9 +2358,12 @@ describe('plugin-meetings', () => {
2213
2358
  turnServerUsed: true,
2214
2359
  retriedWithTurnServer: true,
2215
2360
  isMultistream: false,
2361
+ isJoinWithMediaRetry: false,
2216
2362
  signalingState: 'unknown',
2217
2363
  connectionState: 'unknown',
2218
2364
  iceConnectionState: 'unknown',
2365
+ selectedCandidatePairChanges: 2,
2366
+ numTransports: 1,
2219
2367
  },
2220
2368
  ]);
2221
2369
 
@@ -2394,8 +2542,11 @@ describe('plugin-meetings', () => {
2394
2542
  correlation_id: meeting.correlationId,
2395
2543
  locus_id: meeting.locusUrl.split('/').pop(),
2396
2544
  connectionType: 'udp',
2545
+ selectedCandidatePairChanges: 2,
2546
+ numTransports: 1,
2397
2547
  isMultistream: false,
2398
2548
  retriedWithTurnServer: true,
2549
+ isJoinWithMediaRetry: false,
2399
2550
  },
2400
2551
  ]);
2401
2552
  meeting.roap.doTurnDiscovery;
@@ -2536,8 +2687,11 @@ describe('plugin-meetings', () => {
2536
2687
  correlation_id: meeting.correlationId,
2537
2688
  locus_id: meeting.locusUrl.split('/').pop(),
2538
2689
  connectionType: 'udp',
2690
+ selectedCandidatePairChanges: 2,
2691
+ numTransports: 1,
2539
2692
  isMultistream: false,
2540
2693
  retriedWithTurnServer: false,
2694
+ isJoinWithMediaRetry: false,
2541
2695
  someReachabilityMetric1: 'some value1',
2542
2696
  someReachabilityMetric2: 'some value2',
2543
2697
  }
@@ -2596,9 +2750,12 @@ describe('plugin-meetings', () => {
2596
2750
  turnServerUsed: true,
2597
2751
  retriedWithTurnServer: false,
2598
2752
  isMultistream: false,
2753
+ isJoinWithMediaRetry: false,
2599
2754
  signalingState: 'unknown',
2600
2755
  connectionState: 'unknown',
2601
2756
  iceConnectionState: 'unknown',
2757
+ selectedCandidatePairChanges: 2,
2758
+ numTransports: 1,
2602
2759
  }
2603
2760
  );
2604
2761
 
@@ -3017,7 +3174,7 @@ describe('plugin-meetings', () => {
3017
3174
  meeting.mediaId = 'fake media id';
3018
3175
  meeting.selfUrl = 'selfUrl';
3019
3176
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
3020
- meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
3177
+ meeting.mediaProperties.getCurrentConnectionInfo = sinon.stub().resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
3021
3178
  meeting.setMercuryListener = sinon.stub();
3022
3179
  meeting.locusInfo.onFullLocus = sinon.stub();
3023
3180
  meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
@@ -6029,11 +6186,22 @@ describe('plugin-meetings', () => {
6029
6186
 
6030
6187
  beforeEach(() => {
6031
6188
  sandbox = sinon.createSandbox();
6032
- sandbox.stub(meeting, 'cleanupLocalStreams');
6189
+ meeting.statsAnalyzer = {
6190
+ stopAnalyzer: sinon.stub().returns(Promise.resolve())
6191
+ };
6033
6192
 
6034
- sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
6193
+ meeting.reconnectionManager = {
6194
+ cleanUp: sinon.stub()
6195
+ };
6196
+
6197
+ meeting.cleanupLocalStreams=sinon.stub();
6198
+ meeting.closeRemoteStreams = sinon.stub().returns(Promise.resolve());
6199
+ meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
6200
+ meeting.unsetRemoteStreams = sinon.stub();
6201
+ meeting.unsetPeerConnections = sinon.stub();
6202
+ meeting.addMedia = sinon.stub().returns(Promise.resolve());
6203
+ meeting.mediaProperties.setMediaDirection = sinon.stub();
6035
6204
 
6036
- sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
6037
6205
  sandbox
6038
6206
  .stub(MeetingUtil, 'joinMeeting')
6039
6207
  .returns(
@@ -6095,9 +6263,11 @@ describe('plugin-meetings', () => {
6095
6263
  });
6096
6264
  });
6097
6265
 
6098
- it('should reconnectMedia after DX joins after moveTo', async () => {
6266
+ it('should cleanup on moveTo & addMedia after', async () => {
6099
6267
  await meeting.moveTo('resourceId');
6100
6268
 
6269
+ assert.equal(meeting.isMoveToInProgress, true);
6270
+
6101
6271
  await meeting.locusInfo.emitScoped(
6102
6272
  {
6103
6273
  file: 'locus-info',
@@ -6105,26 +6275,26 @@ describe('plugin-meetings', () => {
6105
6275
  },
6106
6276
  'SELF_OBSERVING'
6107
6277
  );
6278
+
6108
6279
 
6109
- // beacuse we are calling callback so we need to wait
6110
-
6111
- assert.called(meeting.cleanupLocalStreams);
6112
-
6113
- // give queued Promise callbacks a chance to run
6114
- await Promise.resolve();
6115
-
6116
- assert.called(meeting.mediaProperties.setMediaDirection);
6117
-
6118
- assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
6119
- mediaDirection: {
6120
- sendVideo: false,
6121
- receiveVideo: false,
6122
- sendAudio: false,
6123
- receiveAudio: false,
6124
- sendShare: false,
6125
- receiveShare: true,
6126
- },
6127
- });
6280
+ // Verify that the event handler behaves as expected
6281
+ expect(meeting.statsAnalyzer.stopAnalyzer.calledOnce).to.be.true;
6282
+ expect(meeting.closeRemoteStreams.calledOnce).to.be.true;
6283
+ await testUtils.flushPromises();
6284
+ expect(meeting.closePeerConnections.calledOnce).to.be.true;
6285
+ await testUtils.flushPromises();
6286
+ expect(meeting.cleanupLocalStreams.calledOnce).to.be.true;
6287
+ expect(meeting.unsetRemoteStreams.calledOnce).to.be.true;
6288
+ expect(meeting.unsetPeerConnections.calledOnce).to.be.true;
6289
+ expect(meeting.reconnectionManager.cleanUp.calledOnce).to.be.true;
6290
+ expect(meeting.mediaProperties.setMediaDirection.calledOnce).to.be.true;
6291
+ expect(meeting.addMedia.calledOnceWithExactly({
6292
+ audioEnabled: false,
6293
+ videoEnabled: false,
6294
+ shareVideoEnabled: true
6295
+ })).to.be.true;
6296
+ await testUtils.flushPromises();
6297
+ assert.equal(meeting.isMoveToInProgress, false);
6128
6298
  });
6129
6299
 
6130
6300
  it('should throw an error if moveTo call fails', async () => {
@@ -6132,6 +6302,7 @@ describe('plugin-meetings', () => {
6132
6302
  try {
6133
6303
  await meeting.moveTo('resourceId');
6134
6304
  } catch {
6305
+ assert.equal(meeting.isMoveToInProgress, false);
6135
6306
  assert.calledOnce(Metrics.sendBehavioralMetric);
6136
6307
  assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_TO_FAILURE, {
6137
6308
  correlation_id: meeting.correlationId,
@@ -6153,6 +6324,7 @@ describe('plugin-meetings', () => {
6153
6324
  'SELF_OBSERVING'
6154
6325
  );
6155
6326
  } catch {
6327
+ assert.equal(meeting.isMoveToInProgress, false);
6156
6328
  assert.calledOnce(Metrics.sendBehavioralMetric);
6157
6329
  assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_TO_FAILURE, {
6158
6330
  correlation_id: meeting.correlationId,
@@ -6778,21 +6950,13 @@ describe('plugin-meetings', () => {
6778
6950
  }),
6779
6951
  };
6780
6952
  meeting.setupMediaConnectionListeners();
6781
- meeting.deferSDPAnswer = {
6782
- resolve: sinon.stub(),
6783
- reject: sinon.stub(),
6784
- };
6785
6953
  meeting.sdpResponseTimer = '1234';
6786
- meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
6954
+ sinon.stub(meeting.mediaProperties, 'waitForMediaConnectionConnected').resolves();
6787
6955
 
6788
- eventListeners[Event.REMOTE_SDP_ANSWER_PROCESSED]();
6789
6956
  meeting.config.reconnection.enabled = true;
6790
6957
  meeting.currentMediaStatus = {audio: true};
6791
6958
  meeting.reconnectionManager = new ReconnectionManager(meeting);
6792
- meeting.reconnectionManager.reconnect = sinon.stub().returns(Promise.resolve());
6793
- meeting.reconnectionManager.reset = sinon.stub().returns(true);
6794
- meeting.reconnectionManager.cleanup = sinon.stub().returns(true);
6795
- meeting.reconnectionManager.setStatus = sinon.stub();
6959
+ sinon.stub(meeting.reconnectionManager, 'reconnect').returns(Promise.resolve());
6796
6960
  });
6797
6961
 
6798
6962
  it('should throw error if media not established before trying reconnect', async () => {
@@ -6812,87 +6976,72 @@ describe('plugin-meetings', () => {
6812
6976
  }
6813
6977
  });
6814
6978
 
6815
- it('should trigger reconnection success and send CA metric', async () => {
6816
- await meeting.reconnect();
6979
+ it('should call the right functions', async () => {
6980
+ const options = {id: 'fake options'};
6981
+ await meeting.reconnect(options);
6982
+
6983
+ sinon.stub(meeting, 'waitForRemoteSDPAnswer').resolves();
6817
6984
 
6818
- assert.calledWith(
6819
- TriggerProxy.trigger,
6820
- sinon.match.instanceOf(Meeting),
6821
- {file: 'meeting/index', function: 'reconnect'},
6822
- 'meeting:reconnectionSuccess'
6823
- );
6824
- assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
6825
- name: 'client.media.recovered',
6826
- payload: {
6827
- recoveredBy: 'new',
6828
- },
6829
- options: {
6830
- meetingId: meeting.id,
6831
- },
6832
- });
6833
6985
  assert.calledOnceWithExactly(
6834
- meeting.reconnectionManager.setStatus,
6835
- RECONNECTION.STATE.COMPLETE
6986
+ meeting.reconnectionManager.reconnect,
6987
+ options,
6988
+ sinon.match.any
6836
6989
  );
6837
- });
6990
+ const callback = meeting.reconnectionManager.reconnect.getCalls()[0].args[1];
6838
6991
 
6839
- it('should reset after reconnection success', async () => {
6840
- await meeting.reconnect();
6841
- assert.calledOnce(meeting.reconnectionManager.reset);
6992
+ // call the completion callback
6993
+ assert.isFunction(callback);
6994
+ await callback();
6995
+
6996
+ // check that the right things were called by the callback
6997
+ assert.calledOnceWithExactly(meeting.waitForRemoteSDPAnswer);
6998
+ assert.calledOnceWithExactly(meeting.mediaProperties.waitForMediaConnectionConnected);
6842
6999
  });
6843
7000
  });
6844
7001
 
6845
7002
  describe('unsuccessful reconnect', () => {
7003
+ let logUploadSpy;
7004
+
6846
7005
  beforeEach(() => {
6847
- meeting.config.reconnection.enabled = true;
7006
+ logUploadSpy = sinon.spy(meeting, 'uploadLogs');
6848
7007
  meeting.currentMediaStatus = {audio: true};
6849
7008
  meeting.reconnectionManager = new ReconnectionManager(meeting);
6850
7009
  meeting.reconnectionManager.reconnect = sinon
6851
7010
  .stub()
6852
7011
  .returns(Promise.reject(new Error()));
6853
- meeting.reconnectionManager.reset = sinon.stub().returns(true);
6854
7012
  });
6855
7013
 
6856
- it('should trigger an unsuccessful reconnection', async () => {
7014
+ it('should upload logs on reconnect failure', async () => {
6857
7015
  await assert.isRejected(meeting.reconnect());
6858
7016
  assert.calledWith(
6859
7017
  TriggerProxy.trigger,
6860
7018
  sinon.match.instanceOf(Meeting),
6861
7019
  {file: 'meeting/index', function: 'reconnect'},
6862
- 'meeting:reconnectionFailure',
6863
- {error: sinon.match.any}
7020
+ EVENTS.REQUEST_UPLOAD_LOGS,
7021
+ sinon.match.instanceOf(Meeting)
6864
7022
  );
6865
7023
  });
6866
7024
 
6867
- it('should send metrics on reconnect failure', async () => {
7025
+ it('should fail without uploading logs if there is no reconnectionManager', async () => {
7026
+ meeting.reconnectionManager = null;
6868
7027
  await assert.isRejected(meeting.reconnect());
6869
- assert(Metrics.sendBehavioralMetric.calledOnce);
6870
- assert.calledWith(
6871
- Metrics.sendBehavioralMetric,
6872
- BEHAVIORAL_METRICS.MEETING_RECONNECT_FAILURE,
6873
- {
6874
- correlation_id: meeting.correlationId,
6875
- locus_id: meeting.locusUrl.split('/').pop(),
6876
- reason: sinon.match.any,
6877
- stack: sinon.match.any,
6878
- }
6879
- );
7028
+ assert.notCalled(logUploadSpy);
6880
7029
  });
6881
7030
 
6882
- it('should upload logs on reconnect failure', async () => {
7031
+ it('should fail without uploading logs if there is no media established', async () => {
7032
+ meeting.currentMediaStatus = null;
6883
7033
  await assert.isRejected(meeting.reconnect());
6884
- assert.calledWith(
6885
- TriggerProxy.trigger,
6886
- sinon.match.instanceOf(Meeting),
6887
- {file: 'meeting/index', function: 'reconnect'},
6888
- EVENTS.REQUEST_UPLOAD_LOGS,
6889
- sinon.match.instanceOf(Meeting)
6890
- );
7034
+ assert.notCalled(logUploadSpy);
6891
7035
  });
6892
7036
 
6893
- it('should reset after an unsuccessful reconnection', async () => {
6894
- await assert.isRejected(meeting.reconnect());
6895
- assert.calledOnce(meeting.reconnectionManager.reset);
7037
+ it('should resolve if the error is ReconnectionNotStartedError', async () => {
7038
+ meeting.reconnectionManager.reconnect.returns(
7039
+ Promise.reject(new ReconnectionNotStartedError())
7040
+ );
7041
+ await meeting.reconnect();
7042
+
7043
+ // logs shouldn't be uploaded
7044
+ assert.notCalled(logUploadSpy);
6896
7045
  });
6897
7046
  });
6898
7047
  });
@@ -7738,6 +7887,7 @@ describe('plugin-meetings', () => {
7738
7887
  describe('#setUpLocusInfoSelfListener', () => {
7739
7888
  it('listens to the self unadmitted guest event', (done) => {
7740
7889
  meeting.startKeepAlive = sinon.stub();
7890
+ meeting.updateLLMConnection = sinon.stub();
7741
7891
  meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_UNADMITTED_GUEST', test1);
7742
7892
  assert.calledOnceWithExactly(meeting.startKeepAlive);
7743
7893
  assert.calledThrice(TriggerProxy.trigger);
@@ -7748,6 +7898,7 @@ describe('plugin-meetings', () => {
7748
7898
  'meeting:self:lobbyWaiting',
7749
7899
  {payload: test1}
7750
7900
  );
7901
+ assert.calledOnce(meeting.updateLLMConnection);
7751
7902
  done();
7752
7903
  });
7753
7904
  it('listens to the self admitted guest event', (done) => {
@@ -8100,6 +8251,21 @@ describe('plugin-meetings', () => {
8100
8251
  EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
8101
8252
  );
8102
8253
  });
8254
+
8255
+ it('listens to the locus manual caption update event', () => {
8256
+ meeting.locusInfo.emit(
8257
+ {function: 'test', file: 'test'},
8258
+ 'CONTROLS_MEETING_MANUAL_CAPTION_UPDATED',
8259
+ {enable: true}
8260
+ );
8261
+
8262
+ assert.calledWith(
8263
+ TriggerProxy.trigger,
8264
+ meeting,
8265
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
8266
+ EVENT_TRIGGERS.MEETING_MANUAL_CAPTION_UPDATED
8267
+ );
8268
+ });
8103
8269
  });
8104
8270
 
8105
8271
  describe('#setUpLocusUrlListener', () => {