@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
@@ -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,16 +2208,15 @@ 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;
2219
+ sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.ipv4_and_ipv6);
2074
2220
  sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
2075
2221
  sinon.stub(meeting, 'setupMediaConnectionListeners');
2076
2222
  sinon.stub(meeting, 'setMercuryListener');
@@ -2187,10 +2333,12 @@ describe('plugin-meetings', () => {
2187
2333
  someReachabilityMetric1: 'some value1',
2188
2334
  someReachabilityMetric2: 'some value2',
2189
2335
  selectedCandidatePairChanges: 2,
2190
- isSubnetReachable: null,
2191
- selectedCluster: null,
2336
+ subnet_reachable: null,
2337
+ selected_cluster: null,
2338
+ selected_subnet: null,
2192
2339
  numTransports: 1,
2193
2340
  iceCandidatesCount: 0,
2341
+ ipver: 1,
2194
2342
  }
2195
2343
  );
2196
2344
  });
@@ -2235,8 +2383,10 @@ describe('plugin-meetings', () => {
2235
2383
  signalingState: 'unknown',
2236
2384
  connectionState: 'unknown',
2237
2385
  iceConnectionState: 'unknown',
2238
- isSubnetReachable: null,
2239
- selectedCluster: null,
2386
+ subnet_reachable: null,
2387
+ selected_cluster: null,
2388
+ selected_subnet: null,
2389
+ ipver: 1,
2240
2390
  })
2241
2391
  );
2242
2392
 
@@ -2302,8 +2452,10 @@ describe('plugin-meetings', () => {
2302
2452
  selectedCandidatePairChanges: 2,
2303
2453
  numTransports: 1,
2304
2454
  iceCandidatesCount: 0,
2305
- isSubnetReachable: null,
2306
- selectedCluster: null,
2455
+ subnet_reachable: null,
2456
+ selected_cluster: null,
2457
+ selected_subnet: null,
2458
+ ipver: 1,
2307
2459
  }
2308
2460
  );
2309
2461
  });
@@ -2361,8 +2513,10 @@ describe('plugin-meetings', () => {
2361
2513
  signalingState: 'have-local-offer',
2362
2514
  connectionState: 'connecting',
2363
2515
  iceConnectionState: 'checking',
2364
- isSubnetReachable: null,
2365
- selectedCluster: null,
2516
+ subnet_reachable: null,
2517
+ selected_cluster: null,
2518
+ selected_subnet: null,
2519
+ ipver: 1,
2366
2520
  })
2367
2521
  );
2368
2522
 
@@ -2420,8 +2574,10 @@ describe('plugin-meetings', () => {
2420
2574
  signalingState: 'have-local-offer',
2421
2575
  connectionState: 'connecting',
2422
2576
  iceConnectionState: 'checking',
2423
- isSubnetReachable: null,
2424
- selectedCluster: null,
2577
+ subnet_reachable: null,
2578
+ selected_cluster: null,
2579
+ selected_subnet: null,
2580
+ ipver: 1,
2425
2581
  })
2426
2582
  );
2427
2583
 
@@ -2943,8 +3099,10 @@ describe('plugin-meetings', () => {
2943
3099
  selectedCandidatePairChanges: 2,
2944
3100
  numTransports: 1,
2945
3101
  iceCandidatesCount: 0,
2946
- isSubnetReachable: null,
2947
- selectedCluster: null,
3102
+ subnet_reachable: null,
3103
+ selected_cluster: null,
3104
+ selected_subnet: null,
3105
+ ipver: 1,
2948
3106
  },
2949
3107
  ]);
2950
3108
 
@@ -3146,13 +3304,15 @@ describe('plugin-meetings', () => {
3146
3304
  connectionType: 'udp',
3147
3305
  selectedCandidatePairChanges: 2,
3148
3306
  ipVersion: 'IPv6',
3307
+ ipver: 1,
3149
3308
  numTransports: 1,
3150
3309
  isMultistream: false,
3151
3310
  retriedWithTurnServer: true,
3152
3311
  isJoinWithMediaRetry: false,
3153
3312
  iceCandidatesCount: 0,
3154
- isSubnetReachable: null,
3155
- selectedCluster: null,
3313
+ subnet_reachable: null,
3314
+ selected_cluster: null,
3315
+ selected_subnet: null,
3156
3316
  },
3157
3317
  ]);
3158
3318
  meeting.roap.doTurnDiscovery;
@@ -3286,11 +3446,12 @@ describe('plugin-meetings', () => {
3286
3446
  meeting.mediaConnections = [
3287
3447
  {
3288
3448
  mediaAgentCluster: 'some.cluster',
3289
- }
3290
- ]
3449
+ },
3450
+ ];
3291
3451
  meeting.iceCandidatesCount = 3;
3292
3452
  meeting.iceCandidateErrors.set('701_error', 3);
3293
3453
  meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
3454
+ MeetingUtil.getIpVersion.returns(IP_VERSION.only_ipv6);
3294
3455
 
