@webex/plugin-meetings 3.8.1 → 3.9.0-next.10

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 (294) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +32 -3
  5. package/dist/constants.js.map +1 -1
  6. package/dist/controls-options-manager/enums.js +1 -0
  7. package/dist/controls-options-manager/enums.js.map +1 -1
  8. package/dist/controls-options-manager/types.js.map +1 -1
  9. package/dist/controls-options-manager/util.js +26 -0
  10. package/dist/controls-options-manager/util.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/locus-info/controlsUtils.js +11 -3
  14. package/dist/locus-info/controlsUtils.js.map +1 -1
  15. package/dist/locus-info/index.js +84 -91
  16. package/dist/locus-info/index.js.map +1 -1
  17. package/dist/locus-info/parser.js +4 -1
  18. package/dist/locus-info/parser.js.map +1 -1
  19. package/dist/media/index.js +2 -2
  20. package/dist/media/index.js.map +1 -1
  21. package/dist/meeting/brbState.js +17 -14
  22. package/dist/meeting/brbState.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +11 -1
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +484 -287
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/request.js +19 -0
  28. package/dist/meeting/request.js.map +1 -1
  29. package/dist/meeting/request.type.js.map +1 -1
  30. package/dist/{rtcMetrics/constants.js → meeting/type.js} +1 -5
  31. package/dist/meeting/type.js.map +1 -0
  32. package/dist/meeting/util.js +68 -2
  33. package/dist/meeting/util.js.map +1 -1
  34. package/dist/meetings/index.js +35 -33
  35. package/dist/meetings/index.js.map +1 -1
  36. package/dist/members/index.js +14 -11
  37. package/dist/members/index.js.map +1 -1
  38. package/dist/members/request.js +3 -3
  39. package/dist/members/request.js.map +1 -1
  40. package/dist/members/util.js +25 -8
  41. package/dist/members/util.js.map +1 -1
  42. package/dist/metrics/constants.js +1 -0
  43. package/dist/metrics/constants.js.map +1 -1
  44. package/dist/multistream/mediaRequestManager.js +1 -1
  45. package/dist/multistream/mediaRequestManager.js.map +1 -1
  46. package/dist/multistream/remoteMedia.js +34 -5
  47. package/dist/multistream/remoteMedia.js.map +1 -1
  48. package/dist/multistream/remoteMediaGroup.js +42 -2
  49. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  50. package/dist/multistream/sendSlotManager.js +32 -2
  51. package/dist/multistream/sendSlotManager.js.map +1 -1
  52. package/dist/reachability/index.js +8 -13
  53. package/dist/reachability/index.js.map +1 -1
  54. package/dist/types/constants.d.ts +28 -0
  55. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  56. package/dist/types/controls-options-manager/types.d.ts +4 -1
  57. package/dist/types/locus-info/index.d.ts +2 -9
  58. package/dist/types/meeting/brbState.d.ts +0 -1
  59. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  60. package/dist/types/meeting/index.d.ts +47 -19
  61. package/dist/types/meeting/request.d.ts +9 -1
  62. package/dist/types/meeting/request.type.d.ts +74 -0
  63. package/dist/types/meeting/type.d.ts +9 -0
  64. package/dist/types/meeting/util.d.ts +3 -0
  65. package/dist/types/members/index.d.ts +12 -8
  66. package/dist/types/members/request.d.ts +1 -1
  67. package/dist/types/members/util.d.ts +13 -6
  68. package/dist/types/metrics/constants.d.ts +1 -0
  69. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  70. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  71. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  72. package/dist/types/reachability/index.d.ts +2 -2
  73. package/dist/webinar/index.js +1 -1
  74. package/package.json +24 -25
  75. package/src/constants.ts +32 -2
  76. package/src/controls-options-manager/enums.ts +1 -0
  77. package/src/controls-options-manager/types.ts +6 -1
  78. package/src/controls-options-manager/util.ts +31 -0
  79. package/src/locus-info/controlsUtils.ts +15 -0
  80. package/src/locus-info/index.ts +103 -92
  81. package/src/locus-info/parser.ts +5 -1
  82. package/src/media/index.ts +2 -2
  83. package/src/meeting/brbState.ts +13 -9
  84. package/src/meeting/in-meeting-actions.ts +21 -0
  85. package/src/meeting/index.ts +278 -73
  86. package/src/meeting/request.ts +16 -0
  87. package/src/meeting/request.type.ts +64 -0
  88. package/src/meeting/type.ts +9 -0
  89. package/src/meeting/util.ts +73 -2
  90. package/src/meetings/index.ts +3 -2
  91. package/src/members/index.ts +22 -12
  92. package/src/members/request.ts +2 -2
  93. package/src/members/util.ts +34 -6
  94. package/src/metrics/constants.ts +1 -0
  95. package/src/multistream/mediaRequestManager.ts +7 -7
  96. package/src/multistream/remoteMedia.ts +34 -4
  97. package/src/multistream/remoteMediaGroup.ts +37 -2
  98. package/src/multistream/sendSlotManager.ts +34 -2
  99. package/src/reachability/index.ts +8 -16
  100. package/test/unit/spec/controls-options-manager/util.js +58 -0
  101. package/test/unit/spec/locus-info/controlsUtils.js +52 -0
  102. package/test/unit/spec/locus-info/index.js +247 -89
  103. package/test/unit/spec/locus-info/parser.js +3 -2
  104. package/test/unit/spec/media/index.ts +107 -0
  105. package/test/unit/spec/meeting/brbState.ts +23 -4
  106. package/test/unit/spec/meeting/in-meeting-actions.ts +10 -0
  107. package/test/unit/spec/meeting/index.js +976 -91
  108. package/test/unit/spec/meeting/request.js +71 -0
  109. package/test/unit/spec/meeting/utils.js +122 -1
  110. package/test/unit/spec/meetings/index.js +2 -0
  111. package/test/unit/spec/members/index.js +98 -11
  112. package/test/unit/spec/members/request.js +57 -2
  113. package/test/unit/spec/members/utils.js +139 -17
  114. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  115. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  116. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  117. package/test/unit/spec/reachability/index.ts +160 -9
  118. package/dist/annotation/annotation.types.d.ts +0 -42
  119. package/dist/annotation/constants.d.ts +0 -31
  120. package/dist/annotation/index.d.ts +0 -117
  121. package/dist/breakouts/breakout.d.ts +0 -8
  122. package/dist/breakouts/collection.d.ts +0 -5
  123. package/dist/breakouts/edit-lock-error.d.ts +0 -15
  124. package/dist/breakouts/events.d.ts +0 -8
  125. package/dist/breakouts/index.d.ts +0 -5
  126. package/dist/breakouts/request.d.ts +0 -22
  127. package/dist/breakouts/utils.d.ts +0 -15
  128. package/dist/common/browser-detection.d.ts +0 -9
  129. package/dist/common/collection.d.ts +0 -48
  130. package/dist/common/config.d.ts +0 -2
  131. package/dist/common/errors/captcha-error.d.ts +0 -15
  132. package/dist/common/errors/intent-to-join.d.ts +0 -16
  133. package/dist/common/errors/join-meeting.d.ts +0 -17
  134. package/dist/common/errors/media.d.ts +0 -15
  135. package/dist/common/errors/no-meeting-info.d.ts +0 -14
  136. package/dist/common/errors/parameter.d.ts +0 -15
  137. package/dist/common/errors/password-error.d.ts +0 -15
  138. package/dist/common/errors/permission.d.ts +0 -14
  139. package/dist/common/errors/reclaim-host-role-error.d.ts +0 -60
  140. package/dist/common/errors/reclaim-host-role-error.js +0 -158
  141. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  142. package/dist/common/errors/reclaim-host-role-errors.d.ts +0 -60
  143. package/dist/common/errors/reconnection-in-progress.d.ts +0 -9
  144. package/dist/common/errors/reconnection-in-progress.js +0 -35
  145. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  146. package/dist/common/errors/reconnection.d.ts +0 -15
  147. package/dist/common/errors/stats.d.ts +0 -15
  148. package/dist/common/errors/webex-errors.d.ts +0 -81
  149. package/dist/common/errors/webex-meetings-error.d.ts +0 -20
  150. package/dist/common/events/events-scope.d.ts +0 -17
  151. package/dist/common/events/events.d.ts +0 -12
  152. package/dist/common/events/trigger-proxy.d.ts +0 -2
  153. package/dist/common/events/util.d.ts +0 -2
  154. package/dist/common/logs/logger-config.d.ts +0 -2
  155. package/dist/common/logs/logger-proxy.d.ts +0 -2
  156. package/dist/common/logs/request.d.ts +0 -34
  157. package/dist/common/queue.d.ts +0 -32
  158. package/dist/config.d.ts +0 -73
  159. package/dist/constants.d.ts +0 -952
  160. package/dist/controls-options-manager/constants.d.ts +0 -4
  161. package/dist/controls-options-manager/enums.d.ts +0 -5
  162. package/dist/controls-options-manager/index.d.ts +0 -120
  163. package/dist/controls-options-manager/types.d.ts +0 -43
  164. package/dist/controls-options-manager/util.d.ts +0 -7
  165. package/dist/index.d.ts +0 -4
  166. package/dist/interceptors/index.d.ts +0 -2
  167. package/dist/interceptors/locusRetry.d.ts +0 -27
  168. package/dist/interpretation/collection.d.ts +0 -5
  169. package/dist/interpretation/index.d.ts +0 -5
  170. package/dist/interpretation/siLanguage.d.ts +0 -5
  171. package/dist/locus-info/controlsUtils.d.ts +0 -2
  172. package/dist/locus-info/embeddedAppsUtils.d.ts +0 -2
  173. package/dist/locus-info/fullState.d.ts +0 -2
  174. package/dist/locus-info/hostUtils.d.ts +0 -2
  175. package/dist/locus-info/index.d.ts +0 -269
  176. package/dist/locus-info/infoUtils.d.ts +0 -2
  177. package/dist/locus-info/mediaSharesUtils.d.ts +0 -2
  178. package/dist/locus-info/parser.d.ts +0 -212
  179. package/dist/locus-info/selfUtils.d.ts +0 -2
  180. package/dist/media/index.d.ts +0 -32
  181. package/dist/media/properties.d.ts +0 -108
  182. package/dist/media/util.d.ts +0 -2
  183. package/dist/mediaQualityMetrics/config.d.ts +0 -233
  184. package/dist/mediaQualityMetrics/config.js +0 -513
  185. package/dist/mediaQualityMetrics/config.js.map +0 -1
  186. package/dist/meeting/effectsState.d.ts +0 -42
  187. package/dist/meeting/effectsState.js +0 -260
  188. package/dist/meeting/effectsState.js.map +0 -1
  189. package/dist/meeting/in-meeting-actions.d.ts +0 -79
  190. package/dist/meeting/index.d.ts +0 -1622
  191. package/dist/meeting/locusMediaRequest.d.ts +0 -74
  192. package/dist/meeting/muteState.d.ts +0 -116
  193. package/dist/meeting/request.d.ts +0 -257
  194. package/dist/meeting/request.type.d.ts +0 -11
  195. package/dist/meeting/state.d.ts +0 -9
  196. package/dist/meeting/util.d.ts +0 -2
  197. package/dist/meeting/voicea-meeting.d.ts +0 -16
  198. package/dist/meeting-info/collection.d.ts +0 -20
  199. package/dist/meeting-info/index.d.ts +0 -57
  200. package/dist/meeting-info/meeting-info-v2.d.ts +0 -93
  201. package/dist/meeting-info/request.d.ts +0 -22
  202. package/dist/meeting-info/util.d.ts +0 -2
  203. package/dist/meeting-info/utilv2.d.ts +0 -2
  204. package/dist/meetings/collection.d.ts +0 -23
  205. package/dist/meetings/index.d.ts +0 -296
  206. package/dist/meetings/meetings.types.d.ts +0 -4
  207. package/dist/meetings/request.d.ts +0 -27
  208. package/dist/meetings/util.d.ts +0 -18
  209. package/dist/member/index.d.ts +0 -148
  210. package/dist/member/member.types.d.ts +0 -11
  211. package/dist/member/member.types.js +0 -18
  212. package/dist/member/member.types.js.map +0 -1
  213. package/dist/member/types.d.ts +0 -32
  214. package/dist/member/util.d.ts +0 -2
  215. package/dist/members/collection.d.ts +0 -24
  216. package/dist/members/index.d.ts +0 -308
  217. package/dist/members/request.d.ts +0 -58
  218. package/dist/members/types.d.ts +0 -25
  219. package/dist/members/util.d.ts +0 -2
  220. package/dist/metrics/config.d.ts +0 -169
  221. package/dist/metrics/config.js +0 -289
  222. package/dist/metrics/config.js.map +0 -1
  223. package/dist/metrics/constants.d.ts +0 -59
  224. package/dist/metrics/index.d.ts +0 -152
  225. package/dist/multistream/mediaRequestManager.d.ts +0 -119
  226. package/dist/multistream/receiveSlot.d.ts +0 -68
  227. package/dist/multistream/receiveSlotManager.d.ts +0 -56
  228. package/dist/multistream/remoteMedia.d.ts +0 -72
  229. package/dist/multistream/remoteMediaGroup.d.ts +0 -49
  230. package/dist/multistream/remoteMediaManager.d.ts +0 -300
  231. package/dist/multistream/sendSlotManager.d.ts +0 -69
  232. package/dist/networkQualityMonitor/index.d.ts +0 -70
  233. package/dist/networkQualityMonitor/index.js +0 -226
  234. package/dist/networkQualityMonitor/index.js.map +0 -1
  235. package/dist/peer-connection-manager/index.d.ts +0 -6
  236. package/dist/peer-connection-manager/index.js +0 -671
  237. package/dist/peer-connection-manager/index.js.map +0 -1
  238. package/dist/peer-connection-manager/util.d.ts +0 -6
  239. package/dist/peer-connection-manager/util.js +0 -110
  240. package/dist/peer-connection-manager/util.js.map +0 -1
  241. package/dist/personal-meeting-room/index.d.ts +0 -47
  242. package/dist/personal-meeting-room/request.d.ts +0 -14
  243. package/dist/personal-meeting-room/util.d.ts +0 -2
  244. package/dist/reachability/clusterReachability.d.ts +0 -109
  245. package/dist/reachability/index.d.ts +0 -139
  246. package/dist/reachability/request.d.ts +0 -35
  247. package/dist/reachability/util.d.ts +0 -8
  248. package/dist/reactions/constants.d.ts +0 -3
  249. package/dist/reactions/reactions.d.ts +0 -4
  250. package/dist/reactions/reactions.type.d.ts +0 -32
  251. package/dist/reconnection-manager/index.d.ts +0 -112
  252. package/dist/recording-controller/enums.d.ts +0 -7
  253. package/dist/recording-controller/index.d.ts +0 -193
  254. package/dist/recording-controller/util.d.ts +0 -13
  255. package/dist/roap/collection.d.ts +0 -10
  256. package/dist/roap/collection.js +0 -63
  257. package/dist/roap/collection.js.map +0 -1
  258. package/dist/roap/handler.d.ts +0 -47
  259. package/dist/roap/handler.js +0 -279
  260. package/dist/roap/handler.js.map +0 -1
  261. package/dist/roap/index.d.ts +0 -116
  262. package/dist/roap/request.d.ts +0 -35
  263. package/dist/roap/state.d.ts +0 -9
  264. package/dist/roap/state.js +0 -127
  265. package/dist/roap/state.js.map +0 -1
  266. package/dist/roap/turnDiscovery.d.ts +0 -81
  267. package/dist/roap/util.d.ts +0 -2
  268. package/dist/roap/util.js +0 -76
  269. package/dist/roap/util.js.map +0 -1
  270. package/dist/rtcMetrics/constants.d.ts +0 -4
  271. package/dist/rtcMetrics/constants.js.map +0 -1
  272. package/dist/rtcMetrics/index.d.ts +0 -61
  273. package/dist/rtcMetrics/index.js +0 -197
  274. package/dist/rtcMetrics/index.js.map +0 -1
  275. package/dist/statsAnalyzer/global.d.ts +0 -118
  276. package/dist/statsAnalyzer/global.js +0 -127
  277. package/dist/statsAnalyzer/global.js.map +0 -1
  278. package/dist/statsAnalyzer/index.d.ts +0 -193
  279. package/dist/statsAnalyzer/index.js +0 -1019
  280. package/dist/statsAnalyzer/index.js.map +0 -1
  281. package/dist/statsAnalyzer/mqaUtil.d.ts +0 -22
  282. package/dist/statsAnalyzer/mqaUtil.js +0 -181
  283. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  284. package/dist/transcription/index.d.ts +0 -64
  285. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  286. package/dist/types/mediaQualityMetrics/config.d.ts +0 -241
  287. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  288. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  289. package/dist/types/rtcMetrics/index.d.ts +0 -71
  290. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  291. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  292. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  293. package/dist/webinar/collection.d.ts +0 -16
  294. package/dist/webinar/index.d.ts +0 -5
