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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) 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 +69 -85
  16. package/dist/locus-info/index.js.map +1 -1
  17. package/dist/media/index.js +2 -2
  18. package/dist/media/index.js.map +1 -1
  19. package/dist/meeting/brbState.js +17 -14
  20. package/dist/meeting/brbState.js.map +1 -1
  21. package/dist/meeting/in-meeting-actions.js +11 -1
  22. package/dist/meeting/in-meeting-actions.js.map +1 -1
  23. package/dist/meeting/index.js +476 -284
  24. package/dist/meeting/index.js.map +1 -1
  25. package/dist/meeting/request.js +19 -0
  26. package/dist/meeting/request.js.map +1 -1
  27. package/dist/meeting/request.type.js.map +1 -1
  28. package/dist/{rtcMetrics/constants.js → meeting/type.js} +1 -5
  29. package/dist/meeting/type.js.map +1 -0
  30. package/dist/meeting/util.js +68 -2
  31. package/dist/meeting/util.js.map +1 -1
  32. package/dist/meetings/index.js +35 -33
  33. package/dist/meetings/index.js.map +1 -1
  34. package/dist/members/index.js +11 -9
  35. package/dist/members/index.js.map +1 -1
  36. package/dist/members/request.js +3 -3
  37. package/dist/members/request.js.map +1 -1
  38. package/dist/members/util.js +18 -6
  39. package/dist/members/util.js.map +1 -1
  40. package/dist/metrics/constants.js +1 -0
  41. package/dist/metrics/constants.js.map +1 -1
  42. package/dist/multistream/mediaRequestManager.js +1 -1
  43. package/dist/multistream/mediaRequestManager.js.map +1 -1
  44. package/dist/multistream/remoteMedia.js +34 -5
  45. package/dist/multistream/remoteMedia.js.map +1 -1
  46. package/dist/multistream/remoteMediaGroup.js +42 -2
  47. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  48. package/dist/multistream/sendSlotManager.js +32 -2
  49. package/dist/multistream/sendSlotManager.js.map +1 -1
  50. package/dist/reachability/index.js +5 -10
  51. package/dist/reachability/index.js.map +1 -1
  52. package/dist/types/constants.d.ts +28 -0
  53. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  54. package/dist/types/controls-options-manager/types.d.ts +4 -1
  55. package/dist/types/locus-info/index.d.ts +0 -9
  56. package/dist/types/meeting/brbState.d.ts +0 -1
  57. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  58. package/dist/types/meeting/index.d.ts +47 -19
  59. package/dist/types/meeting/request.d.ts +9 -1
  60. package/dist/types/meeting/request.type.d.ts +74 -0
  61. package/dist/types/meeting/type.d.ts +9 -0
  62. package/dist/types/meeting/util.d.ts +3 -0
  63. package/dist/types/members/index.d.ts +10 -7
  64. package/dist/types/members/request.d.ts +1 -1
  65. package/dist/types/members/util.d.ts +7 -3
  66. package/dist/types/metrics/constants.d.ts +1 -0
  67. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  68. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  69. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  70. package/dist/types/reachability/index.d.ts +2 -2
  71. package/dist/webinar/index.js +1 -1
  72. package/package.json +24 -25
  73. package/src/constants.ts +32 -2
  74. package/src/controls-options-manager/enums.ts +1 -0
  75. package/src/controls-options-manager/types.ts +6 -1
  76. package/src/controls-options-manager/util.ts +31 -0
  77. package/src/locus-info/controlsUtils.ts +15 -0
  78. package/src/locus-info/index.ts +89 -86
  79. package/src/media/index.ts +2 -2
  80. package/src/meeting/brbState.ts +13 -9
  81. package/src/meeting/in-meeting-actions.ts +21 -0
  82. package/src/meeting/index.ts +271 -71
  83. package/src/meeting/request.ts +16 -0
  84. package/src/meeting/request.type.ts +64 -0
  85. package/src/meeting/type.ts +9 -0
  86. package/src/meeting/util.ts +73 -2
  87. package/src/meetings/index.ts +3 -2
  88. package/src/members/index.ts +13 -10
  89. package/src/members/request.ts +2 -2
  90. package/src/members/util.ts +16 -4
  91. package/src/metrics/constants.ts +1 -0
  92. package/src/multistream/mediaRequestManager.ts +7 -7
  93. package/src/multistream/remoteMedia.ts +34 -4
  94. package/src/multistream/remoteMediaGroup.ts +37 -2
  95. package/src/multistream/sendSlotManager.ts +34 -2
  96. package/src/reachability/index.ts +5 -13
  97. package/test/unit/spec/controls-options-manager/util.js +58 -0
  98. package/test/unit/spec/locus-info/controlsUtils.js +52 -0
  99. package/test/unit/spec/locus-info/index.js +240 -82
  100. package/test/unit/spec/media/index.ts +107 -0
  101. package/test/unit/spec/meeting/brbState.ts +23 -4
  102. package/test/unit/spec/meeting/in-meeting-actions.ts +10 -0
  103. package/test/unit/spec/meeting/index.js +954 -85
  104. package/test/unit/spec/meeting/request.js +71 -0
  105. package/test/unit/spec/meeting/utils.js +122 -1
  106. package/test/unit/spec/meetings/index.js +2 -0
  107. package/test/unit/spec/members/index.js +68 -9
  108. package/test/unit/spec/members/request.js +2 -2
  109. package/test/unit/spec/members/utils.js +27 -7
  110. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  111. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  112. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  113. package/test/unit/spec/reachability/index.ts +2 -6
  114. package/dist/annotation/annotation.types.d.ts +0 -42
  115. package/dist/annotation/constants.d.ts +0 -31
  116. package/dist/annotation/index.d.ts +0 -117
  117. package/dist/breakouts/breakout.d.ts +0 -8
  118. package/dist/breakouts/collection.d.ts +0 -5
  119. package/dist/breakouts/edit-lock-error.d.ts +0 -15
  120. package/dist/breakouts/events.d.ts +0 -8
  121. package/dist/breakouts/index.d.ts +0 -5
  122. package/dist/breakouts/request.d.ts +0 -22
  123. package/dist/breakouts/utils.d.ts +0 -15
  124. package/dist/common/browser-detection.d.ts +0 -9
  125. package/dist/common/collection.d.ts +0 -48
  126. package/dist/common/config.d.ts +0 -2
  127. package/dist/common/errors/captcha-error.d.ts +0 -15
  128. package/dist/common/errors/intent-to-join.d.ts +0 -16
  129. package/dist/common/errors/join-meeting.d.ts +0 -17
  130. package/dist/common/errors/media.d.ts +0 -15
  131. package/dist/common/errors/no-meeting-info.d.ts +0 -14
  132. package/dist/common/errors/parameter.d.ts +0 -15
  133. package/dist/common/errors/password-error.d.ts +0 -15
  134. package/dist/common/errors/permission.d.ts +0 -14
  135. package/dist/common/errors/reclaim-host-role-error.d.ts +0 -60
  136. package/dist/common/errors/reclaim-host-role-error.js +0 -158
  137. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  138. package/dist/common/errors/reclaim-host-role-errors.d.ts +0 -60
  139. package/dist/common/errors/reconnection-in-progress.d.ts +0 -9
  140. package/dist/common/errors/reconnection-in-progress.js +0 -35
  141. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  142. package/dist/common/errors/reconnection.d.ts +0 -15
  143. package/dist/common/errors/stats.d.ts +0 -15
  144. package/dist/common/errors/webex-errors.d.ts +0 -81
  145. package/dist/common/errors/webex-meetings-error.d.ts +0 -20
  146. package/dist/common/events/events-scope.d.ts +0 -17
  147. package/dist/common/events/events.d.ts +0 -12
  148. package/dist/common/events/trigger-proxy.d.ts +0 -2
  149. package/dist/common/events/util.d.ts +0 -2
  150. package/dist/common/logs/logger-config.d.ts +0 -2
  151. package/dist/common/logs/logger-proxy.d.ts +0 -2
  152. package/dist/common/logs/request.d.ts +0 -34
  153. package/dist/common/queue.d.ts +0 -32
  154. package/dist/config.d.ts +0 -73
  155. package/dist/constants.d.ts +0 -952
  156. package/dist/controls-options-manager/constants.d.ts +0 -4
  157. package/dist/controls-options-manager/enums.d.ts +0 -5
  158. package/dist/controls-options-manager/index.d.ts +0 -120
  159. package/dist/controls-options-manager/types.d.ts +0 -43
  160. package/dist/controls-options-manager/util.d.ts +0 -7
  161. package/dist/index.d.ts +0 -4
  162. package/dist/interceptors/index.d.ts +0 -2
  163. package/dist/interceptors/locusRetry.d.ts +0 -27
  164. package/dist/interpretation/collection.d.ts +0 -5
  165. package/dist/interpretation/index.d.ts +0 -5
  166. package/dist/interpretation/siLanguage.d.ts +0 -5
  167. package/dist/locus-info/controlsUtils.d.ts +0 -2
  168. package/dist/locus-info/embeddedAppsUtils.d.ts +0 -2
  169. package/dist/locus-info/fullState.d.ts +0 -2
  170. package/dist/locus-info/hostUtils.d.ts +0 -2
  171. package/dist/locus-info/index.d.ts +0 -269
  172. package/dist/locus-info/infoUtils.d.ts +0 -2
  173. package/dist/locus-info/mediaSharesUtils.d.ts +0 -2
  174. package/dist/locus-info/parser.d.ts +0 -212
  175. package/dist/locus-info/selfUtils.d.ts +0 -2
  176. package/dist/media/index.d.ts +0 -32
  177. package/dist/media/properties.d.ts +0 -108
  178. package/dist/media/util.d.ts +0 -2
  179. package/dist/mediaQualityMetrics/config.d.ts +0 -233
  180. package/dist/mediaQualityMetrics/config.js +0 -513
  181. package/dist/mediaQualityMetrics/config.js.map +0 -1
  182. package/dist/meeting/effectsState.d.ts +0 -42
  183. package/dist/meeting/effectsState.js +0 -260
  184. package/dist/meeting/effectsState.js.map +0 -1
  185. package/dist/meeting/in-meeting-actions.d.ts +0 -79
  186. package/dist/meeting/index.d.ts +0 -1622
  187. package/dist/meeting/locusMediaRequest.d.ts +0 -74
  188. package/dist/meeting/muteState.d.ts +0 -116
  189. package/dist/meeting/request.d.ts +0 -257
  190. package/dist/meeting/request.type.d.ts +0 -11
  191. package/dist/meeting/state.d.ts +0 -9
  192. package/dist/meeting/util.d.ts +0 -2
  193. package/dist/meeting/voicea-meeting.d.ts +0 -16
  194. package/dist/meeting-info/collection.d.ts +0 -20
  195. package/dist/meeting-info/index.d.ts +0 -57
  196. package/dist/meeting-info/meeting-info-v2.d.ts +0 -93
  197. package/dist/meeting-info/request.d.ts +0 -22
  198. package/dist/meeting-info/util.d.ts +0 -2
  199. package/dist/meeting-info/utilv2.d.ts +0 -2
  200. package/dist/meetings/collection.d.ts +0 -23
  201. package/dist/meetings/index.d.ts +0 -296
  202. package/dist/meetings/meetings.types.d.ts +0 -4
  203. package/dist/meetings/request.d.ts +0 -27
  204. package/dist/meetings/util.d.ts +0 -18
  205. package/dist/member/index.d.ts +0 -148
  206. package/dist/member/member.types.d.ts +0 -11
  207. package/dist/member/member.types.js +0 -18
  208. package/dist/member/member.types.js.map +0 -1
  209. package/dist/member/types.d.ts +0 -32
  210. package/dist/member/util.d.ts +0 -2
  211. package/dist/members/collection.d.ts +0 -24
  212. package/dist/members/index.d.ts +0 -308
  213. package/dist/members/request.d.ts +0 -58
  214. package/dist/members/types.d.ts +0 -25
  215. package/dist/members/util.d.ts +0 -2
  216. package/dist/metrics/config.d.ts +0 -169
  217. package/dist/metrics/config.js +0 -289
  218. package/dist/metrics/config.js.map +0 -1
  219. package/dist/metrics/constants.d.ts +0 -59
  220. package/dist/metrics/index.d.ts +0 -152
  221. package/dist/multistream/mediaRequestManager.d.ts +0 -119
  222. package/dist/multistream/receiveSlot.d.ts +0 -68
  223. package/dist/multistream/receiveSlotManager.d.ts +0 -56
  224. package/dist/multistream/remoteMedia.d.ts +0 -72
  225. package/dist/multistream/remoteMediaGroup.d.ts +0 -49
  226. package/dist/multistream/remoteMediaManager.d.ts +0 -300
  227. package/dist/multistream/sendSlotManager.d.ts +0 -69
  228. package/dist/networkQualityMonitor/index.d.ts +0 -70
  229. package/dist/networkQualityMonitor/index.js +0 -226
  230. package/dist/networkQualityMonitor/index.js.map +0 -1
  231. package/dist/peer-connection-manager/index.d.ts +0 -6
  232. package/dist/peer-connection-manager/index.js +0 -671
  233. package/dist/peer-connection-manager/index.js.map +0 -1
  234. package/dist/peer-connection-manager/util.d.ts +0 -6
  235. package/dist/peer-connection-manager/util.js +0 -110
  236. package/dist/peer-connection-manager/util.js.map +0 -1
  237. package/dist/personal-meeting-room/index.d.ts +0 -47
  238. package/dist/personal-meeting-room/request.d.ts +0 -14
  239. package/dist/personal-meeting-room/util.d.ts +0 -2
  240. package/dist/reachability/clusterReachability.d.ts +0 -109
  241. package/dist/reachability/index.d.ts +0 -139
  242. package/dist/reachability/request.d.ts +0 -35
  243. package/dist/reachability/util.d.ts +0 -8
  244. package/dist/reactions/constants.d.ts +0 -3
  245. package/dist/reactions/reactions.d.ts +0 -4
  246. package/dist/reactions/reactions.type.d.ts +0 -32
  247. package/dist/reconnection-manager/index.d.ts +0 -112
  248. package/dist/recording-controller/enums.d.ts +0 -7
  249. package/dist/recording-controller/index.d.ts +0 -193
  250. package/dist/recording-controller/util.d.ts +0 -13
  251. package/dist/roap/collection.d.ts +0 -10
  252. package/dist/roap/collection.js +0 -63
  253. package/dist/roap/collection.js.map +0 -1
  254. package/dist/roap/handler.d.ts +0 -47
  255. package/dist/roap/handler.js +0 -279
  256. package/dist/roap/handler.js.map +0 -1
  257. package/dist/roap/index.d.ts +0 -116
  258. package/dist/roap/request.d.ts +0 -35
  259. package/dist/roap/state.d.ts +0 -9
  260. package/dist/roap/state.js +0 -127
  261. package/dist/roap/state.js.map +0 -1
  262. package/dist/roap/turnDiscovery.d.ts +0 -81
  263. package/dist/roap/util.d.ts +0 -2
  264. package/dist/roap/util.js +0 -76
  265. package/dist/roap/util.js.map +0 -1
  266. package/dist/rtcMetrics/constants.d.ts +0 -4
  267. package/dist/rtcMetrics/constants.js.map +0 -1
  268. package/dist/rtcMetrics/index.d.ts +0 -61
  269. package/dist/rtcMetrics/index.js +0 -197
  270. package/dist/rtcMetrics/index.js.map +0 -1
  271. package/dist/statsAnalyzer/global.d.ts +0 -118
  272. package/dist/statsAnalyzer/global.js +0 -127
  273. package/dist/statsAnalyzer/global.js.map +0 -1
  274. package/dist/statsAnalyzer/index.d.ts +0 -193
  275. package/dist/statsAnalyzer/index.js +0 -1019
  276. package/dist/statsAnalyzer/index.js.map +0 -1
  277. package/dist/statsAnalyzer/mqaUtil.d.ts +0 -22
  278. package/dist/statsAnalyzer/mqaUtil.js +0 -181
  279. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  280. package/dist/transcription/index.d.ts +0 -64
  281. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  282. package/dist/types/mediaQualityMetrics/config.d.ts +0 -241
  283. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  284. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  285. package/dist/types/rtcMetrics/index.d.ts +0 -71
  286. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  287. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  288. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  289. package/dist/webinar/collection.d.ts +0 -16
  290. package/dist/webinar/index.d.ts +0 -5