3295
3456
  await meeting.addMedia({
3296
3457
  mediaSettings: {},
@@ -3306,6 +3467,7 @@ describe('plugin-meetings', () => {
3306
3467
  connectionType: 'udp',
3307
3468
  selectedCandidatePairChanges: 2,
3308
3469
  ipVersion: 'IPv6',
3470
+ ipver: 6,
3309
3471
  numTransports: 1,
3310
3472
  isMultistream: false,
3311
3473
  retriedWithTurnServer: false,
@@ -3315,8 +3477,9 @@ describe('plugin-meetings', () => {
3315
3477
  iceCandidatesCount: 3,
3316
3478
  '701_error': 3,
3317
3479
  '701_turn_host_lookup_received_error': 1,
3318
- isSubnetReachable: null,
3319
- selectedCluster: 'some.cluster',
3480
+ subnet_reachable: null,
3481
+ selected_cluster: 'some.cluster',
3482
+ selected_subnet: null,
3320
3483
  }
3321
3484
  );
3322
3485
 
@@ -3379,9 +3542,11 @@ describe('plugin-meetings', () => {
3379
3542
  iceConnectionState: 'unknown',
3380
3543
  selectedCandidatePairChanges: 2,
3381
3544
  numTransports: 1,
3382
- isSubnetReachable: null,
3383
- selectedCluster: null,
3545
+ subnet_reachable: null,
3546
+ selected_cluster: null,
3547
+ selected_subnet: null,
3384
3548
  iceCandidatesCount: 0,
3549
+ ipver: 1,
3385
3550
  }
3386
3551
  );
3387
3552
 
@@ -3442,16 +3607,18 @@ describe('plugin-meetings', () => {
3442
3607
  numTransports: 1,
3443
3608
  '701_error': 2,
3444
3609
  '701_turn_host_lookup_received_error': 1,
3445
- isSubnetReachable: null,
3446
- selectedCluster: null,
3610
+ subnet_reachable: null,
3611
+ selected_cluster: null,
3612
+ selected_subnet: null,
3447
3613
  iceCandidatesCount: 0,
3614
+ ipver: 1,
3448
3615
  }
3449
3616
  );
3450
3617
 
3451
3618
  assert.isOk(errorThrown);
3452
3619
  });
3453
3620
 
3454
- it('should send valid isSubnetReachability if media connection success', async () => {
3621
+ it('should send subnet reachablity metrics if media connection success', async () => {
3455
3622
  meeting.roap.doTurnDiscovery = sinon.stub().returns({
3456
3623
  turnServerInfo: undefined,
3457
3624
  turnDiscoverySkippedReason: undefined,
@@ -3465,6 +3632,12 @@ describe('plugin-meetings', () => {
3465
3632
  stopReachability: sinon.stub(),
3466
3633
  isSubnetReachable: sinon.stub().returns(false),
3467
3634
  };
3635
+ meeting.mediaServerIp = '1.2.3.4';
3636
+ meeting.mediaConnections = [
3637
+ {
3638
+ mediaAgentCluster: 'some.cluster',
3639
+ },
3640
+ ];
3468
3641
 
3469
3642
  const forceRtcMetricsSend = sinon.stub().resolves();
3470
3643
  const closeMediaConnectionStub = sinon.stub();
@@ -3485,6 +3658,7 @@ describe('plugin-meetings', () => {
3485
3658
  locus_id: meeting.locusUrl.split('/').pop(),
3486
3659
  connectionType: 'udp',
3487
3660
  ipVersion: 'IPv6',
3661
+ ipver: 1,
3488
3662
  selectedCandidatePairChanges: 2,
3489
3663
  numTransports: 1,
3490
3664
  isMultistream: false,
@@ -3492,12 +3666,13 @@ describe('plugin-meetings', () => {
3492
3666
  isJoinWithMediaRetry: false,
3493
3667
  iceCandidatesCount: 0,
3494
3668
  reachability_public_udp_success: 5,
3495
- isSubnetReachable: false,
3496
- selectedCluster: null,
3669
+ subnet_reachable: false,
3670
+ selected_cluster: 'some.cluster',
3671
+ selected_subnet: '1.X.X.X',
3497
3672
  });
3498
3673
  });
3499
3674
 
3500
- it('should send valid isSubnetReachability if media connection fails', async () => {
3675
+ it('should send subnet reachablity metrics if media connection fails', async () => {
3501
3676
  let errorThrown = undefined;
3502
3677
 
3503
3678
  meeting.roap.doTurnDiscovery = sinon.stub().returns({
@@ -3513,6 +3688,12 @@ describe('plugin-meetings', () => {
3513
3688
  stopReachability: sinon.stub(),
3514
3689
  isSubnetReachable: sinon.stub().returns(true),
3515
3690
  };
3691
+ meeting.mediaServerIp = '1.2.3.4';
3692
+ meeting.mediaConnections = [
3693
+ {
3694
+ mediaAgentCluster: 'some.cluster',
3695
+ },
3696
+ ];
3516
3697
 
3517
3698
  const forceRtcMetricsSend = sinon.stub().resolves();
3518
3699
  const closeMediaConnectionStub = sinon.stub();
@@ -3554,9 +3735,11 @@ describe('plugin-meetings', () => {
3554
3735
  selectedCandidatePairChanges: 2,
3555
3736
  numTransports: 1,
3556
3737
  reachability_public_udp_success: 5,
3557
- isSubnetReachable: true,
3558
- selectedCluster: null,
3738
+ subnet_reachable: true,
3739
+ selected_cluster: 'some.cluster',
3740
+ selected_subnet: '1.X.X.X',
3559
3741
  iceCandidatesCount: 0,
3742
+ ipver: 1,
3560
3743
  }
3561
3744
  );
3562
3745
 
@@ -3862,13 +4045,14 @@ describe('plugin-meetings', () => {
3862
4045
  });
3863
4046
  });
3864
4047
 
3865
- it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
4048
+ it('counts the number of members that are in the meeting or lobby for MEDIA_QUALITY event', async () => {
3866
4049
  let fakeMembersCollection = {
3867
4050
  members: {
3868
- member1: {isInMeeting: true},
3869
- member2: {isInMeeting: true},
3870
- member3: {isInMeeting: false},
3871
- },
4051
+ member1: {isInMeeting: true, isInLobby: false},
4052
+ member2: {isInMeeting: false, isInLobby: true},
4053
+ member3: {isInMeeting: false, isInLobby: false},
4054
+ member4: {isInMeeting: true, isInLobby: false},
4055
+ }
3872
4056
  };
3873
4057
  sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
3874
4058
  const fakeData = {intervalMetadata: {}};
@@ -3886,11 +4070,12 @@ describe('plugin-meetings', () => {
3886
4070
  },
3887
4071
  payload: {
3888
4072
  intervals: [
3889
- sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
4073
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 3)),
3890
4074
  ],
3891
4075
  },
3892
4076
  });
3893
- fakeMembersCollection.members.member2.isInMeeting = false;
4077
+ // Move member2 from lobby to neither in meeting nor lobby
4078
+ fakeMembersCollection.members.member2.isInLobby = false;
3894
4079
 
3895
4080
  statsAnalyzerStub.emit(
3896
4081
  {file: 'test', function: 'test'},
@@ -3905,7 +4090,7 @@ describe('plugin-meetings', () => {
3905
4090
  },
3906
4091
  payload: {
3907
4092
  intervals: [
3908
- sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1)),
4093
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
3909
4094
  ],
3910
4095
  },
3911
4096
  });
@@ -4152,7 +4337,7 @@ describe('plugin-meetings', () => {
4152
4337
  meeting.deviceUrl = 'device url';
4153
4338
  meeting.selfId = 'self id';
4154
4339
  meeting.brbState = createBrbState(meeting, false);
4155
- meeting.brbState.enable = sinon.stub().resolves();
4340
+ sinon.stub(meeting.brbState, 'enable').resolves();
4156
4341
  });
4157
4342
 
4158
4343
  afterEach(() => {
@@ -4216,6 +4401,17 @@ describe('plugin-meetings', () => {
4216
4401
 
4217
4402
  assert.notCalled(meeting.audio.handleServerRemoteMuteUpdate);
4218
4403
  });
4404
+
4405
+ it('should reject when brb enable fails', async () => {
4406
+ meeting.brbState.enable.restore();
4407
+
4408
+ const error = new Error();
4409
+ meeting.meetingRequest.setBrb = sinon.stub().rejects(error);
4410
+
4411
+ await expect(meeting.beRightBack(true)).to.be.rejectedWith(error);
4412
+
4413
+ assert.isFalse(meeting.brbState.state.syncToServerInProgress);
4414
+ });
4219
4415
  });
4220
4416
  });
4221
4417
 
@@ -6362,25 +6558,36 @@ describe('plugin-meetings', () => {
6362
6558
  const DIAL_IN_URL = meeting.dialInUrl;
6363
6559
 
6364
6560
  assert.calledWith(meeting.meetingRequest.dialIn, {
6365
- correlationId: meeting.correlationId,
6561
+ correlationId: meeting.pstnCorrelationId,
6366
6562
  dialInUrl: DIAL_IN_URL,
6367
6563
  locusUrl: meeting.locusUrl,
6368
6564
  clientUrl: meeting.deviceUrl,
6369
6565
  });
6370
6566
  assert.notCalled(meeting.meetingRequest.dialOut);
6371
6567
 
6568
+ // Verify pstnCorrelationId was set
6569
+ assert.exists(meeting.pstnCorrelationId);
6570
+ assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
6571
+ const firstPstnCorrelationId = meeting.pstnCorrelationId
6572
+
6372
6573
  meeting.meetingRequest.dialIn.resetHistory();
6373
6574
 
6374
6575
  // try again. the dial in urls should match
6375
6576
  await meeting.usePhoneAudio();
6376
6577
 
6377
6578
  assert.calledWith(meeting.meetingRequest.dialIn, {
6378
- correlationId: meeting.correlationId,
6579
+ correlationId: meeting.pstnCorrelationId,
6379
6580
  dialInUrl: DIAL_IN_URL,
6380
6581
  locusUrl: meeting.locusUrl,
6381
6582
  clientUrl: meeting.deviceUrl,
6382
6583
  });
6383
6584
  assert.notCalled(meeting.meetingRequest.dialOut);
6585
+ // A new PSTN correlationId should be generated for the second attempt
6586
+ assert.notEqual(
6587
+ meeting.pstnCorrelationId,
6588
+ firstPstnCorrelationId,
6589
+ 'pstnCorrelationId should be regenerated on each dial-in attempt'
6590
+ );
6384
6591
  });
6385
6592
 
6386
6593
  it('given a phone number, triggers dial-out, delegating request to meetingRequest correctly', async () => {
@@ -6390,7 +6597,7 @@ describe('plugin-meetings', () => {
6390
6597
  const DIAL_OUT_URL = meeting.dialOutUrl;
6391
6598
 
6392
6599
  assert.calledWith(meeting.meetingRequest.dialOut, {
6393
- correlationId: meeting.correlationId,
6600
+ correlationId: meeting.pstnCorrelationId,
6394
6601
  dialOutUrl: DIAL_OUT_URL,
6395
6602
  locusUrl: meeting.locusUrl,
6396
6603
  clientUrl: meeting.deviceUrl,
@@ -6398,49 +6605,126 @@ describe('plugin-meetings', () => {
6398
6605
  });
6399
6606
  assert.notCalled(meeting.meetingRequest.dialIn);
6400
6607
 
6608
+ // Verify pstnCorrelationId was set
6609
+ assert.exists(meeting.pstnCorrelationId);
6610
+ assert.notEqual(meeting.pstnCorrelationId, meeting.correlationId);
6611
+ const firstPstnCorrelationId = meeting.pstnCorrelationId;
6612
+
6401
6613
  meeting.meetingRequest.dialOut.resetHistory();
6402
6614
 
6403
6615
  // try again. the dial out urls should match
6404
6616
  await meeting.usePhoneAudio(phoneNumber);
6405
6617
 
6406
6618
  assert.calledWith(meeting.meetingRequest.dialOut, {
6407
- correlationId: meeting.correlationId,
6619
+ correlationId: meeting.pstnCorrelationId,
6408
6620
  dialOutUrl: DIAL_OUT_URL,
6409
6621
  locusUrl: meeting.locusUrl,
6410
6622
  clientUrl: meeting.deviceUrl,
6411
6623
  phoneNumber,
6412
6624
  });
6413
6625
  assert.notCalled(meeting.meetingRequest.dialIn);
6626
+ // A new PSTN correlationId should be generated for the second attempt
6627
+ assert.notEqual(
6628
+ meeting.pstnCorrelationId,
6629
+ firstPstnCorrelationId,
6630
+ 'pstnCorrelationId should be regenerated on each dial-out attempt'
6631
+ );
6414
6632
  });
6415
6633
 
6416
- it('rejects if the request failed (dial in)', () => {
6417
- const error = 'something bad happened';
6634
+ it('rejects if the request failed (dial in)', async () => {
6635
+ const error = {error: {message: 'dial in failed'}, stack: 'error stack'};
6418
6636
 
6419
6637
  meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
6420
6638
 
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);
6639
+ try {
6640
+ await meeting.usePhoneAudio();
6641
+ throw new Error('Promise resolved when it should have rejected');
6642
+ } catch (e) {
6643
+ assert.equal(e, error);
6426
6644
 
6427
- return Promise.resolve();
6645
+ // Verify behavioral metric was sent with dial_in_correlation_id
6646
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
6647
+ correlation_id: meeting.correlationId,
6648
+ dial_in_url: meeting.dialInUrl,
6649
+ dial_in_correlation_id: sinon.match.string,
6650
+ locus_id: meeting.locusUrl.split('/').pop(),
6651
+ client_url: meeting.deviceUrl,
6652
+ reason: error.error.message,
6653
+ stack: error.stack,
6428
6654
  });
6655
+
6656
+ // Verify pstnCorrelationId was cleared after error
6657
+ assert.equal(meeting.pstnCorrelationId, undefined);
6658
+ }
6429
6659
  });
6430
6660
 
6431
6661
  it('rejects if the request failed (dial out)', async () => {
6432
- const error = 'something bad happened';
6662
+ const error = {error: {message: 'dial out failed'}, stack: 'error stack'};
6433
6663
 
6434
6664
  meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
6435
6665
 
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);
6666
+ try {
6667
+ await meeting.usePhoneAudio('+441234567890');
6668
+ throw new Error('Promise resolved when it should have rejected');
6669
+ } catch (e) {
6670
+ assert.equal(e, error);
6441
6671
 
6442
- return Promise.resolve();
6672
+ // Verify behavioral metric was sent with dial_out_correlation_id
6673
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
6674
+ correlation_id: meeting.correlationId,
6675
+ dial_out_url: meeting.dialOutUrl,
6676
+ dial_out_correlation_id: sinon.match.string,
6677
+ locus_id: meeting.locusUrl.split('/').pop(),
6678
+ client_url: meeting.deviceUrl,
6679
+ reason: error.error.message,
6680
+ stack: error.stack,
6443
6681
  });
6682
+
6683
+ // Verify pstnCorrelationId was cleared after error
6684
+ assert.equal(meeting.pstnCorrelationId, undefined);
6685
+ }
6686
+ });
6687
+ });
6688
+
6689
+ describe('#disconnectPhoneAudio', () => {
6690
+ beforeEach(() => {
6691
+ // Mock the MeetingUtil.disconnectPhoneAudio method
6692
+ sinon.stub(MeetingUtil, 'disconnectPhoneAudio').resolves();
6693
+ meeting.dialInUrl = 'dialin:///test-dial-in-url';
6694
+ meeting.dialOutUrl = 'dialout:///test-dial-out-url';
6695
+ meeting.dialInDeviceStatus = 'JOINED';
6696
+ meeting.dialOutDeviceStatus = 'JOINED';
6697
+ });
6698
+
6699
+ afterEach(() => {
6700
+ MeetingUtil.disconnectPhoneAudio.restore();
6701
+ });
6702
+
6703
+ it('should disconnect phone audio and clear pstnCorrelationId', async () => {
6704
+ meeting.pstnCorrelationId = 'test-pstn-correlation-id';
6705
+
6706
+ await meeting.disconnectPhoneAudio();
6707
+
6708
+ // Verify that pstnCorrelationId is cleared
6709
+ assert.equal(meeting.pstnCorrelationId, undefined);
6710
+
6711
+ // Verify that MeetingUtil.disconnectPhoneAudio was called for both dial-in and dial-out
6712
+ assert.calledTwice(MeetingUtil.disconnectPhoneAudio);
6713
+ assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialInUrl);
6714
+ assert.calledWith(MeetingUtil.disconnectPhoneAudio, meeting, meeting.dialOutUrl);
6715
+ });
6716
+
6717
+ it('should handle case when no PSTN connection is active', async () => {
6718
+ meeting.dialInDeviceStatus = 'IDLE';
6719
+ meeting.dialOutDeviceStatus = 'IDLE';
6720
+ meeting.pstnCorrelationId = 'test-pstn-correlation-id';
6721
+
6722
+ await meeting.disconnectPhoneAudio();
6723
+
6724
+ // Verify that pstnCorrelationId is still cleared even when no phone connection is active
6725
+ assert.equal(meeting.pstnCorrelationId, undefined);
6726
+ // And verify no disconnect was attempted
6727
+ assert.notCalled(MeetingUtil.disconnectPhoneAudio);
6444
6728
  });
6445
6729
  });
6446
6730
 
@@ -7957,6 +8241,7 @@ describe('plugin-meetings', () => {
7957
8241
 
7958
8242
  meeting.requestScreenShareFloor = sinon.stub().resolves({});
7959
8243
  meeting.releaseScreenShareFloor = sinon.stub().resolves({});
8244
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
7960
8245
  meeting.mediaProperties.mediaDirection = {
7961
8246
  sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
7962
8247
  sendVideo: 'fake value',
@@ -8038,6 +8323,12 @@ describe('plugin-meetings', () => {
8038
8323
  payload: {mediaType: 'share', shareInstanceId: meeting.localShareInstanceId},
8039
8324
  options: {meetingId: meeting.id},
8040
8325
  });
8326
+
8327
+ // ensure the share start timestamp is saved
8328
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
8329
+ key: 'internal.client.share.initiated',
8330
+ });
8331
+
8041
8332
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
8042
8333
 
8043
8334
  assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
@@ -8056,6 +8347,11 @@ describe('plugin-meetings', () => {
8056
8347
  options: {meetingId: meeting.id},
8057
8348
  });
8058
8349
 
8350
+ // ensure the share start timestamp is saved
8351
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
8352
+ key: 'internal.client.share.initiated',
8353
+ });
8354
+
8059
8355
  assert.calledWith(
8060
8356
  meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
8061
8357
  stream
@@ -10011,6 +10307,24 @@ describe('plugin-meetings', () => {
10011
10307
  );
10012
10308
  });
10013
10309
 
10310
+ it('listens to CONTROLS_POLLING_QA_CHANGED', async () => {
10311
+ const state = {example: 'value'};
10312
+
10313
+ await meeting.locusInfo.emitScoped(
10314
+ {function: 'test', file: 'test'},
10315
+ LOCUSINFO.EVENTS.CONTROLS_POLLING_QA_CHANGED,
10316
+ {state}
10317
+ );
10318
+
10319
+ assert.calledWith(
10320
+ TriggerProxy.trigger,
10321
+ meeting,
10322
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10323
+ EVENT_TRIGGERS.MEETING_CONTROLS_POLLING_QA_UPDATED,
10324
+ {state}
10325
+ );
10326
+ });
10327
+
10014
10328
  it('listens to the locus interpretation update event', () => {
10015
10329
  const interpretation = {
10016
10330
  siLanguages: [{languageCode: 20, languageName: 'en'}],
@@ -10311,6 +10625,8 @@ describe('plugin-meetings', () => {
10311
10625
  meeting.mediaProperties = {mediaDirection: {sendShare: true}};
10312
10626
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
10313
10627
  (meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
10628
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
10629
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
10314
10630
  });
10315
10631
  it('should call changeMeetingFloor()', async () => {
10316
10632
  meeting.screenShareFloorState = 'GRANTED';
@@ -10328,6 +10644,22 @@ describe('plugin-meetings', () => {
10328
10644
  assert.exists(share.then);
10329
10645
  await share;
10330
10646
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
10647
+
10648
+ // ensure the share stop timestamp is saved
10649
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
10650
+ key: 'internal.client.share.stopped',
10651
+ });
10652
+
10653
+ // ensure the CA share stopped metric is submitted with duration
10654
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
10655
+ name: 'client.share.stopped',
10656
+ payload: {
10657
+ mediaType: 'share',
10658
+ shareInstanceId: meeting.localShareInstanceId,
10659
+ shareDuration: 1000,
10660
+ },
10661
+ options: {meetingId: meeting.id},
10662
+ });
10331
10663
  });
10332
10664
  it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
10333
10665
  // change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
@@ -11603,6 +11935,14 @@ describe('plugin-meetings', () => {
11603
11935
  requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
11604
11936
  displayHints: userDisplayHints,
11605
11937
  });
11938
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11939
+ requiredHints: [DISPLAY_HINTS.ENABLE_ATTENDEE_START_POLLING_QA],
11940
+ displayHints: userDisplayHints,
11941
+ });
11942
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11943
+ requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
11944
+ displayHints: userDisplayHints,
11945
+ });
11606
11946
 
11607
11947
  assert.calledWith(
11608
11948
  TriggerProxy.trigger,
@@ -11892,6 +12232,7 @@ describe('plugin-meetings', () => {
11892
12232
  meeting.locusInfo.self = {url: url1};
11893
12233
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
11894
12234
  meeting.deviceUrl = 'deviceUrl.com';
12235
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
11895
12236
  });
11896
12237
  it('should have #startWhiteboardShare', () => {
11897
12238
  assert.exists(meeting.startWhiteboardShare);
@@ -11919,6 +12260,11 @@ describe('plugin-meetings', () => {
11919
12260
  payload: {mediaType: 'whiteboard'},
11920
12261
  options: {meetingId: meeting.id},
11921
12262
  });
12263
+
12264
+ // ensure the share start timestamp is saved
12265
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
12266
+ key: 'internal.client.share.initiated',
12267
+ });
11922
12268
  });
11923
12269
  });