@@ -306,6 +306,20 @@ describe('plugin-meetings', () => {
306
306
  );
307
307
  });
308
308
 
309
+ it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
310
+ locusInfo.controls = {};
311
+ locusInfo.emitScoped = sinon.stub();
312
+ newControls.pollingQAControl = { enabled: true };
313
+ locusInfo.updateControls(newControls);
314
+
315
+ assert.calledWith(
316
+ locusInfo.emitScoped,
317
+ {file: 'locus-info', function: 'updateControls'},
318
+ LOCUSINFO.EVENTS.CONTROLS_POLLING_QA_CHANGED,
319
+ {state: newControls.pollingQAControl}
320
+ );
321
+ });
322
+
309
323
  it('should keep the recording state to `IDLE`', () => {
310
324
  locusInfo.controls = {
311
325
  record: {
@@ -557,6 +571,34 @@ describe('plugin-meetings', () => {
557
571
  );
558
572
  });
559
573
 
574
+ it('should update the transcribe spoken language', () => {
575
+ locusInfo.emitScoped = sinon.stub();
576
+ locusInfo.controls = {
577
+ transcribe: {
578
+ transcribing: false,
579
+ caption: true,
580
+ spokenLanguage: 'en-US',
581
+ },
582
+ };
583
+ newControls.transcribe.transcribing = false;
584
+ newControls.transcribe.caption = true;
585
+ newControls.transcribe.spokenLanguage = 'fr';
586
+
587
+ locusInfo.updateControls(newControls);
588
+
589
+ assert.calledWith(
590
+ locusInfo.emitScoped,
591
+ {
592
+ file: 'locus-info',
593
+ function: 'updateControls',
594
+ },
595
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
596
+ {
597
+ spokenLanguage: 'fr',
598
+ }
599
+ );
600
+ });
601
+
560
602
  it('should update the manual caption state', () => {
561
603
  locusInfo.emitScoped = sinon.stub();
562
604
  locusInfo.controls = {
@@ -792,39 +834,6 @@ describe('plugin-meetings', () => {
792
834
  );
793
835
  });
794
836
 
795
- it('should update the deltaParticipants object', () => {
796
- const prev = locusInfo.deltaParticipants;
797
-
798
- locusInfo.updateParticipantDeltas(newParticipants);
799
-
800
- assert.notEqual(locusInfo.deltaParticipants, prev);
801
- });
802
-
803
- it('should update the delta property on all changed states', () => {
804
- locusInfo.updateParticipantDeltas(newParticipants);
805
-
806
- const [exampleParticipant] = locusInfo.deltaParticipants;
807
-
808
- assert.isTrue(exampleParticipant.delta.audioStatus);
809
- assert.isTrue(exampleParticipant.delta.videoSlidesStatus);
810
- assert.isTrue(exampleParticipant.delta.videoStatus);
811
- });
812
-
813
- it('should include the person details of the changed participant', () => {
814
- locusInfo.updateParticipantDeltas(newParticipants);
815
-
816
- const [exampleParticipant] = locusInfo.deltaParticipants;
817
-
818
- assert.equal(exampleParticipant.person, newParticipants[0].person);
819
- });
820
-
821
- it('should clear deltaParticipants when no changes occured', () => {
822
- locusInfo.participants = [...newParticipants];
823
-
824
- locusInfo.updateParticipantDeltas(locusInfo.participants);
825
-
826
- assert.isTrue(locusInfo.deltaParticipants.length === 0);
827
- });
828
837
 
829
838
  it('should call with participant display name', () => {
830
839
  const failureParticipant = [
@@ -1632,6 +1641,28 @@ describe('plugin-meetings', () => {
1632
1641
  );
1633
1642
  });
1634
1643
 
1644
+ it('should trigger MEETING_INFO_UPDATED even if the roles array is empty', () => {
1645
+ const initialInfo = cloneDeep(meetingInfo);
1646
+
1647
+ const updateSelf = cloneDeep(self);
1648
+ updateSelf.controls.role.roles = [];
1649
+
1650
+ locusInfo.emitScoped = sinon.stub();
1651
+ locusInfo.updateMeetingInfo(initialInfo, updateSelf);
1652
+
1653
+ assert.calledWith(
1654
+ locusInfo.emitScoped,
1655
+ {
1656
+ file: 'locus-info',
1657
+ function: 'updateMeetingInfo',
1658
+ },
1659
+ LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1660
+ {
1661
+ isInitializing: !self,
1662
+ }
1663
+ );
1664
+ });
1665
+
1635
1666
  const checkMeetingInfoUpdatedCalled = (expected, payload) => {
1636
1667
  const expectedArgs = [
1637
1668
  locusInfo.emitScoped,
@@ -2066,6 +2097,38 @@ describe('plugin-meetings', () => {
2066
2097
  assert.isFunction(locusParser.onDeltaAction);
2067
2098
  });
2068
2099
 
2100
+ it("#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo", () => {
2101
+ const callOrder = [];
2102
+ sinon.stub(locusInfo, "updateControls");
2103
+ sinon.stub(locusInfo, "updateConversationUrl");
2104
+ sinon.stub(locusInfo, "updateCreated");
2105
+ sinon.stub(locusInfo, "updateFullState");
2106
+ sinon.stub(locusInfo, "updateHostInfo");
2107
+ sinon.stub(locusInfo, "updateMeetingInfo").callsFake(() => {
2108
+ callOrder.push("updateMeetingInfo");
2109
+ });
2110
+ sinon.stub(locusInfo, "updateMediaShares");
2111
+ sinon.stub(locusInfo, "updateParticipantsUrl");
2112
+ sinon.stub(locusInfo, "updateReplace");
2113
+ sinon.stub(locusInfo, "updateSelf");
2114
+ sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
2115
+ callOrder.push("updateLocusUrl");
2116
+ });
2117
+ sinon.stub(locusInfo, "updateAclUrl");
2118
+ sinon.stub(locusInfo, "updateBasequence");
2119
+ sinon.stub(locusInfo, "updateSequence");
2120
+ sinon.stub(locusInfo, "updateMemberShip");
2121
+ sinon.stub(locusInfo, "updateIdentifiers");
2122
+ sinon.stub(locusInfo, "updateEmbeddedApps");
2123
+ sinon.stub(locusInfo, "updateResources");
2124
+ sinon.stub(locusInfo, "compareAndUpdate");
2125
+
2126
+ locusInfo.updateLocusInfo(locus);
2127
+
2128
+ // Ensure updateLocusUrl is called before updateMeetingInfo if both are called
2129
+ assert.deepEqual(callOrder, ['updateLocusUrl', 'updateMeetingInfo']);
2130
+ });
2131
+
2069
2132
  it('#updateLocusInfo ignores breakout LEFT message', () => {
2070
2133
  const newLocus = {
2071
2134
  self: {
@@ -2117,10 +2180,11 @@ describe('plugin-meetings', () => {
2117
2180
  assert.notCalled(locusInfo.compareAndUpdate);
2118
2181
  });
2119
2182
 
2183
+
2184
+
2120
2185
  it('onFullLocus() updates the working-copy of locus parser', () => {
2121
2186
  const eventType = 'fakeEvent';
2122
2187
 
2123
- sandbox.stub(locusInfo, 'updateParticipantDeltas');
2124
2188
  sandbox.stub(locusInfo, 'updateLocusInfo');
2125
2189
  sandbox.stub(locusInfo, 'updateParticipants');
2126
2190
  sandbox.stub(locusInfo, 'isMeetingActive');
@@ -2140,7 +2204,6 @@ describe('plugin-meetings', () => {
2140
2204
  const oldWorkingCopy = locusParser.workingCopy;
2141
2205
 
2142
2206
  const spies = [
2143
- sandbox.stub(locusInfo, 'updateParticipantDeltas'),
2144
2207
  sandbox.stub(locusInfo, 'updateLocusInfo'),
2145
2208
  sandbox.stub(locusInfo, 'updateParticipants'),
2146
2209
  sandbox.stub(locusInfo, 'isMeetingActive'),
@@ -2215,7 +2278,7 @@ describe('plugin-meetings', () => {
2215
2278
 
2216
2279
  it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
2217
2280
  const {DESYNC} = LocusDeltaParser.loci;
2218
- const fakeDeltaLocus = {id: 'fake delta locus'};
2281
+ const fakeDeltaLocus = {baseSequence: {}, id: 'fake delta locus'};
2219
2282
  const meeting = {
2220
2283
  meetingRequest: {
2221
2284
  getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
@@ -2306,23 +2369,23 @@ describe('plugin-meetings', () => {
2306
2369
 
2307
2370
  it('applyLocusDeltaData handles LOCUS_URL_CHANGED action correctly', () => {
2308
2371
  const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
2309
- const fakeDeltaLocus = {id: 'fake delta locus'};
2372
+ const fakeFullLocus = {
2373
+ url: 'new full loci url',
2374
+ };
2310
2375
  const meeting = {
2311
2376
  meetingRequest: {
2312
- getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
2377
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocus}),
2313
2378
  },
2314
2379
  locusInfo: {
2315
2380
  handleLocusDelta: sandbox.stub(),
2316
2381
  },
2317
- locusUrl: 'current locus url',
2382
+ locusUrl: 'current BO session locus url',
2318
2383
  };
2319
2384
 
2320
- locusInfo.locusParser.workingCopy = {
2321
- syncUrl: 'current sync url',
2322
- };
2385
+ locusInfo.locusParser.workingCopy = null;
2323
2386
 
2324
2387
  locusInfo.applyLocusDeltaData(LOCUS_URL_CHANGED, fakeLocus, meeting);
2325
- assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'current sync url'});
2388
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: fakeLocus.url});
2326
2389
  });
2327
2390
 
2328
2391
  describe('edge cases for sync failing', () => {
@@ -2350,25 +2413,22 @@ describe('plugin-meetings', () => {
2350
2413
  };
2351
2414
  });
2352
2415
 
2353
- it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
2416
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', async () => {
2354
2417
  meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
2355
2418
 
2356
2419
  locusInfo.locusParser.workingCopy = {}; // no syncUrl
2357
2420
 
2358
- // Since we have a promise inside a function we want to test that's not returned,
2359
- // we will wait and stub it's last function to resolve this waiting promise.
2360
- return new Promise((resolve) => {
2361
- webex.meetings.destroy.callsFake(() => resolve());
2362
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2363
- }).then(() => {
2364
- assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
2421
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2365
2422
 
2366
- assert.notCalled(meeting.locusInfo.handleLocusDelta);
2367
- assert.notCalled(meeting.locusInfo.onFullLocus);
2368
- assert.notCalled(locusInfo.locusParser.resume);
2423
+ await testUtils.flushPromises();
2369
2424
 
2370
- assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2371
- });
2425
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
2426
+
2427
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2428
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2429
+ assert.notCalled(locusInfo.locusParser.resume);
2430
+
2431
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2372
2432
  });
2373
2433
 
2374
2434
  it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
@@ -2405,39 +2465,62 @@ describe('plugin-meetings', () => {
2405
2465
  });
2406
2466
  });
2407
2467
 
2408
- it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', () => {
2468
+ it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails with 403, it does not do a full locus sync', async () => {
2469
+ const fake403Error = new Error('fake error');
2470
+ fake403Error.statusCode = 403;
2471
+
2472
+ meeting.meetingRequest.getLocusDTO.onCall(0).rejects(fake403Error);
2473
+
2474
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2475
+
2476
+ await testUtils.flushPromises();
2477
+
2478
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'deltaSyncUrl'});
2479
+
2480
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2481
+ correlationId: meeting.correlationId,
2482
+ url: 'deltaSyncUrl',
2483
+ reason: 'fake error',
2484
+ errorName: 'Error',
2485
+ stack: sinon.match.any,
2486
+ code: sinon.match.any,
2487
+ });
2488
+
2489
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2490
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2491
+ assert.notCalled(locusInfo.locusParser.resume);
2492
+ });
2493
+
2494
+ it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', async () => {
2409
2495
  meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
2410
2496
 
2411
- // Since we have a promise inside a function we want to test that's not returned,
2412
- // we will wait and stub it's last function to resolve this waiting promise.
2413
- return new Promise((resolve) => {
2414
- webex.meetings.destroy.callsFake(() => resolve());
2415
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2416
- }).then(() => {
2417
- assert.calledTwice(meeting.meetingRequest.getLocusDTO);
2497
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2418
2498
 
2419
- assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
2420
- {url: 'deltaSyncUrl'},
2421
- ]);
2422
- assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
2423
- {url: 'fullSyncUrl'},
2424
- ]);
2499
+ await testUtils.flushPromises();
2425
2500
 
2426
- assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2427
- correlationId: meeting.correlationId,
2428
- url: 'deltaSyncUrl',
2429
- reason: 'fake error',
2430
- errorName: 'Error',
2431
- stack: sinon.match.any,
2432
- code: sinon.match.any,
2433
- });
2501
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
2434
2502
 
2435
- assert.notCalled(meeting.locusInfo.handleLocusDelta);
2436
- assert.notCalled(meeting.locusInfo.onFullLocus);
2437
- assert.notCalled(locusInfo.locusParser.resume);
2503
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
2504
+ {url: 'deltaSyncUrl'},
2505
+ ]);
2506
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
2507
+ {url: 'fullSyncUrl'},
2508
+ ]);
2438
2509
 