@@ -56,6 +56,7 @@ import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/reques
56
56
  import LocusInfo from '@webex/plugin-meetings/src/locus-info';
57
57
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
58
58
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
59
+ import MembersUtil from '@webex/plugin-meetings/src/members/util';
59
60
  import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
60
61
  import Media from '@webex/plugin-meetings/src/media/index';
61
62
  import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
@@ -244,6 +245,7 @@ describe('plugin-meetings', () => {
244
245
  });
245
246
 
246
247
  webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
248
+ webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
247
249
  webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
248
250
  webex.internal.services = {get: sinon.stub().returns('locus-url')};
249
251
  webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
@@ -368,6 +370,35 @@ describe('plugin-meetings', () => {
368
370
  assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
369
371
  assert.instanceOf(meeting.webinar, Webinar);
370
372
  });
373
+
374
+ it('should call the callback with the meeting that has id already set', () => {
375
+ let meetingIdFromCallback;
376
+ // check that the meeting id is already set correctly at the time when the callback is called
377
+ const meetingCreationCallback = sinon.stub().callsFake((meeting) => {
378
+ meetingIdFromCallback = meeting.id;
379
+ });
380
+
381
+ meeting = new Meeting(
382
+ {
383
+ userId: uuid1,
384
+ resource: uuid2,
385
+ deviceUrl: uuid3,
386
+ locus: {url: url1},
387
+ destination: testDestination,
388
+ destinationType: DESTINATION_TYPE.MEETING_ID,
389
+ correlationId,
390
+ selfId: uuid1,
391
+ },
392
+ {
393
+ parent: webex,
394
+ },
395
+ meetingCreationCallback
396
+ );
397
+ assert.exists(meeting.id);
398
+ assert.calledOnceWithExactly(meetingCreationCallback, meeting);
399
+ assert.equal(meeting.id, meetingIdFromCallback);
400
+ });
401
+
371
402
  it('creates MediaRequestManager instances', () => {
372
403
  assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
373
404
  assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
@@ -454,6 +485,18 @@ describe('plugin-meetings', () => {
454
485
  });
455
486
  });
456
487
 