11924
12270
  describe('#stopWhiteboardShare', () => {
@@ -11930,6 +12276,9 @@ describe('plugin-meetings', () => {
11930
12276
  meeting.locusInfo.self = {url: url1};
11931
12277
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
11932
12278
  meeting.deviceUrl = 'deviceUrl.com';
12279
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
12280
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
12281
+ webex.internal.newMetrics.submitClientEvent = sinon.stub();
11933
12282
  });
11934
12283
  it('should stop the whiteboard share', async () => {
11935
12284
  const whiteboardShare = meeting.stopWhiteboardShare();
@@ -11944,6 +12293,21 @@ describe('plugin-meetings', () => {
11944
12293
  uri: url1,
11945
12294
  });
11946
12295
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
12296
+
12297
+ // ensure the share stop timestamp is saved
12298
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
12299
+ key: 'internal.client.share.stopped',
12300
+ });
12301
+
12302
+ // ensure the CA share stopped metric is submitted with duration
12303
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
12304
+ name: 'client.share.stopped',
12305
+ payload: {
12306
+ mediaType: 'whiteboard',
12307
+ shareDuration: 1000,
12308
+ },
12309
+ options: {meetingId: meeting.id},
12310
+ });
11947
12311
  });
11948
12312
  });
11949
12313
  });