2439
- assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2510
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2511
+ correlationId: meeting.correlationId,
2512
+ url: 'deltaSyncUrl',
2513
+ reason: 'fake error',
2514
+ errorName: 'Error',
2515
+ stack: sinon.match.any,
2516
+ code: sinon.match.any,
2440
2517
  });
2518
+
2519
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2520
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2521
+ assert.notCalled(locusInfo.locusParser.resume);
2522
+
2523
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2441
2524
  });
2442
2525
  });
2443
2526
 
@@ -2467,9 +2550,7 @@ describe('plugin-meetings', () => {
2467
2550
  });
2468
2551
 
2469
2552
  it('onDeltaLocus merges delta participants with existing participants', () => {
2470
- const FAKE_DELTA_PARTICIPANTS = [
2471
- {id: '1111'}, {id: '2222'}
2472
- ]
2553
+ const FAKE_DELTA_PARTICIPANTS = [{id: '1111'}, {id: '2222'}];
2473
2554
  fakeLocus.participants = FAKE_DELTA_PARTICIPANTS;
2474
2555
 
2475
2556
  sinon.spy(locusInfo, 'mergeParticipants');
@@ -2477,9 +2558,87 @@ describe('plugin-meetings', () => {
2477
2558
  const existingParticipants = locusInfo.participants;
2478
2559
 
2479
2560
  locusInfo.onDeltaLocus(fakeLocus);
2480
- assert.calledOnceWithExactly(locusInfo.mergeParticipants, existingParticipants, FAKE_DELTA_PARTICIPANTS);
2561
+ assert.calledOnceWithExactly(
2562
+ locusInfo.mergeParticipants,
2563
+ existingParticipants,
2564
+ FAKE_DELTA_PARTICIPANTS
2565
+ );
2481
2566
  assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, false);
2482
2567
  });
2568
+
2569
+ [true, false].forEach((isDelta) =>
2570
+ it(`applyLocusDeltaData - handles empty ${
2571
+ isDelta ? 'delta' : 'full'
2572
+ } DTO in response`, async () => {
2573
+ const {DESYNC} = LocusDeltaParser.loci;
2574
+ const fakeFullLocusDto = {};
2575
+ const meeting = {
2576
+ meetingRequest: {
2577
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
2578
+ },
2579
+ locusInfo: {
2580
+ onFullLocus: sandbox.stub(),
2581
+ handleLocusDelta: sandbox.stub(),
2582
+ },
2583
+ locusUrl: 'fake locus FULL url',
2584
+ };
2585
+
2586
+ sinon.stub(locusInfo.locusParser, 'resume').resolves();
2587
+
2588
+ if (isDelta) {
2589
+ locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
2590
+ } else {
2591
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl (to trigger FULL DTO request)
2592
+ }
2593
+
2594
+ await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2595
+
2596
+ await testUtils.flushPromises();
2597
+
2598
+ if (isDelta) {
2599
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2600
+ url: 'fake locus DELTA url',
2601
+ });
2602
+ } else {
2603
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2604
+ url: 'fake locus FULL url',
2605
+ });
2606
+ }
2607
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2608
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2609
+ assert.calledOnce(locusInfo.locusParser.resume);
2610
+ })
2611
+ );
2612
+
2613
+ it(`applyLocusDeltaData - handles the case when we get FULL DTO when we asked for DELTA DTO`, async () => {
2614
+ const {DESYNC} = LocusDeltaParser.loci;
2615
+ const fakeFullLocusDto = {someStuff: 'data'}; // non-empty DTO, without baseSequence
2616
+ const meeting = {
2617
+ meetingRequest: {
2618
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
2619
+ },
2620
+ locusInfo: {
2621
+ onFullLocus: sandbox.stub(),
2622
+ handleLocusDelta: sandbox.stub(),
2623
+ },
2624
+ locusUrl: 'fake locus FULL url',
2625
+ };
2626
+
2627
+ sinon.stub(locusInfo.locusParser, 'resume').resolves();
2628
+
2629
+ locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
2630
+
2631
+ await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2632
+
2633
+ await testUtils.flushPromises();
2634
+
2635
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2636
+ url: 'fake locus DELTA url',
2637
+ });
2638
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2639
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
2640
+ assert.calledOnce(locusInfo.locusParser.resume);
2641
+ });
2483
2642
  });