488
+ it('pstnCorrelationId getter/setter should work correctly', () => {
489
+ const testPstnCorrelationId = uuid.v4();
490
+
491
+ meeting.pstnCorrelationId = testPstnCorrelationId;
492
+ assert.equal(meeting.pstnCorrelationId, testPstnCorrelationId);
493
+ assert.equal(meeting.callStateForMetrics.pstnCorrelationId, testPstnCorrelationId);
494
+
495
+ meeting.pstnCorrelationId = undefined;
496
+ assert.equal(meeting.pstnCorrelationId, undefined);
497
+ assert.equal(meeting.callStateForMetrics.pstnCorrelationId, undefined);
498
+ });
499
+
457
500
  describe('creates ReceiveSlot manager instance', () => {
458
501
  let mockReceiveSlotManagerCtor;
459
502
  let providedCreateSlotCallback;
@@ -581,7 +624,6 @@ describe('plugin-meetings', () => {
581
624
  assert.isFalse(meeting.isLocusCall());
582
625
  });
583
626
  });
584
-
585
627
  describe('#invite', () => {
586
628
  it('should have #invite', () => {
587
629
  assert.exists(meeting.invite);
@@ -592,8 +634,6 @@ describe('plugin-meetings', () => {
592
634
  it('should proxy members #addMember and return a promise', async () => {
593
635
  const invite = meeting.invite(uuid1, false);
594
636
 
595
- assert.exists(invite.then);
596
- await invite;
597
637
  assert.calledOnce(meeting.members.addMember);
598
638
  assert.calledWith(meeting.members.addMember, uuid1, false);
599
639
  });
@@ -614,20 +654,20 @@ describe('plugin-meetings', () => {
614
654
  assert.calledWith(meeting.members.cancelPhoneInvite, uuid1);
615
655
  });
616
656
  });
617
- describe('#cancelSIPInvite', () => {
618
- it('should have #cancelSIPInvite', () => {
619
- assert.exists(meeting.cancelSIPInvite);
657
+ describe('#cancelInviteByMemberId', () => {
658
+ it('should have #cancelInviteByMemberId', () => {
659
+ assert.exists(meeting.cancelInviteByMemberId);
620
660
  });
621
661
  beforeEach(() => {
622
- meeting.members.cancelSIPInvite = sinon.stub().returns(Promise.resolve(test1));
662
+ meeting.members.cancelInviteByMemberId = sinon.stub().returns(Promise.resolve(test1));
623
663
  });
624
- it('should proxy members #cancelSIPInvite and return a promise', async () => {
625
- const cancel = meeting.cancelSIPInvite({memberId: uuid1});
664
+ it('should proxy members #cancelInviteByMemberId and return a promise', async () => {
665
+ const cancel = meeting.cancelInviteByMemberId({memberId: uuid1});
626
666
 
627
667
  assert.exists(cancel.then);
628
668
  await cancel;
629
- assert.calledOnce(meeting.members.cancelSIPInvite);
630
- assert.calledWith(meeting.members.cancelSIPInvite, {memberId: uuid1});
669
+ assert.calledOnce(meeting.members.cancelInviteByMemberId);
670
+ assert.calledWith(meeting.members.cancelInviteByMemberId, {memberId: uuid1});
631
671
  });
632
672
  });
633
673
  describe('#admit', () => {
@@ -1208,8 +1248,73 @@ describe('plugin-meetings', () => {
1208
1248
  reason: 'joinWithMedia failure',
1209
1249
  });
1210
1250
  });
1211
- });
1212
1251
 
1252
+ it('should ignore sendVideo/receiveVideo when videoEnabled is false', async () => {
1253
+ await meeting.joinWithMedia({
1254
+ joinOptions,
1255
+ mediaOptions: {
1256
+ videoEnabled: false,
1257
+ sendVideo: true,
1258
+ receiveVideo: true,
1259
+ allowMediaInLobby: true,
1260
+ },
1261
+ });
1262
+
1263
+ assert.calledWithMatch(
1264
+ meeting.addMediaInternal,
1265
+ sinon.match.any,
1266
+ sinon.match.any,
1267
+ sinon.match.any,
1268
+ sinon.match.has('videoEnabled', false).and(sinon.match.has('allowMediaInLobby', true))
1269
+ );
1270
+ });
1271
+
1272
+ it('should ignore sendAudio/receiveAudio when audioEnabled is false', async () => {
1273
+ await meeting.joinWithMedia({
1274
+ joinOptions,
1275
+ mediaOptions: {
1276
+ audioEnabled: false,
1277
+ sendAudio: true,
1278
+ receiveAudio: false,
1279
+ allowMediaInLobby: true,
1280
+ },
1281
+ });
1282
+
1283
+ assert.calledWithMatch(
1284
+ meeting.addMediaInternal,
1285
+ sinon.match.any,
1286
+ sinon.match.any,
1287
+ sinon.match.any,
1288
+ sinon.match.has('audioEnabled', false).and(sinon.match.has('allowMediaInLobby', true))
1289
+ );
1290
+ });
1291
+
1292
+ it('should use provided send/receive values when videoEnabled/audioEnabled are true or not set', async () => {
1293
+ await meeting.joinWithMedia({
1294
+ joinOptions,
1295
+ mediaOptions: {
1296
+ sendVideo: true,
1297
+ receiveVideo: false,
1298
+ sendAudio: false,
1299
+ receiveAudio: true,
1300
+ allowMediaInLobby: true,
1301
+ },
1302
+ });
1303
+
1304
+ assert.calledWith(
1305
+ meeting.addMediaInternal,
1306
+ sinon.match.any,
1307
+ sinon.match.any,
1308
+ sinon.match.any,
1309
+ sinon.match({
1310
+ sendVideo: true,
1311
+ receiveVideo: false,
1312
+ sendAudio: false,
1313
+ receiveAudio: true,
1314
+ })
1315
+ );
1316
+ });
1317
+ });
1213
1318
  describe('#isTranscriptionSupported', () => {
1214
1319
  it('should return false if the feature is not supported for the meeting', () => {
1215
1320
  meeting.locusInfo.controls = {transcribe: {caption: false}};
@@ -1223,6 +1328,44 @@ describe('plugin-meetings', () => {
1223
1328
  });
1224
1329
  });
1225
1330
 
1331
+ describe('#update spoken language', () => {
1332
+ beforeEach(() => {
1333
+ webex.internal.voicea.onSpokenLanguageUpdate = sinon.stub();
1334
+ meeting.transcription = {languageOptions: {currentSpokenLanguage: 'en'}};
1335
+ });
1336
+ afterEach(() => {
1337
+ // Restore the original methods after each test
1338
+ sinon.restore();
1339
+ });
1340
+ it('should call voicea.onSpokenLanguageUpdate when joined', async () => {
1341
+ meeting.joinedWith = {state: 'JOINED'};
1342
+ await meeting.locusInfo.emitScoped(
1343
+ {function: 'test', file: 'test'},
1344
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
1345
+ {spokenLanguage: 'fr'}
1346
+ );
1347
+ assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'fr', meeting.id);
1348
+ assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, 'fr');
1349
+ assert.calledWith(
1350
+ TriggerProxy.trigger,
1351
+ meeting,
1352
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
1353
+ EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED
1354
+ );
1355
+ });
1356
+
1357
+ it('should also call voicea.onSpokenLanguageUpdate when not joined', async () => {
1358
+ meeting.joinedWith = {state: 'NOT_JOINED'};
1359
+ await meeting.locusInfo.emitScoped(
1360
+ {function: 'test', file: 'test'},
1361
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
1362
+ {spokenLanguage: 'de'}
1363
+ );
1364
+ assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'de');
1365
+ assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, 'de');
1366
+ });
1367
+ });
1368
+
1226
1369
  describe('#startTranscription', () => {
1227
1370
  beforeEach(() => {
1228
1371
  webex.internal.voicea.on = sinon.stub();
@@ -1846,21 +1989,25 @@ describe('plugin-meetings', () => {
1846
1989
  });
1847
1990
  });
1848
1991
 
1849
- it('should post error event if failed', async () => {
1992
+ it('should handle join failure', async () => {
1850
1993
  MeetingUtil.isPinOrGuest = sinon.stub().returns(false);
1994
+ webex.internal.newMetrics.submitClientEvent = sinon.stub();
1995
+
1851
1996
  await meeting.join().catch(() => {
1852
- assert.deepEqual(
1853
- webex.internal.newMetrics.submitClientEvent.getCall(1).args[0].name,
1854
- 'client.locus.join.response'
1855
- );
1856
- assert.match(
1857
- webex.internal.newMetrics.submitClientEvent.getCall(1).args[0].options.rawError,
1997
+ assert.calledOnce(MeetingUtil.joinMeeting);
1998
+
1999
+ // Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
2000
+ assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
2001
+ assert.calledWithMatch(
2002
+ webex.internal.newMetrics.submitClientEvent,
1858
2003
  {
1859
- code: 2,
1860
- error: null,
1861
- joinOptions: {},
1862
- sdkMessage:
1863
- 'There was an issue joining the meeting, meeting could be in a bad state.',
2004
+ name: 'client.call.initiated',
2005
+ payload: {
2006
+ trigger: 'user-interaction',
2007
+ isRoapCallEnabled: true,
2008
+ pstnAudioType: undefined
2009
+ },
2010
+ options: {meetingId: meeting.id},
1864
2011
  }
1865
2012
  );
1866
2013
  });
@@ -2061,14 +2208,12 @@ describe('plugin-meetings', () => {
2061
2208
  };
2062
2209
  meeting.mediaProperties.setMediaDirection = sinon.stub().returns(true);
2063
2210
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2064
- meeting.mediaProperties.getCurrentConnectionInfo = sinon
2065
- .stub()
2066
- .resolves({
2067
- connectionType: 'udp',
2068
- selectedCandidatePairChanges: 2,
2069
- numTransports: 1,
2070
- ipVersion: 'IPv6',
2071
- });
2211
+ meeting.mediaProperties.getCurrentConnectionInfo = sinon.stub().resolves({
2212
+ connectionType: 'udp',
2213
+ selectedCandidatePairChanges: 2,
2214
+ numTransports: 1,
2215
+ ipVersion: 'IPv6',
2216
+ });
2072
2217
  meeting.audio = muteStateStub;
2073
2218
  meeting.video = muteStateStub;
2074
2219
  sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
@@ -2187,8 +2332,9 @@ describe('plugin-meetings', () => {
2187
2332
  someReachabilityMetric1: 'some value1',
2188
2333
  someReachabilityMetric2: 'some value2',
2189
2334
  selectedCandidatePairChanges: 2,
2190
- isSubnetReachable: null,
2191
- selectedCluster: null,
2335
+ subnet_reachable: null,
2336
+ selected_cluster: null,
2337
+ selected_subnet: null,
2192
2338
  numTransports: 1,
2193
2339
  iceCandidatesCount: 0,
2194
2340
  }
@@ -2235,8 +2381,9 @@ describe('plugin-meetings', () => {
2235
2381
  signalingState: 'unknown',
2236
2382
  connectionState: 'unknown',
2237
2383
  iceConnectionState: 'unknown',
2238
- isSubnetReachable: null,
2239
- selectedCluster: null,
2384
+ subnet_reachable: null,
2385
+ selected_cluster: null,
2386
+ selected_subnet: null,
2240
2387
  })
2241
2388
  );
2242
2389
 
@@ -2302,8 +2449,9 @@ describe('plugin-meetings', () => {
2302
2449
  selectedCandidatePairChanges: 2,
2303
2450
  numTransports: 1,
2304
2451
  iceCandidatesCount: 0,
2305
- isSubnetReachable: null,
2306
- selectedCluster: null,
2452
+ subnet_reachable: null,
2453
+ selected_cluster: null,
2454
+ selected_subnet: null,
2307
2455
  }
2308
2456
  );
2309
2457
  });
@@ -2361,8 +2509,9 @@ describe('plugin-meetings', () => {
2361
2509
  signalingState: 'have-local-offer',
2362
2510
  connectionState: 'connecting',
2363
2511
  iceConnectionState: 'checking',
2364
- isSubnetReachable: null,
2365
- selectedCluster: null,
2512
+ subnet_reachable: null,
2513
+ selected_cluster: null,
2514
+ selected_subnet: null,
2366
2515
  })
2367
2516
  );
2368
2517
 
@@ -2420,8 +2569,9 @@ describe('plugin-meetings', () => {
2420
2569
  signalingState: 'have-local-offer',
2421
2570
  connectionState: 'connecting',
2422
2571
  iceConnectionState: 'checking',
2423
- isSubnetReachable: null,
2424
- selectedCluster: null,
2572
+ subnet_reachable: null,
2573
+ selected_cluster: null,
2574
+ selected_subnet: null,
2425
2575
  })
2426
2576
  );
2427
2577
 
@@ -2943,8 +3093,9 @@ describe('plugin-meetings', () => {
2943
3093
  selectedCandidatePairChanges: 2,
2944
3094
  numTransports: 1,
2945
3095
  iceCandidatesCount: 0,
2946
- isSubnetReachable: null,
2947
- selectedCluster: null,
3096
+ subnet_reachable: null,
3097
+ selected_cluster: null,
3098
+ selected_subnet: null,
2948
3099
  },
2949
3100
  ]);
2950
3101
 
@@ -3151,8 +3302,9 @@ describe('plugin-meetings', () => {
3151
3302
  retriedWithTurnServer: true,
3152
3303
  isJoinWithMediaRetry: false,
3153
3304
  iceCandidatesCount: 0,
3154
- isSubnetReachable: null,
3155
- selectedCluster: null,
3305
+ subnet_reachable: null,
3306
+ selected_cluster: null,
3307
+ selected_subnet: null,
3156
3308
  },
3157
3309
  ]);
3158
3310
  meeting.roap.doTurnDiscovery;
@@ -3286,8 +3438,8 @@ describe('plugin-meetings', () => {
3286
3438
  meeting.mediaConnections = [
3287
3439
  {
3288
3440
  mediaAgentCluster: 'some.cluster',
3289
- }
3290
- ]
3441
+ },
3442
+ ];
3291
3443
  meeting.iceCandidatesCount = 3;
3292
3444
  meeting.iceCandidateErrors.set('701_error', 3);
3293
3445
  meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
@@ -3315,8 +3467,9 @@ describe('plugin-meetings', () => {
3315
3467
  iceCandidatesCount: 3,
3316
3468
  '701_error': 3,
3317
3469
  '701_turn_host_lookup_received_error': 1,
3318
- isSubnetReachable: null,
3319
- selectedCluster: 'some.cluster',
3470
+ subnet_reachable: null,
3471
+ selected_cluster: 'some.cluster',
3472
+ selected_subnet: null,
3320
3473
  }
3321
3474
  );
3322
3475
 
@@ -3379,8 +3532,9 @@ describe('plugin-meetings', () => {
3379
3532
  iceConnectionState: 'unknown',
3380
3533
  selectedCandidatePairChanges: 2,
3381
3534
  numTransports: 1,
3382
- isSubnetReachable: null,
3383
- selectedCluster: null,
3535
+ subnet_reachable: null,
3536
+ selected_cluster: null,
3537
+ selected_subnet: null,
3384
3538
  iceCandidatesCount: 0,
3385
3539
  }
3386
3540
  );
@@ -3442,8 +3596,9 @@ describe('plugin-meetings', () => {
3442
3596
  numTransports: 1,
3443
3597
  '701_error': 2,
3444
3598
  '701_turn_host_lookup_received_error': 1,
3445
- isSubnetReachable: null,
3446
- selectedCluster: null,
3599
+ subnet_reachable: null,
3600
+ selected_cluster: null,
3601
+ selected_subnet: null,
3447
3602
  iceCandidatesCount: 0,
3448
3603
  }
3449
3604
  );
@@ -3451,7 +3606,7 @@ describe('plugin-meetings', () => {
3451
3606
  assert.isOk(errorThrown);
3452
3607
  });
3453
3608
 
3454
- it('should send valid isSubnetReachability if media connection success', async () => {
3609
+ it('should send subnet reachablity metrics if media connection success', async () => {
3455
3610
  meeting.roap.doTurnDiscovery = sinon.stub().returns({
3456
3611
  turnServerInfo: undefined,
3457
3612
  turnDiscoverySkippedReason: undefined,
@@ -3465,6 +3620,12 @@ describe('plugin-meetings', () => {
3465
3620
  stopReachability: sinon.stub(),
3466
3621
  isSubnetReachable: sinon.stub().returns(false),
3467
3622
  };
3623
+ meeting.mediaServerIp = '1.2.3.4';
3624
+ meeting.mediaConnections = [
3625
+ {
3626
+ mediaAgentCluster: 'some.cluster',
3627
+ },
3628
+ ];
3468
3629
 
3469
3630
  const forceRtcMetricsSend = sinon.stub().resolves();
3470
3631
  const closeMediaConnectionStub = sinon.stub();
@@ -3492,12 +3653,13 @@ describe('plugin-meetings', () => {
3492
3653
  isJoinWithMediaRetry: false,
3493
3654
  iceCandidatesCount: 0,
3494
3655
  reachability_public_udp_success: 5,
3495
- isSubnetReachable: false,
3496
- selectedCluster: null,
3656
+ subnet_reachable: false,
3657
+ selected_cluster: 'some.cluster',
3658
+ selected_subnet: '1.X.X.X',
3497
3659
  });
3498
3660
  });
3499
3661
 
3500
- it('should send valid isSubnetReachability if media connection fails', async () => {
3662
+ it('should send subnet reachablity metrics if media connection fails', async () => {
3501
3663
  let errorThrown = undefined;
3502
3664
 
3503
3665
  meeting.roap.doTurnDiscovery = sinon.stub().returns({
@@ -3513,6 +3675,12 @@ describe('plugin-meetings', () => {
3513
3675
  stopReachability: sinon.stub(),
3514
3676
  isSubnetReachable: sinon.stub().returns(true),
3515
3677
  };
3678
+ meeting.mediaServerIp = '1.2.3.4';
3679
+ meeting.mediaConnections = [
3680
+ {
3681
+ mediaAgentCluster: 'some.cluster',
3682
+ },
3683
+ ];
3516
3684
 
3517
3685
  const forceRtcMetricsSend = sinon.stub().resolves();
3518
3686
  const closeMediaConnectionStub = sinon.stub();
@@ -3554,8 +3722,9 @@ describe('plugin-meetings', () => {
3554
3722
  selectedCandidatePairChanges: 2,
3555
3723
  numTransports: 1,
3556
3724
  reachability_public_udp_success: 5,
3557
- isSubnetReachable: true,
3558
- selectedCluster: null,
3725
+ subnet_reachable: true,
3726
+ selected_cluster: 'some.cluster',
3727
+ selected_subnet: '1.X.X.X',
3559
3728
  iceCandidatesCount: 0,
3560
3729
  }
3561
3730
  );
@@ -4152,7 +4321,7 @@ describe('plugin-meetings', () => {
4152
4321
  meeting.deviceUrl = 'device url';
4153
4322
  meeting.selfId = 'self id';
4154
4323
  meeting.brbState = createBrbState(meeting, false);
4155
- meeting.brbState.enable = sinon.stub().resolves();
4324
+ sinon.stub(meeting.brbState, 'enable').resolves();
4156
4325
  });
4157
4326
 
4158
4327
  afterEach(() => {
@@ -4216,6 +4385,17 @@ describe('plugin-meetings', () => {
4216
4385
 
4217
4386
  assert.notCalled(meeting.audio.handleServerRemoteMuteUpdate);
4218
4387
  });
4388
+
4389
+ it('should reject when brb enable fails', async () => {
4390
+ meeting.brbState.enable.restore();
4391
+
4392
+ const error = new Error();
4393
+ meeting.meetingRequest.setBrb = sinon.stub().rejects(error);
4394
+
4395
+ await expect(meeting.beRightBack(true)).to.be.rejectedWith(error);
4396
+
4397
+ assert.isFalse(meeting.brbState.state.syncToServerInProgress);
4398
+ });
4219
4399
  });
4220
4400
  });
4221
4401
 
@@ -6362,12 +6542,17 @@ describe('plugin-meetings', () => {
6362
6542
  const DIAL_IN_URL = meeting.dialInUrl;
6363
6543
 
6364
6544
  assert.calledWith(meeting.meetingRequest.dialIn, {
6365
- correlationId: meeting.correlationId,
6545
+ correlationId: meeting.pstnCorrelationId,
6366
6546
  dialInUrl: DIAL_IN_URL,
6367
6547
  locusUrl: meeting.locusUrl,
6368
6548
  clientUrl: meeting.deviceUrl,
6369
6549
  });
6370
6550
  assert.notCalled(meeting.meetingRequest.dialOut);
6551
+
6552
+ // Verify pstnCorrelationId was set
6553
+ assert.exists(meeting.pstnCorrelationId);
6554
+ assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
6555
+ const firstPstnCorrelationId = meeting.pstnCorrelationId
6371
6556
 
6372
6557
  meeting.meetingRequest.dialIn.resetHistory();
6373
6558
 
@@ -6375,12 +6560,18 @@ describe('plugin-meetings', () => {
6375
6560
  await meeting.usePhoneAudio();
6376
6561
 
6377
6562
  assert.calledWith(meeting.meetingRequest.dialIn, {
6378
- correlationId: meeting.correlationId,
6563
+ correlationId: meeting.pstnCorrelationId,
6379
6564
  dialInUrl: DIAL_IN_URL,
6380
6565
  locusUrl: meeting.locusUrl,
6381
6566
  clientUrl: meeting.deviceUrl,
6382
6567
  });
6383
6568
  assert.notCalled(meeting.meetingRequest.dialOut);
6569
+ // A new PSTN correlationId should be generated for the second attempt
6570
+ assert.notEqual(
6571
+ meeting.pstnCorrelationId,
6572
+ firstPstnCorrelationId,
6573
+ 'pstnCorrelationId should be regenerated on each dial-in attempt'
6574
+ );
6384
6575
  });
6385
6576
 
6386
6577
  it('given a phone number, triggers dial-out, delegating request to meetingRequest correctly', async () => {
@@ -6390,7 +6581,7 @@ describe('plugin-meetings', () => {
6390
6581
  const DIAL_OUT_URL = meeting.dialOutUrl;
6391
6582
 
6392
6583
  assert.calledWith(meeting.meetingRequest.dialOut, {
6393
- correlationId: meeting.correlationId,
6584
+ correlationId: meeting.pstnCorrelationId,
6394
6585
  dialOutUrl: DIAL_OUT_URL,
6395
6586
  locusUrl: meeting.locusUrl,
6396
6587
  clientUrl: meeting.deviceUrl,
@@ -6398,49 +6589,126 @@ describe('plugin-meetings', () => {
6398
6589
  });
6399
6590
  assert.notCalled(meeting.meetingRequest.dialIn);
6400
6591
 
6592
+ // Verify pstnCorrelationId was set
6593
+ assert.exists(meeting.pstnCorrelationId);
6594
+ assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
6595
+ const firstPstnCorrelationId = meeting.pstnCorrelationId;
6596
+
6401
6597
  meeting.meetingRequest.dialOut.resetHistory();
6402
6598
 
6403
6599
  // try again. the dial out urls should match
6404
6600
  await meeting.usePhoneAudio(phoneNumber);
6405
6601
 
6406
6602
  assert.calledWith(meeting.meetingRequest.dialOut, {
6407
- correlationId: meeting.correlationId,
6603
+ correlationId: meeting.pstnCorrelationId,
6408
6604
  dialOutUrl: DIAL_OUT_URL,
6409
6605
  locusUrl: meeting.locusUrl,
6410
6606
  clientUrl: meeting.deviceUrl,
6411
6607
  phoneNumber,
6412
6608
  });
6413
6609
  assert.notCalled(meeting.meetingRequest.dialIn);
6610
+ // A new PSTN correlationId should be generated for the second attempt
6611
+ assert.notEqual(
6612
+ meeting.pstnCorrelationId,
6613
+ firstPstnCorrelationId,
6614
+ 'pstnCorrelationId should be regenerated on each dial-out attempt'
6615
+ );
6414
6616
  });
6415
6617
 
6416
- it('rejects if the request failed (dial in)', () => {
6417
- const error = 'something bad happened';
6618
+ it('rejects if the request failed (dial in)', async () => {
6619
+ const error = {error: {message: 'dial in failed'}, stack: 'error stack'};
6418
6620
 
6419
6621
  meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
6420
6622
 
6421
- return meeting
6422
- .usePhoneAudio()
6423
- .then(() => Promise.reject(new Error('Promise resolved when it should have rejected')))
6424
- .catch((e) => {
6425
- assert.equal(e, error);
6426
-
6427
- return Promise.resolve();
6623
+ try {
6624
+ await meeting.usePhoneAudio();
6625
+ throw new Error('Promise resolved when it should have rejected');
6626
+ } catch (e) {
6627
+ assert.equal(e, error);
6628
+
6629
+ // Verify behavioral metric was sent with dial_in_correlation_id
6630
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
6631
+ correlation_id: meeting.correlationId,
6632
+ dial_in_url: meeting.dialInUrl,
6633
+ dial_in_correlation_id: sinon.match.string,
6634
+ locus_id: meeting.locusUrl.split('/').pop(),
6635
+ client_url: meeting.deviceUrl,
6636
+ reason: error.error.message,
6637
+ stack: error.stack,
6428
6638
  });
6639
+
6640
+ // Verify pstnCorrelationId was cleared after error
6641
+ assert.equal(meeting.pstnCorrelationId, undefined);
6642
+ }
6429
6643
  });
6430
6644
 
6431
6645
  it('rejects if the request failed (dial out)', async () => {
6432
- const error = 'something bad happened';
6646
+ const error = {error: {message: 'dial out failed'}, stack: 'error stack'};
6433
6647
 
6434
6648
  meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
6435
6649
 
6436
- return meeting
6437
- .usePhoneAudio('+441234567890')
6438
- .then(() => Promise.reject(new Error('Promise resolved when it should have rejected')))
6439
- .catch((e) => {
6440
- assert.equal(e, error);
6441
-
6442
- return Promise.resolve();
6650
+ try {
6651
+ await meeting.usePhoneAudio('+441234567890');
6652
+ throw new Error('Promise resolved when it should have rejected');
6653
+ } catch (e) {
6654
+ assert.equal(e, error);
6655
+
6656
+ // Verify behavioral metric was sent with dial_out_correlation_id
6657
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
6658
+ correlation_id: meeting.correlationId,
6659
+ dial_out_url: meeting.dialOutUrl,
6660
+ dial_out_correlation_id: sinon.match.string,
6661
+ locus_id: meeting.locusUrl.split('/').pop(),
6662
+ client_url: meeting.deviceUrl,
6663
+ reason: error.error.message,
6664
+ stack: error.stack,
6443
6665
  });
6666
+
6667
+ // Verify pstnCorrelationId was cleared after error
6668
+ assert.equal(meeting.pstnCorrelationId, undefined);
6669
+ }
6670
+ });
6671
+ });
6672
+
6673
+ describe('#disconnectPhoneAudio', () => {
6674
+ beforeEach(() => {
6675
+ // Mock the MeetingUtil.disconnectPhoneAudio method
6676
+ sinon.stub(MeetingUtil, 'disconnectPhoneAudio').resolves();
6677
+ meeting.dialInUrl = 'dialin:///test-dial-in-url';
6678
+ meeting.dialOutUrl = 'dialout:///test-dial-out-url';
6679
+ meeting.dialInDeviceStatus = 'JOINED';
6680
+ meeting.dialOutDeviceStatus = 'JOINED';
6681
+ });
6682
+
6683
+ afterEach(() => {
6684
+ MeetingUtil.disconnectPhoneAudio.restore();
6685
+ });
6686
+
6687
+ it('should disconnect phone audio and clear pstnCorrelationId', async () => {
6688
+ meeting.pstnCorrelationId = 'test-pstn-correlation-id';
6689
+
6690
+ await meeting.disconnectPhoneAudio();
6691
+
6692
+ // Verify that pstnCorrelationId is cleared
6693
+ assert.equal(meeting.pstnCorrelationId, undefined);
6694
+
6695
+ // Verify that MeetingUtil.disconnectPhoneAudio was called for both dial-in and dial-out
6696
+ assert.calledTwice(MeetingUtil.disconnectPhoneAudio);
6697
+ assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialInUrl);
6698
+ assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialOutUrl);
6699
+ });
6700
+
6701
+ it('should handle case when no PSTN connection is active', async () => {
6702
+ meeting.dialInDeviceStatus = 'IDLE';
6703
+ meeting.dialOutDeviceStatus = 'IDLE';
6704
+ meeting.pstnCorrelationId = 'test-pstn-correlation-id';
6705
+
6706
+ await meeting.disconnectPhoneAudio();
6707
+
6708
+ // Verify that pstnCorrelationId is still cleared even when no phone connection is active
6709
+ assert.equal(meeting.pstnCorrelationId, undefined);
6710
+ // And verify no disconnect was attempted
6711
+ assert.notCalled(MeetingUtil.disconnectPhoneAudio);
6444
6712
  });
6445
6713
  });
6446
6714
 
@@ -7957,6 +8225,7 @@ describe('plugin-meetings', () => {
7957
8225
 
7958
8226
  meeting.requestScreenShareFloor = sinon.stub().resolves({});
7959
8227
  meeting.releaseScreenShareFloor = sinon.stub().resolves({});
8228
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
7960
8229
  meeting.mediaProperties.mediaDirection = {
7961
8230
  sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
7962
8231
  sendVideo: 'fake value',
@@ -8038,6 +8307,12 @@ describe('plugin-meetings', () => {
8038
8307
  payload: {mediaType: 'share', shareInstanceId: meeting.localShareInstanceId},
8039
8308
  options: {meetingId: meeting.id},
8040
8309
  });
8310
+
8311
+ // ensure the share start timestamp is saved
8312
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
8313
+ key: 'internal.client.share.initiated',
8314
+ });
8315
+
8041
8316
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
8042
8317
 
8043
8318
  assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
@@ -8056,6 +8331,11 @@ describe('plugin-meetings', () => {
8056
8331
  options: {meetingId: meeting.id},
8057
8332
  });
8058
8333
 
8334
+ // ensure the share start timestamp is saved
8335
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
8336
+ key: 'internal.client.share.initiated',
8337
+ });
8338
+
8059
8339
  assert.calledWith(
8060
8340
  meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
8061
8341
  stream
@@ -10011,6 +10291,24 @@ describe('plugin-meetings', () => {
10011
10291
  );
10012
10292
  });
10013
10293
 
10294
+ it('listens to CONTROLS_POLLING_QA_CHANGED', async () => {
10295
+ const state = {example: 'value'};
10296
+
10297
+ await meeting.locusInfo.emitScoped(
10298
+ {function: 'test', file: 'test'},
10299
+ LOCUSINFO.EVENTS.CONTROLS_POLLING_QA_CHANGED,
10300
+ {state}
10301
+ );
10302
+
10303
+ assert.calledWith(
10304
+ TriggerProxy.trigger,
10305
+ meeting,
10306
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10307
+ EVENT_TRIGGERS.MEETING_CONTROLS_POLLING_QA_UPDATED,
10308
+ {state}
10309
+ );
10310
+ });
10311
+
10014
10312
  it('listens to the locus interpretation update event', () => {
10015
10313
  const interpretation = {
10016
10314
  siLanguages: [{languageCode: 20, languageName: 'en'}],
@@ -10311,6 +10609,8 @@ describe('plugin-meetings', () => {
10311
10609
  meeting.mediaProperties = {mediaDirection: {sendShare: true}};
10312
10610
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
10313
10611
  (meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
10612
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
10613
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
10314
10614
  });
10315
10615
  it('should call changeMeetingFloor()', async () => {
10316
10616
  meeting.screenShareFloorState = 'GRANTED';
@@ -10328,6 +10628,22 @@ describe('plugin-meetings', () => {
10328
10628
  assert.exists(share.then);
10329
10629
  await share;
10330
10630
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
10631
+
10632
+ // ensure the share stop timestamp is saved
10633
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
10634
+ key: 'internal.client.share.stopped',
10635
+ });
10636
+
10637
+ // ensure the CA share stopped metric is submitted with duration
10638
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
10639
+ name: 'client.share.stopped',
10640
+ payload: {
10641
+ mediaType: 'share',
10642
+ shareInstanceId: meeting.localShareInstanceId,
10643
+ shareDuration: 1000,
10644
+ },
10645
+ options: {meetingId: meeting.id},
10646
+ });
10331
10647
  });
10332
10648
  it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
10333
10649
  // change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
@@ -11603,6 +11919,14 @@ describe('plugin-meetings', () => {
11603
11919
  requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
11604
11920
  displayHints: userDisplayHints,
11605
11921
  });
11922
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11923
+ requiredHints: [DISPLAY_HINTS.ENABLE_ATTENDEE_START_POLLING_QA],
11924
+ displayHints: userDisplayHints,
11925
+ });
11926
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11927
+ requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
11928
+ displayHints: userDisplayHints,
11929
+ });
11606
11930
 
11607
11931
  assert.calledWith(
11608
11932
  TriggerProxy.trigger,
@@ -11892,6 +12216,7 @@ describe('plugin-meetings', () => {
11892
12216
  meeting.locusInfo.self = {url: url1};
11893
12217
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
11894
12218
  meeting.deviceUrl = 'deviceUrl.com';
12219
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
11895
12220
  });
11896
12221
  it('should have #startWhiteboardShare', () => {
11897
12222
  assert.exists(meeting.startWhiteboardShare);
@@ -11919,6 +12244,11 @@ describe('plugin-meetings', () => {
11919
12244
  payload: {mediaType: 'whiteboard'},
11920
12245
  options: {meetingId: meeting.id},
11921
12246
  });
12247
+
12248
+ // ensure the share start timestamp is saved
12249
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
12250
+ key: 'internal.client.share.initiated',
12251
+ });
11922
12252
  });
11923
12253
  });
11924
12254
  describe('#stopWhiteboardShare', () => {
@@ -11930,6 +12260,9 @@ describe('plugin-meetings', () => {
11930
12260
  meeting.locusInfo.self = {url: url1};
11931
12261
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
11932
12262
  meeting.deviceUrl = 'deviceUrl.com';
12263
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
12264
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
12265
+ webex.internal.newMetrics.submitClientEvent = sinon.stub();
11933
12266
  });
11934
12267
  it('should stop the whiteboard share', async () => {
11935
12268
  const whiteboardShare = meeting.stopWhiteboardShare();
@@ -11944,6 +12277,21 @@ describe('plugin-meetings', () => {
11944
12277
  uri: url1,
11945
12278
  });
11946
12279
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
12280
+
12281
+ // ensure the share stop timestamp is saved
12282
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
12283
+ key: 'internal.client.share.stopped',
12284
+ });
12285
+
12286
+ // ensure the CA share stopped metric is submitted with duration
12287
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
12288
+ name: 'client.share.stopped',
12289
+ payload: {
12290
+ mediaType: 'whiteboard',
12291
+ shareDuration: 1000,
12292
+ },
12293
+ options: {meetingId: meeting.id},
12294
+ });
11947
12295
  });
11948
12296
  });
11949
12297
  });
@@ -12016,6 +12364,9 @@ describe('plugin-meetings', () => {
12016
12364
  meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
12017
12365
  meeting.deviceUrl = 'my-web-url';
12018
12366
  meeting.locusInfo.info = {isWebinar: false};
12367
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
12368
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1500);
12369
+ webex.internal.newMetrics.submitClientEvent = sinon.stub();
12019
12370
  });
12020
12371
 
12021
12372
  const USER_IDS = {
@@ -12242,7 +12593,7 @@ describe('plugin-meetings', () => {
12242
12593
  activeSharingId.whiteboard = beneficiaryId;
12243
12594
 
12244
12595
  eventTrigger.share.push(
12245
- meeting.webinar.selfIsAttendee
12596
+ meeting.webinar.selfIsAttendee || meeting.guest
12246
12597
  ? {
12247
12598
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12248
12599
  functionName: 'remoteShare',
@@ -12261,7 +12612,8 @@ describe('plugin-meetings', () => {
12261
12612
  }
12262
12613
  );
12263
12614
 
12264
- shareStatus = meeting.webinar.selfIsAttendee
12615
+ shareStatus =
12616
+ meeting.webinar.selfIsAttendee || meeting.guest
12265
12617
  ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12266
12618
  : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
12267
12619
  }
@@ -12479,6 +12831,36 @@ describe('plugin-meetings', () => {
12479
12831
  });
12480
12832
  });
12481
12833
 
12834
+ describe('Whiteboard Share - User is guest', () => {
12835
+ it('User receives a remote share instead of whiteboard share', () => {
12836
+ // Set the guest flag
12837
+ meeting.guest = true;
12838
+
12839
+ // Step 1: Start sharing whiteboard A
12840
+ const data1 = generateData(
12841
+ blankPayload, // Initial payload
12842
+ true, // isGranting: Granting share
12843
+ false, // isContent: Whiteboard (not content)
12844
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
12845
+ RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
12846
+ );
12847
+
12848
+ // Step 2: Stop sharing whiteboard A
12849
+ const data2 = generateData(
12850
+ data1.payload, // Updated payload from Step 1
12851
+ false, // isGranting: Stopping share
12852
+ false, // isContent: Whiteboard
12853
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
12854
+ );
12855
+
12856
+ // Validate the payload changes and status updates
12857
+ payloadTestHelper([data1]);
12858
+
12859
+ // Specific assertions for guest
12860
+ assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
12861
+ });
12862
+ });
12863
+
12482
12864
  describe('Whiteboard A --> Whiteboard B', () => {
12483
12865
  it('Scenario #1: you share both whiteboards', () => {
12484
12866
  const data1 = generateData(
@@ -13130,7 +13512,54 @@ describe('plugin-meetings', () => {
13130
13512
  payloadTestHelper([data1, data2, data3]);
13131
13513
  });
13132
13514
  });
13133
- });
13515
+
13516
+ it('should send share stopped metric when whiteboard sharing stops', () => {
13517
+ // Start whiteboard sharing (this won't trigger metrics)
13518
+ const data1 = generateData(
13519
+ blankPayload,
13520
+ true, // isGranting: true
13521
+ false, // isContent: false (whiteboard)
13522
+ USER_IDS.ME,
13523
+ RESOURCE_URLS.WHITEBOARD_A
13524
+ );
13525
+
13526
+ // Stop whiteboard sharing (this should trigger metrics)
13527
+ const data2 = generateData(
13528
+ data1.payload,
13529
+ false, // isGranting: false (stopping share)
13530
+ false, // isContent: false (whiteboard)
13531
+ USER_IDS.ME
13532
+ );
13533
+
13534
+ // Trigger the events
13535
+ meeting.locusInfo.emit(
13536
+ {function: 'test', file: 'test'},
13537
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
13538
+ data1.payload
13539
+ );
13540
+
13541
+ meeting.locusInfo.emit(
13542
+ {function: 'test', file: 'test'},
13543
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
13544
+ data2.payload
13545
+ );
13546
+
13547
+ // Verify metrics were called when whiteboard sharing stopped
13548
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
13549
+ key: 'internal.client.share.stopped',
13550
+ });
13551
+
13552
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
13553
+ name: 'client.share.stopped',
13554
+ payload: {
13555
+ mediaType: 'whiteboard',
13556
+ shareDuration: 1500, // mocked return value
13557
+ },
13558
+ options: {
13559
+ meetingId: meeting.id,
13560
+ },
13561
+ });
13562
+ });
13134
13563
 
13135
13564
  describe('handleShareVideoStreamMuteStateChange', () => {
13136
13565
  it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
@@ -13157,6 +13586,7 @@ describe('plugin-meetings', () => {
13157
13586
  });
13158
13587
  });
13159
13588
  });
13589
+ });
13160
13590
 
13161
13591
  describe('#startKeepAlive', () => {
13162
13592
  let clock;
@@ -13923,4 +14353,443 @@ describe('plugin-meetings', () => {
13923
14353
  assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
13924
14354
  });
13925
14355
  });
14356
+
14357
+ describe('#setStage', () => {
14358
+ const check = async (options, expectedVideoLayout) => {
14359
+ const locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${uuidv4()}`;
14360
+ meeting.locusUrl = locusUrl;
14361
+
14362
+ const setStagePromise = meeting.setStage(options);
14363
+
14364
+ assert.exists(setStagePromise.then);
14365
+ await setStagePromise;
14366
+
14367
+ assert.calledOnceWithExactly(
14368
+ meeting.meetingRequest.synchronizeStage,
14369
+ locusUrl,
14370
+ expectedVideoLayout
14371
+ );
14372
+ };
14373
+
14374
+ beforeEach(() => {
14375
+ meeting.meetingRequest.synchronizeStage = sinon.stub().returns(Promise.resolve());
14376
+ });
14377
+
14378
+ it('sends the expected request when no options are provided', async () => {
14379
+ await check(undefined, {
14380
+ overrideDefault: true,
14381
+ lockAttendeeViewOnStageOnly: false,
14382
+ stageParameters: {
14383
+ activeSpeakerProportion: 0.5,
14384
+ showActiveSpeaker: {show: false, order: 0},
14385
+ stageManagerType: 0,
14386
+ },
14387
+ });
14388
+ });
14389
+
14390
+ it('sends the expected request when empty options are provided', async () => {
14391
+ await check(
14392
+ {},
14393
+ {
14394
+ overrideDefault: true,
14395
+ lockAttendeeViewOnStageOnly: false,
14396
+ stageParameters: {
14397
+ activeSpeakerProportion: 0.5,
14398
+ showActiveSpeaker: {show: false, order: 0},
14399
+ stageManagerType: 0,
14400
+ },
14401
+ }
14402
+ );
14403
+ });
14404
+
14405
+ [0.25, 0.5, 0.75].forEach((activeSpeakerProportion) => {
14406
+ it(`sends the expected request when only the active speaker proportion option is provided as ${activeSpeakerProportion}`, async () => {
14407
+ await check(
14408
+ {activeSpeakerProportion},
14409
+ {
14410
+ overrideDefault: true,
14411
+ lockAttendeeViewOnStageOnly: false,
14412
+ stageParameters: {
14413
+ activeSpeakerProportion,
14414
+ showActiveSpeaker: {show: false, order: 0},
14415
+ stageManagerType: 0,
14416
+ },
14417
+ }
14418
+ );
14419
+ });
14420
+ });
14421
+
14422
+ it('sends the expected request when only the custom background option is provided', async () => {
14423
+ const customBackground = {
14424
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14425
+ };
14426
+
14427
+ await check(
14428
+ {customBackground},
14429
+ {
14430
+ overrideDefault: true,
14431
+ lockAttendeeViewOnStageOnly: false,
14432
+ stageParameters: {
14433
+ activeSpeakerProportion: 0.5,
14434
+ showActiveSpeaker: {show: false, order: 0},
14435
+ stageManagerType: 2,
14436
+ },
14437
+ customLayouts: {background: customBackground},
14438
+ }
14439
+ );
14440
+ });
14441
+
14442
+ it('sends the expected request when only the custom logo option is provided', async () => {
14443
+ const customLogo = {
14444
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14445
+ position: 'LowerRight',
14446
+ };
14447
+
14448
+ await check(
14449
+ {customLogo},
14450
+ {
14451
+ overrideDefault: true,
14452
+ lockAttendeeViewOnStageOnly: false,
14453
+ stageParameters: {
14454
+ activeSpeakerProportion: 0.5,
14455
+ showActiveSpeaker: {show: false, order: 0},
14456
+ stageManagerType: 1,
14457
+ },
14458
+ customLayouts: {logo: customLogo},
14459
+ }
14460
+ );
14461
+ });
14462
+
14463
+ it('sends the expected request when only the custom name label option is provided', async () => {
14464
+ const customNameLabel = {
14465
+ accentColor: '#0A7806',
14466
+ background: {color: 'rgba(255, 255, 255, 1)'},
14467
+ border: {color: 'rgba(255, 255, 255, 1)'},
14468
+ content: {
14469
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14470
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14471
+ },
14472
+ decoration: {color: 'rgba(10, 120, 6, 1)'},
14473
+ fadeOut: {delay: 15},
14474
+ type: 'Primary',
14475
+ };
14476
+
14477
+ await check(
14478
+ {customNameLabel},
14479
+ {
14480
+ overrideDefault: true,
14481
+ lockAttendeeViewOnStageOnly: false,
14482
+ stageParameters: {
14483
+ activeSpeakerProportion: 0.5,
14484
+ showActiveSpeaker: {show: false, order: 0},
14485
+ stageManagerType: 4,
14486
+ },
14487
+ nameLabelStyle: customNameLabel,
14488
+ }
14489
+ );
14490
+ });
14491
+
14492
+ it('sends the expected request when only the custom background and logo options are provided', async () => {
14493
+ const customBackground = {
14494
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14495
+ };
14496
+ const customLogo = {
14497
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14498
+ position: 'UpperRight',
14499
+ };
14500
+
14501
+ await check(
14502
+ {customBackground, customLogo},
14503
+ {
14504
+ overrideDefault: true,
14505
+ lockAttendeeViewOnStageOnly: false,
14506
+ stageParameters: {
14507
+ activeSpeakerProportion: 0.5,
14508
+ showActiveSpeaker: {show: false, order: 0},
14509
+ stageManagerType: 3,
14510
+ },
14511
+ customLayouts: {background: customBackground, logo: customLogo},
14512
+ }
14513
+ );
14514
+ });
14515
+
14516
+ it('sends the expected request when only the custom background and name label options are provided', async () => {
14517
+ const customBackground = {
14518
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14519
+ };
14520
+ const customNameLabel = {
14521
+ accentColor: '#00A3FF',
14522
+ background: {color: 'rgba(0, 163, 255, 1)'},
14523
+ border: {color: 'rgba(0, 163, 255, 1)'},
14524
+ content: {
14525
+ displayName: {color: 'rgba(255, 255, 255, 0.95)'},
14526
+ subtitle: {color: 'rgba(255, 255, 255, 0.7)'},
14527
+ },
14528
+ decoration: {color: 'rgba(255, 255, 255, 0.95)'},
14529
+ fadeOut: {delay: 15},
14530
+ type: 'PrimaryInverted',
14531
+ };
14532
+
14533
+ await check(
14534
+ {customBackground, customNameLabel},
14535
+ {
14536
+ overrideDefault: true,
14537
+ lockAttendeeViewOnStageOnly: false,
14538
+ stageParameters: {
14539
+ activeSpeakerProportion: 0.5,
14540
+ showActiveSpeaker: {show: false, order: 0},
14541
+ stageManagerType: 6,
14542
+ },
14543
+ customLayouts: {background: customBackground},
14544
+ nameLabelStyle: customNameLabel,
14545
+ }
14546
+ );
14547
+ });
14548
+
14549
+ it('sends the expected request when only the custom logo and name label options are provided', async () => {
14550
+ const customLogo = {
14551
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14552
+ position: 'UpperLeft',
14553
+ };
14554
+ const customNameLabel = {
14555
+ accentColor: '#942B2B',
14556
+ background: {color: 'rgba(255, 255, 255, 1)'},
14557
+ border: {color: 'rgba(148, 43, 43, 1)'},
14558
+ content: {
14559
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14560
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14561
+ },
14562
+ decoration: {color: 'rgba(0, 0, 0, 0)'},
14563
+ fadeOut: {delay: 15},
14564
+ type: 'Secondary',
14565
+ };
14566
+
14567
+ await check(
14568
+ {customLogo, customNameLabel},
14569
+ {
14570
+ overrideDefault: true,
14571
+ lockAttendeeViewOnStageOnly: false,
14572
+ stageParameters: {
14573
+ activeSpeakerProportion: 0.5,
14574
+ showActiveSpeaker: {show: false, order: 0},
14575
+ stageManagerType: 5,
14576
+ },
14577
+ customLayouts: {logo: customLogo},
14578
+ nameLabelStyle: customNameLabel,
14579
+ }
14580
+ );
14581
+ });
14582
+
14583
+ it('sends the expected request when only the custom background, logo, name label options are provided', async () => {
14584
+ const customBackground = {
14585
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14586
+ };
14587
+ const customLogo = {
14588
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14589
+ position: 'LowerLeft',
14590
+ };
14591
+ const customNameLabel = {
14592
+ accentColor: '#EBD960',
14593
+ background: {color: 'rgba(235, 217, 96, 0.55)'},
14594
+ border: {color: 'rgba(235, 217, 96, 0.55)'},
14595
+ content: {
14596
+ displayName: {color: 'rgba(255, 255, 255, 0.95)'},
14597
+ subtitle: {color: 'rgba(255, 255, 255, 0.7)'},
14598
+ },
14599
+ decoration: {color: 'rgba(0, 0, 0, 0)'},
14600
+ fadeOut: {delay: 15},
14601
+ type: 'SecondaryInverted',
14602
+ };
14603
+
14604
+ await check(
14605
+ {customBackground, customLogo, customNameLabel},
14606
+ {
14607
+ overrideDefault: true,
14608
+ lockAttendeeViewOnStageOnly: false,
14609
+ stageParameters: {
14610
+ activeSpeakerProportion: 0.5,
14611
+ showActiveSpeaker: {show: false, order: 0},
14612
+ stageManagerType: 7,
14613
+ },
14614
+ customLayouts: {background: customBackground, logo: customLogo},
14615
+ nameLabelStyle: customNameLabel,
14616
+ }
14617
+ );
14618
+ });
14619
+
14620
+ it('sends the expected request when only the important participants option is provided as empty', async () => {
14621
+ await check(
14622
+ {importantParticipants: []},
14623
+ {
14624
+ overrideDefault: true,
14625
+ lockAttendeeViewOnStageOnly: false,
14626
+ stageParameters: {
14627
+ activeSpeakerProportion: 0.5,
14628
+ showActiveSpeaker: {show: false, order: 0},
14629
+ stageManagerType: 0,
14630
+ },
14631
+ }
14632
+ );
14633
+ });
14634
+
14635
+ it('sends the expected request when only the important participants option is provided as populated', async () => {
14636
+ const importantParticipants = [
14637
+ {mainCsi: 11111111, participantId: uuidv4()},
14638
+ {mainCsi: 22222222, participantId: uuidv4()},
14639
+ {mainCsi: 33333333, participantId: uuidv4()},
14640
+ {mainCsi: 44444444, participantId: uuidv4()},
14641
+ {mainCsi: 55555555, participantId: uuidv4()},
14642
+ {mainCsi: 66666666, participantId: uuidv4()},
14643
+ {mainCsi: 77777777, participantId: uuidv4()},
14644
+ {mainCsi: 88888888, participantId: uuidv4()},
14645
+ ];
14646
+
14647
+ await check(
14648
+ {importantParticipants},
14649
+ {
14650
+ overrideDefault: true,
14651
+ lockAttendeeViewOnStageOnly: false,
14652
+ stageParameters: {
14653
+ activeSpeakerProportion: 0.5,
14654
+ importantParticipants: [
14655
+ {...importantParticipants[0], order: 1},
14656
+ {...importantParticipants[1], order: 2},
14657
+ {...importantParticipants[2], order: 3},
14658
+ {...importantParticipants[3], order: 4},
14659
+ {...importantParticipants[4], order: 5},
14660
+ {...importantParticipants[5], order: 6},
14661
+ {...importantParticipants[6], order: 7},
14662
+ {...importantParticipants[7], order: 8},
14663
+ ],
14664
+ showActiveSpeaker: {show: false, order: 0},
14665
+ stageManagerType: 0,
14666
+ },
14667
+ }
14668
+ );
14669
+ });
14670
+
14671
+ [false, true].forEach((lockAttendeeViewOnStage) => {
14672
+ it(`sends the expected request when only the lock attendee view on stage option is provided as ${lockAttendeeViewOnStage}`, async () => {
14673
+ await check(
14674
+ {lockAttendeeViewOnStage},
14675
+ {
14676
+ overrideDefault: true,
14677
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
14678
+ stageParameters: {
14679
+ activeSpeakerProportion: 0.5,
14680
+ showActiveSpeaker: {show: false, order: 0},
14681
+ stageManagerType: 0,
14682
+ },
14683
+ }
14684
+ );
14685
+ });
14686
+ });
14687
+
14688
+ [false, true].forEach((showActiveSpeaker) => {
14689
+ it(`sends the expected request when only the show active speaker option is provided as ${showActiveSpeaker}`, async () => {
14690
+ await check(
14691
+ {showActiveSpeaker},
14692
+ {
14693
+ overrideDefault: true,
14694
+ lockAttendeeViewOnStageOnly: false,
14695
+ stageParameters: {
14696
+ activeSpeakerProportion: 0.5,
14697
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
14698
+ stageManagerType: 0,
14699
+ },
14700
+ }
14701
+ );
14702
+ });
14703
+ });
14704
+
14705
+ it('sends the expected request when all options are provided', async () => {
14706
+ const activeSpeakerProportion = 0.6;
14707
+ const customBackground = {
14708
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14709
+ };
14710
+ const customLogo = {
14711
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14712
+ position: 'UpperMiddle',
14713
+ };
14714
+ const customNameLabel = {
14715
+ accentColor: '#0A7806',
14716
+ background: {color: 'rgba(255, 255, 255, 1)'},
14717
+ border: {color: 'rgba(255, 255, 255, 1)'},
14718
+ content: {
14719
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14720
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14721
+ },
14722
+ decoration: {color: 'rgba(10, 120, 6, 1)'},
14723
+ fadeOut: {delay: 15},
14724
+ type: 'Primary',
14725
+ };
14726
+ const importantParticipants = [
14727
+ {mainCsi: 11111111, participantId: uuidv4()},
14728
+ {mainCsi: 22222222, participantId: uuidv4()},
14729
+ {mainCsi: 33333333, participantId: uuidv4()},
14730
+ {mainCsi: 44444444, participantId: uuidv4()},
14731
+ {mainCsi: 55555555, participantId: uuidv4()},
14732
+ {mainCsi: 66666666, participantId: uuidv4()},
14733
+ {mainCsi: 77777777, participantId: uuidv4()},
14734
+ {mainCsi: 88888888, participantId: uuidv4()},
14735
+ ];
14736
+ const lockAttendeeViewOnStage = true;
14737
+ const showActiveSpeaker = true;
14738
+
14739
+ await check(
14740
+ {
14741
+ activeSpeakerProportion,
14742
+ customBackground,
14743
+ customLogo,
14744
+ customNameLabel,
14745
+ importantParticipants,
14746
+ lockAttendeeViewOnStage,
14747
+ showActiveSpeaker,
14748
+ },
14749
+ {
14750
+ overrideDefault: true,
14751
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
14752
+ stageParameters: {
14753
+ activeSpeakerProportion,
14754
+ importantParticipants: [
14755
+ {...importantParticipants[0], order: 1},
14756
+ {...importantParticipants[1], order: 2},
14757
+ {...importantParticipants[2], order: 3},
14758
+ {...importantParticipants[3], order: 4},
14759
+ {...importantParticipants[4], order: 5},
14760
+ {...importantParticipants[5], order: 6},
14761
+ {...importantParticipants[6], order: 7},
14762
+ {...importantParticipants[7], order: 8},
14763
+ ],
14764
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
14765
+ stageManagerType: 7,
14766
+ },
14767
+ customLayouts: {background: customBackground, logo: customLogo},
14768
+ nameLabelStyle: customNameLabel,
14769
+ }
14770
+ );
14771
+ });
14772
+ });
14773
+
14774
+ describe('#unsetStage', () => {
14775
+ beforeEach(() => {
14776
+ meeting.meetingRequest.synchronizeStage = sinon.stub().returns(Promise.resolve());
14777
+ });
14778
+
14779
+ it('sends the expected request', async () => {
14780
+ const locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${uuidv4()}`;
14781
+ meeting.locusUrl = locusUrl;
14782
+
14783
+ const unsetStagePromise = meeting.unsetStage();
14784
+
14785
+ assert.exists(unsetStagePromise.then);
14786
+ await unsetStagePromise;
14787
+
14788
+ assert.calledOnceWithExactly(
14789
+ meeting.meetingRequest.synchronizeStage,
14790
+ locusUrl,
14791
+ {overrideDefault: false}
14792
+ );
14793
+ });
14794
+ });
13926
14795
  });