@@ -12016,6 +12380,9 @@ describe('plugin-meetings', () => {
12016
12380
  meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
12017
12381
  meeting.deviceUrl = 'my-web-url';
12018
12382
  meeting.locusInfo.info = {isWebinar: false};
12383
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
12384
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1500);
12385
+ webex.internal.newMetrics.submitClientEvent = sinon.stub();
12019
12386
  });
12020
12387
 
12021
12388
  const USER_IDS = {
@@ -12242,7 +12609,7 @@ describe('plugin-meetings', () => {
12242
12609
  activeSharingId.whiteboard = beneficiaryId;
12243
12610
 
12244
12611
  eventTrigger.share.push(
12245
- meeting.webinar.selfIsAttendee
12612
+ meeting.webinar.selfIsAttendee || meeting.guest
12246
12613
  ? {
12247
12614
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12248
12615
  functionName: 'remoteShare',
@@ -12261,7 +12628,8 @@ describe('plugin-meetings', () => {
12261
12628
  }
12262
12629
  );
12263
12630
 
12264
- shareStatus = meeting.webinar.selfIsAttendee
12631
+ shareStatus =
12632
+ meeting.webinar.selfIsAttendee || meeting.guest
12265
12633
  ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12266
12634
  : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
12267
12635
  }
@@ -12479,6 +12847,36 @@ describe('plugin-meetings', () => {
12479
12847
  });
12480
12848
  });
12481
12849
 
12850
+ describe('Whiteboard Share - User is guest', () => {
12851
+ it('User receives a remote share instead of whiteboard share', () => {
12852
+ // Set the guest flag
12853
+ meeting.guest = true;
12854
+
12855
+ // Step 1: Start sharing whiteboard A
12856
+ const data1 = generateData(
12857
+ blankPayload, // Initial payload
12858
+ true, // isGranting: Granting share
12859
+ false, // isContent: Whiteboard (not content)
12860
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
12861
+ RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
12862
+ );
12863
+
12864
+ // Step 2: Stop sharing whiteboard A
12865
+ const data2 = generateData(
12866
+ data1.payload, // Updated payload from Step 1
12867
+ false, // isGranting: Stopping share
12868
+ false, // isContent: Whiteboard
12869
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
12870
+ );
12871
+
12872
+ // Validate the payload changes and status updates
12873
+ payloadTestHelper([data1]);
12874
+
12875
+ // Specific assertions for guest
12876
+ assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
12877
+ });
12878
+ });
12879
+
12482
12880
  describe('Whiteboard A --> Whiteboard B', () => {
12483
12881
  it('Scenario #1: you share both whiteboards', () => {
12484
12882
  const data1 = generateData(
@@ -13130,7 +13528,54 @@ describe('plugin-meetings', () => {
13130
13528
  payloadTestHelper([data1, data2, data3]);
13131
13529
  });
13132
13530
  });
13133
- });
13531
+
13532
+ it('should send share stopped metric when whiteboard sharing stops', () => {
13533
+ // Start whiteboard sharing (this won't trigger metrics)
13534
+ const data1 = generateData(
13535
+ blankPayload,
13536
+ true, // isGranting: true
13537
+ false, // isContent: false (whiteboard)
13538
+ USER_IDS.ME,
13539
+ RESOURCE_URLS.WHITEBOARD_A
13540
+ );
13541
+
13542
+ // Stop whiteboard sharing (this should trigger metrics)
13543
+ const data2 = generateData(
13544
+ data1.payload,
13545
+ false, // isGranting: false (stopping share)
13546
+ false, // isContent: false (whiteboard)
13547
+ USER_IDS.ME
13548
+ );
13549
+
13550
+ // Trigger the events
13551
+ meeting.locusInfo.emit(
13552
+ {function: 'test', file: 'test'},
13553
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
13554
+ data1.payload
13555
+ );
13556
+
13557
+ meeting.locusInfo.emit(
13558
+ {function: 'test', file: 'test'},
13559
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
13560
+ data2.payload
13561
+ );
13562
+
13563
+ // Verify metrics were called when whiteboard sharing stopped
13564
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
13565
+ key: 'internal.client.share.stopped',
13566
+ });
13567
+
13568
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
13569
+ name: 'client.share.stopped',
13570
+ payload: {
13571
+ mediaType: 'whiteboard',
13572
+ shareDuration: 1500, // mocked return value
13573
+ },
13574
+ options: {
13575
+ meetingId: meeting.id,
13576
+ },
13577
+ });
13578
+ });
13134
13579
 