2484
2643
 
2485
2644
  describe('#updateLocusCache', () => {
@@ -2892,10 +3051,9 @@ describe('plugin-meetings', () => {
2892
3051
  beforeEach(() => {
2893
3052
  clock = sinon.useFakeTimers();
2894
3053
 
2895
- sinon.stub(locusInfo, 'updateParticipantDeltas');
2896
3054
  sinon.stub(locusInfo, 'updateParticipants');
2897
- sinon.stub(locusInfo, 'isMeetingActive'),
2898
- sinon.stub(locusInfo, 'handleOneOnOneEvent'),
3055
+ sinon.stub(locusInfo, 'isMeetingActive');
3056
+ sinon.stub(locusInfo, 'handleOneOnOneEvent');
2899
3057
  (updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
2900
3058
  syncRequestStub = sinon.stub().resolves({body: {}});
2901
3059
 
@@ -253,7 +253,8 @@ describe('locus-info/parser', () => {
253
253
  });
254
254
 
255
255
  it('replaces current loci when the locus URL changes and incoming sequence is later, even when baseSequence doesn\'t match', () => {
256
- const {USE_INCOMING} = LocusDeltaParser.loci;
256
+ const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
257
+ sandbox.stub(LocusDeltaParser, 'compare').returns(LOCUS_URL_CHANGED);
257
258
 
258
259
  parser.queue.dequeue = sandbox.stub().returns(NEW_LOCI);
259
260
  parser.onDeltaAction = sandbox.stub();
@@ -262,7 +263,7 @@ describe('locus-info/parser', () => {
262
263
 
263
264
  parser.processDeltaEvent();
264
265
 
265
- assert.equal(parser.workingCopy, NEW_LOCI);
266
+ assert.equal(parser.workingCopy, null);
266
267
  });
267
268
 
268
269
  it('does not replace current loci when the locus URL changes but incoming sequence is not later', () => {
@@ -137,6 +137,113 @@ describe('createMediaConnection', () => {
137
137
  );
138
138
  });
139
139
 
140
+ it('should set direction to sendonly for both audio and video when only send flags are true', () => {
141
+ const roapMediaConnectionConstructorStub = sinon
142
+ .stub(InternalMediaCoreModule, 'RoapMediaConnection')
143
+ .returns(fakeRoapMediaConnection);
144
+
145
+ StaticConfig.set({bandwidth: {audio: 123, video: 456, startBitrate: 999}});
146
+
147
+ const ENABLE_EXTMAP = false;
148
+ const ENABLE_RTX = true;
149
+
150
+ Media.createMediaConnection(false, 'sendonly-debug-id', 'meetingId', {
151
+ mediaProperties: {
152
+ mediaDirection: {
153
+ sendAudio: true,
154
+ receiveAudio: false,
155
+ sendVideo: true,
156
+ receiveVideo: false,
157
+ sendShare: false,
158
+ receiveShare: false,
159
+ },
160
+ audioStream: fakeAudioStream,
161
+ videoStream: fakeVideoStream,
162
+ shareVideoTrack: null,
163
+ shareAudioTrack: null,
164
+ },
165
+ remoteQualityLevel: 'HIGH',
166
+ enableRtx: ENABLE_RTX,
167
+ enableExtmap: ENABLE_EXTMAP,
168
+ turnServerInfo: undefined,
169
+ iceCandidatesTimeout: undefined,
170
+ });
171
+
172
+ assert.calledWith(
173
+ roapMediaConnectionConstructorStub,
174
+ sinon.match.any,
175
+ {
176
+ localTracks: {
177
+ audio: fakeTrack,
178
+ video: fakeTrack,
179
+ screenShareVideo: undefined,
180
+ screenShareAudio: undefined,
181
+ },
182
+ direction: {
183
+ audio: 'sendonly',
184
+ video: 'sendonly',
185
+ screenShareVideo: 'inactive',
186
+ },
187
+ remoteQualityLevel: 'HIGH',
188
+ },
189
+ 'sendonly-debug-id'
190
+ );
191
+ });
192
+
193
+ it('should set direction to recvonly for both audio and video when only receive flags are true', () => {
194
+ const roapMediaConnectionConstructorStub = sinon
195
+ .stub(InternalMediaCoreModule, 'RoapMediaConnection')
196
+ .returns(fakeRoapMediaConnection);
197
+
198
+ StaticConfig.set({bandwidth: {audio: 123, video: 456, startBitrate: 999}});
199
+
200
+ const ENABLE_EXTMAP = true;
201
+ const ENABLE_RTX = false;
202
+
203
+ Media.createMediaConnection(false, 'recvonly-debug-id', 'meetingId', {
204
+ mediaProperties: {
205
+ mediaDirection: {
206
+ sendAudio: false,
207
+ receiveAudio: true,
208
+ sendVideo: false,
209
+ receiveVideo: true,
210
+ sendShare: false,
211
+ receiveShare: false,
212
+ },
213
+ audioStream: fakeAudioStream,
214
+ videoStream: fakeVideoStream,
215
+ shareVideoTrack: null,
216
+ shareAudioTrack: null,
217
+ },
218
+ remoteQualityLevel: 'HIGH',
219
+ enableRtx: ENABLE_RTX,
220
+ enableExtmap: ENABLE_EXTMAP,
221
+ turnServerInfo: undefined,
222
+ iceCandidatesTimeout: undefined,
223
+ });
224
+
225
+ assert.calledWith(
226
+ roapMediaConnectionConstructorStub,
227
+ sinon.match.any,
228
+ {
229
+ localTracks: {
230
+ audio: fakeTrack,
231
+ video: fakeTrack,
232
+ screenShareVideo: undefined,
233
+ screenShareAudio: undefined,
234
+ },
235
+ direction: {
236
+ audio: 'recvonly',
237
+ video: 'recvonly',
238
+ screenShareVideo: 'inactive',
239
+ },
240
+ remoteQualityLevel: 'HIGH',
241
+ },
242
+ 'recvonly-debug-id'
243
+ );
244
+ });
245
+
246
+
140
247
  it('creates a MultistreamRoapMediaConnection when multistream is enabled', () => {
141
248
  const multistreamRoapMediaConnectionConstructorStub = sinon
142
249
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
@@ -1,12 +1,14 @@
1
1
  import sinon from 'sinon';
2
- import {assert} from '@webex/test-helper-chai';
2
+ import {assert, expect} from '@webex/test-helper-chai';
3
3
 
4
4
  import testUtils from '../../../utils/testUtils';
5
5
  import {BrbState, createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
6
+ import {MediaType} from '@webex/internal-media-core';
6
7
 
7
8
  describe('plugin-meetings', () => {
8
9
  let meeting: any;
9
10
  let brbState: BrbState;
11
+ let setBrbStub: sinon.SinonStub;
10
12
 
11
13
  beforeEach(async () => {
12
14
  meeting = {
@@ -21,14 +23,20 @@ describe('plugin-meetings', () => {
21
23
  setSourceStateOverride: sinon.stub(),
22
24
  },
23
25
  meetingRequest: {
24
- setBrb: sinon.stub().resolves(),
26
+ setBrb: () => {},
25
27
  },
26
28
  };
27
29
 
30
+ setBrbStub = sinon.stub(meeting.meetingRequest, 'setBrb').resolves();
31
+
28
32
  brbState = new BrbState(meeting, false);
29
33
  await testUtils.flushPromises();
30
34
  });
31
35
 
36
+ afterEach(() => {
37
+ sinon.restore();
38
+ });
39
+
32
40
  describe('brbState library', () => {
33
41
  it('takes into account current status when instantiated', async () => {
34
42
  // create a new BrbState instance
@@ -96,12 +104,12 @@ describe('plugin-meetings', () => {
96
104
  assert.isTrue(meeting.meetingRequest.setBrb.calledOnce);
97
105
  });
98
106
 
99
- it('sets source state override when client state does not match server state', async () => {
107
+ it('updates source state override', async () => {
100
108
  brbState.enable(true, meeting.sendSlotManager);
101
109
  brbState.handleServerBrbUpdate(true);
102
110
  await testUtils.flushPromises();
103
111
 
104
- assert.isTrue(meeting.sendSlotManager.setSourceStateOverride.calledOnce);
112
+ assert.isTrue(meeting.sendSlotManager.setSourceStateOverride.called);
105
113
  });
106
114
 
107
115
  it('handles server update', async () => {
@@ -129,5 +137,16 @@ describe('plugin-meetings', () => {
129
137
  sendLocalBrbStateToServerStub.restore();
130
138
  handleServerBrbUpdateSpy.restore();
131
139
  });
140
+
141
+ it('should reject when sendLocalBrbStateToServer fails', async () => {
142
+ const error = new Error('send failed');
143
+ setBrbStub.rejects(error);
144
+
145
+ const enablePromise = brbState.enable(true, meeting.sendSlotManager);
146
+ await expect(enablePromise).to.be.rejectedWith(error);
147
+
148
+ assert.isFalse(brbState.state.syncToServerInProgress);
149
+ expect(meeting.sendSlotManager.setSourceStateOverride.called).to.be.false;
150
+ });
132
151
  });
133
152
  });