13135
13580
  describe('handleShareVideoStreamMuteStateChange', () => {
13136
13581
  it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
@@ -13157,6 +13602,7 @@ describe('plugin-meetings', () => {
13157
13602
  });
13158
13603
  });
13159
13604
  });
13605
+ });
13160
13606
 
13161
13607
  describe('#startKeepAlive', () => {
13162
13608
  let clock;
@@ -13923,4 +14369,443 @@ describe('plugin-meetings', () => {
13923
14369
  assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
13924
14370
  });
13925
14371
  });
14372
+
14373
+ describe('#setStage', () => {
14374
+ const check = async (options, expectedVideoLayout) => {
14375
+ const locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${uuidv4()}`;
14376
+ meeting.locusUrl = locusUrl;
14377
+
14378
+ const setStagePromise = meeting.setStage(options);
14379
+
14380
+ assert.exists(setStagePromise.then);
14381
+ await setStagePromise;
14382
+
14383
+ assert.calledOnceWithExactly(
14384
+ meeting.meetingRequest.synchronizeStage,
14385
+ locusUrl,
14386
+ expectedVideoLayout
14387
+ );
14388
+ };
14389
+
14390
+ beforeEach(() => {
14391
+ meeting.meetingRequest.synchronizeStage = sinon.stub().returns(Promise.resolve());
14392
+ });
14393
+
14394
+ it('sends the expected request when no options are provided', async () => {
14395
+ await check(undefined, {
14396
+ overrideDefault: true,
14397
+ lockAttendeeViewOnStageOnly: false,
14398
+ stageParameters: {
14399
+ activeSpeakerProportion: 0.5,
14400
+ showActiveSpeaker: {show: false, order: 0},
14401
+ stageManagerType: 0,
14402
+ },
14403
+ });
14404
+ });
14405
+
14406
+ it('sends the expected request when empty options are provided', async () => {
14407
+ await check(
14408
+ {},
14409
+ {
14410
+ overrideDefault: true,
14411
+ lockAttendeeViewOnStageOnly: false,
14412
+ stageParameters: {
14413
+ activeSpeakerProportion: 0.5,
14414
+ showActiveSpeaker: {show: false, order: 0},
14415
+ stageManagerType: 0,
14416
+ },
14417
+ }
14418
+ );
14419
+ });
14420
+
14421
+ [0.25, 0.5, 0.75].forEach((activeSpeakerProportion) => {
14422
+ it(`sends the expected request when only the active speaker proportion option is provided as ${activeSpeakerProportion}`, async () => {
14423
+ await check(
14424
+ {activeSpeakerProportion},
14425
+ {
14426
+ overrideDefault: true,
14427
+ lockAttendeeViewOnStageOnly: false,
14428
+ stageParameters: {
14429
+ activeSpeakerProportion,
14430
+ showActiveSpeaker: {show: false, order: 0},
14431
+ stageManagerType: 0,
14432
+ },
14433
+ }
14434
+ );
14435
+ });
14436
+ });
14437
+
14438
+ it('sends the expected request when only the custom background option is provided', async () => {
14439
+ const customBackground = {
14440
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14441
+ };
14442
+
14443
+ await check(
14444
+ {customBackground},
14445
+ {
14446
+ overrideDefault: true,
14447
+ lockAttendeeViewOnStageOnly: false,
14448
+ stageParameters: {
14449
+ activeSpeakerProportion: 0.5,
14450
+ showActiveSpeaker: {show: false, order: 0},
14451
+ stageManagerType: 2,
14452
+ },
14453
+ customLayouts: {background: customBackground},
14454
+ }
14455
+ );
14456
+ });
14457
+
14458
+ it('sends the expected request when only the custom logo option is provided', async () => {
14459
+ const customLogo = {
14460
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14461
+ position: 'LowerRight',
14462
+ };
14463
+
14464
+ await check(
14465
+ {customLogo},
14466
+ {
14467
+ overrideDefault: true,
14468
+ lockAttendeeViewOnStageOnly: false,
14469
+ stageParameters: {
14470
+ activeSpeakerProportion: 0.5,
14471
+ showActiveSpeaker: {show: false, order: 0},
14472
+ stageManagerType: 1,
14473
+ },
14474
+ customLayouts: {logo: customLogo},
14475
+ }
14476
+ );
14477
+ });
14478
+
14479
+ it('sends the expected request when only the custom name label option is provided', async () => {
14480
+ const customNameLabel = {
14481
+ accentColor: '#0A7806',
14482
+ background: {color: 'rgba(255, 255, 255, 1)'},
14483
+ border: {color: 'rgba(255, 255, 255, 1)'},
14484
+ content: {
14485
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14486
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14487
+ },
14488
+ decoration: {color: 'rgba(10, 120, 6, 1)'},
14489
+ fadeOut: {delay: 15},
14490
+ type: 'Primary',
14491
+ };
14492
+
14493
+ await check(
14494
+ {customNameLabel},
14495
+ {
14496
+ overrideDefault: true,
14497
+ lockAttendeeViewOnStageOnly: false,
14498
+ stageParameters: {
14499
+ activeSpeakerProportion: 0.5,
14500
+ showActiveSpeaker: {show: false, order: 0},
14501
+ stageManagerType: 4,
14502
+ },
14503
+ nameLabelStyle: customNameLabel,
14504
+ }
14505
+ );
14506
+ });
14507
+
14508
+ it('sends the expected request when only the custom background and logo options are provided', async () => {
14509
+ const customBackground = {
14510
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14511
+ };
14512
+ const customLogo = {
14513
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14514
+ position: 'UpperRight',
14515
+ };
14516
+
14517
+ await check(
14518
+ {customBackground, customLogo},
14519
+ {
14520
+ overrideDefault: true,
14521
+ lockAttendeeViewOnStageOnly: false,
14522
+ stageParameters: {
14523
+ activeSpeakerProportion: 0.5,
14524
+ showActiveSpeaker: {show: false, order: 0},
14525
+ stageManagerType: 3,
14526
+ },
14527
+ customLayouts: {background: customBackground, logo: customLogo},
14528
+ }
14529
+ );
14530
+ });
14531
+
14532
+ it('sends the expected request when only the custom background and name label options are provided', async () => {
14533
+ const customBackground = {
14534
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14535
+ };
14536
+ const customNameLabel = {
14537
+ accentColor: '#00A3FF',
14538
+ background: {color: 'rgba(0, 163, 255, 1)'},
14539
+ border: {color: 'rgba(0, 163, 255, 1)'},
14540
+ content: {
14541
+ displayName: {color: 'rgba(255, 255, 255, 0.95)'},
14542
+ subtitle: {color: 'rgba(255, 255, 255, 0.7)'},
14543
+ },
14544
+ decoration: {color: 'rgba(255, 255, 255, 0.95)'},
14545
+ fadeOut: {delay: 15},
14546
+ type: 'PrimaryInverted',
14547
+ };
14548
+
14549
+ await check(
14550
+ {customBackground, customNameLabel},
14551
+ {
14552
+ overrideDefault: true,
14553
+ lockAttendeeViewOnStageOnly: false,
14554
+ stageParameters: {
14555
+ activeSpeakerProportion: 0.5,
14556
+ showActiveSpeaker: {show: false, order: 0},
14557
+ stageManagerType: 6,
14558
+ },
14559
+ customLayouts: {background: customBackground},
14560
+ nameLabelStyle: customNameLabel,
14561
+ }
14562
+ );
14563
+ });
14564
+
14565
+ it('sends the expected request when only the custom logo and name label options are provided', async () => {
14566
+ const customLogo = {
14567
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14568
+ position: 'UpperLeft',
14569
+ };
14570
+ const customNameLabel = {
14571
+ accentColor: '#942B2B',
14572
+ background: {color: 'rgba(255, 255, 255, 1)'},
14573
+ border: {color: 'rgba(148, 43, 43, 1)'},
14574
+ content: {
14575
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14576
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14577
+ },
14578
+ decoration: {color: 'rgba(0, 0, 0, 0)'},
14579
+ fadeOut: {delay: 15},
14580
+ type: 'Secondary',
14581
+ };
14582
+
14583
+ await check(
14584
+ {customLogo, customNameLabel},
14585
+ {
14586
+ overrideDefault: true,
14587
+ lockAttendeeViewOnStageOnly: false,
14588
+ stageParameters: {
14589
+ activeSpeakerProportion: 0.5,
14590
+ showActiveSpeaker: {show: false, order: 0},
14591
+ stageManagerType: 5,
14592
+ },
14593
+ customLayouts: {logo: customLogo},
14594
+ nameLabelStyle: customNameLabel,
14595
+ }
14596
+ );
14597
+ });
14598
+
14599
+ it('sends the expected request when only the custom background, logo, name label options are provided', async () => {
14600
+ const customBackground = {
14601
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14602
+ };
14603
+ const customLogo = {
14604
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14605
+ position: 'LowerLeft',
14606
+ };
14607
+ const customNameLabel = {
14608
+ accentColor: '#EBD960',
14609
+ background: {color: 'rgba(235, 217, 96, 0.55)'},
14610
+ border: {color: 'rgba(235, 217, 96, 0.55)'},
14611
+ content: {
14612
+ displayName: {color: 'rgba(255, 255, 255, 0.95)'},
14613
+ subtitle: {color: 'rgba(255, 255, 255, 0.7)'},
14614
+ },
14615
+ decoration: {color: 'rgba(0, 0, 0, 0)'},
14616
+ fadeOut: {delay: 15},
14617
+ type: 'SecondaryInverted',
14618
+ };
14619
+
14620
+ await check(
14621
+ {customBackground, customLogo, customNameLabel},
14622
+ {
14623
+ overrideDefault: true,
14624
+ lockAttendeeViewOnStageOnly: false,
14625
+ stageParameters: {
14626
+ activeSpeakerProportion: 0.5,
14627
+ showActiveSpeaker: {show: false, order: 0},
14628
+ stageManagerType: 7,
14629
+ },
14630
+ customLayouts: {background: customBackground, logo: customLogo},
14631
+ nameLabelStyle: customNameLabel,
14632
+ }
14633
+ );
14634
+ });
14635
+
14636
+ it('sends the expected request when only the important participants option is provided as empty', async () => {
14637
+ await check(
14638
+ {importantParticipants: []},
14639
+ {
14640
+ overrideDefault: true,
14641
+ lockAttendeeViewOnStageOnly: false,
14642
+ stageParameters: {
14643
+ activeSpeakerProportion: 0.5,
14644
+ showActiveSpeaker: {show: false, order: 0},
14645
+ stageManagerType: 0,
14646
+ },
14647
+ }
14648
+ );
14649
+ });
14650
+
14651
+ it('sends the expected request when only the important participants option is provided as populated', async () => {
14652
+ const importantParticipants = [
14653
+ {mainCsi: 11111111, participantId: uuidv4()},
14654
+ {mainCsi: 22222222, participantId: uuidv4()},
14655
+ {mainCsi: 33333333, participantId: uuidv4()},
14656
+ {mainCsi: 44444444, participantId: uuidv4()},
14657
+ {mainCsi: 55555555, participantId: uuidv4()},
14658
+ {mainCsi: 66666666, participantId: uuidv4()},
14659
+ {mainCsi: 77777777, participantId: uuidv4()},
14660
+ {mainCsi: 88888888, participantId: uuidv4()},
14661
+ ];
14662
+
14663
+ await check(
14664
+ {importantParticipants},
14665
+ {
14666
+ overrideDefault: true,
14667
+ lockAttendeeViewOnStageOnly: false,
14668
+ stageParameters: {
14669
+ activeSpeakerProportion: 0.5,
14670
+ importantParticipants: [
14671
+ {...importantParticipants[0], order: 1},
14672
+ {...importantParticipants[1], order: 2},
14673
+ {...importantParticipants[2], order: 3},
14674
+ {...importantParticipants[3], order: 4},
14675
+ {...importantParticipants[4], order: 5},
14676
+ {...importantParticipants[5], order: 6},
14677
+ {...importantParticipants[6], order: 7},
14678
+ {...importantParticipants[7], order: 8},
14679
+ ],
14680
+ showActiveSpeaker: {show: false, order: 0},
14681
+ stageManagerType: 0,
14682
+ },
14683
+ }
14684
+ );
14685
+ });
14686
+
14687
+ [false, true].forEach((lockAttendeeViewOnStage) => {
14688
+ it(`sends the expected request when only the lock attendee view on stage option is provided as ${lockAttendeeViewOnStage}`, async () => {
14689
+ await check(
14690
+ {lockAttendeeViewOnStage},
14691
+ {
14692
+ overrideDefault: true,
14693
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
14694
+ stageParameters: {
14695
+ activeSpeakerProportion: 0.5,
14696
+ showActiveSpeaker: {show: false, order: 0},
14697
+ stageManagerType: 0,
14698
+ },
14699
+ }
14700
+ );
14701
+ });
14702
+ });
14703
+
14704
+ [false, true].forEach((showActiveSpeaker) => {
14705
+ it(`sends the expected request when only the show active speaker option is provided as ${showActiveSpeaker}`, async () => {
14706
+ await check(
14707
+ {showActiveSpeaker},
14708
+ {
14709
+ overrideDefault: true,
14710
+ lockAttendeeViewOnStageOnly: false,
14711
+ stageParameters: {
14712
+ activeSpeakerProportion: 0.5,
14713
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
14714
+ stageManagerType: 0,
14715
+ },
14716
+ }
14717
+ );
14718
+ });
14719
+ });
14720
+
14721
+ it('sends the expected request when all options are provided', async () => {
14722
+ const activeSpeakerProportion = 0.6;
14723
+ const customBackground = {
14724
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14725
+ };
14726
+ const customLogo = {
14727
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14728
+ position: 'UpperMiddle',
14729
+ };
14730
+ const customNameLabel = {
14731
+ accentColor: '#0A7806',
14732
+ background: {color: 'rgba(255, 255, 255, 1)'},
14733
+ border: {color: 'rgba(255, 255, 255, 1)'},
14734
+ content: {
14735
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14736
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14737
+ },
14738
+ decoration: {color: 'rgba(10, 120, 6, 1)'},
14739
+ fadeOut: {delay: 15},
14740
+ type: 'Primary',
14741
+ };
14742
+ const importantParticipants = [
14743
+ {mainCsi: 11111111, participantId: uuidv4()},
14744
+ {mainCsi: 22222222, participantId: uuidv4()},
14745
+ {mainCsi: 33333333, participantId: uuidv4()},
14746
+ {mainCsi: 44444444, participantId: uuidv4()},
14747
+ {mainCsi: 55555555, participantId: uuidv4()},
14748
+ {mainCsi: 66666666, participantId: uuidv4()},
14749
+ {mainCsi: 77777777, participantId: uuidv4()},
14750
+ {mainCsi: 88888888, participantId: uuidv4()},
14751
+ ];
14752
+ const lockAttendeeViewOnStage = true;
14753
+ const showActiveSpeaker = true;
14754
+
14755
+ await check(
14756
+ {
14757
+ activeSpeakerProportion,
14758
+ customBackground,
14759
+ customLogo,
14760
+ customNameLabel,
14761
+ importantParticipants,
14762
+ lockAttendeeViewOnStage,
14763
+ showActiveSpeaker,
14764
+ },
14765
+ {
14766
+ overrideDefault: true,
14767
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
14768
+ stageParameters: {
14769
+ activeSpeakerProportion,
14770
+ importantParticipants: [
14771
+ {...importantParticipants[0], order: 1},
14772
+ {...importantParticipants[1], order: 2},
14773
+ {...importantParticipants[2], order: 3},
14774
+ {...importantParticipants[3], order: 4},
14775
+ {...importantParticipants[4], order: 5},
14776
+ {...importantParticipants[5], order: 6},
14777
+ {...importantParticipants[6], order: 7},
14778
+ {...importantParticipants[7], order: 8},
14779
+ ],
14780
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
14781
+ stageManagerType: 7,
14782
+ },
14783
+ customLayouts: {background: customBackground, logo: customLogo},
14784
+ nameLabelStyle: customNameLabel,
14785
+ }
14786
+ );
14787
+ });
14788
+ });
14789
+
14790
+ describe('#unsetStage', () => {
14791
+ beforeEach(() => {
14792
+ meeting.meetingRequest.synchronizeStage = sinon.stub().returns(Promise.resolve());
14793
+ });
14794
+
14795
+ it('sends the expected request', async () => {
14796
+ const locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${uuidv4()}`;
14797
+ meeting.locusUrl = locusUrl;
14798
+
14799
+ const unsetStagePromise = meeting.unsetStage();
14800
+
14801
+ assert.exists(unsetStagePromise.then);
14802
+ await unsetStagePromise;
14803
+
14804
+ assert.calledOnceWithExactly(
14805
+ meeting.meetingRequest.synchronizeStage,
14806
+ locusUrl,
14807
+ {overrideDefault: false}
14808
+ );
14809
+ });
14810
+ });
13926
14811
  });