@webex/plugin-meetings 3.0.0-beta.3 → 3.0.0-beta.30

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 (493) hide show
  1. package/UPGRADING.md +9 -9
  2. package/browsers.js +19 -24
  3. package/dist/breakouts/breakout.js +137 -0
  4. package/dist/breakouts/breakout.js.map +1 -0
  5. package/dist/breakouts/collection.js +23 -0
  6. package/dist/breakouts/collection.js.map +1 -0
  7. package/dist/breakouts/index.js +374 -0
  8. package/dist/breakouts/index.js.map +1 -0
  9. package/dist/breakouts/request.js +78 -0
  10. package/dist/breakouts/request.js.map +1 -0
  11. package/dist/breakouts/utils.js +23 -0
  12. package/dist/breakouts/utils.js.map +1 -0
  13. package/dist/common/browser-detection.js +1 -20
  14. package/dist/common/browser-detection.js.map +1 -1
  15. package/dist/common/collection.js +5 -20
  16. package/dist/common/collection.js.map +1 -1
  17. package/dist/common/config.js +0 -7
  18. package/dist/common/config.js.map +1 -1
  19. package/dist/common/errors/captcha-error.js +10 -24
  20. package/dist/common/errors/captcha-error.js.map +1 -1
  21. package/dist/common/errors/intent-to-join.js +11 -24
  22. package/dist/common/errors/intent-to-join.js.map +1 -1
  23. package/dist/common/errors/join-meeting.js +12 -25
  24. package/dist/common/errors/join-meeting.js.map +1 -1
  25. package/dist/common/errors/media.js +10 -24
  26. package/dist/common/errors/media.js.map +1 -1
  27. package/dist/common/errors/parameter.js +5 -33
  28. package/dist/common/errors/parameter.js.map +1 -1
  29. package/dist/common/errors/password-error.js +10 -24
  30. package/dist/common/errors/password-error.js.map +1 -1
  31. package/dist/common/errors/permission.js +9 -23
  32. package/dist/common/errors/permission.js.map +1 -1
  33. package/dist/common/errors/reconnection-in-progress.js +0 -17
  34. package/dist/common/errors/reconnection-in-progress.js.map +1 -1
  35. package/dist/common/errors/reconnection.js +10 -24
  36. package/dist/common/errors/reconnection.js.map +1 -1
  37. package/dist/common/errors/stats.js +10 -24
  38. package/dist/common/errors/stats.js.map +1 -1
  39. package/dist/common/errors/webex-errors.js +6 -41
  40. package/dist/common/errors/webex-errors.js.map +1 -1
  41. package/dist/common/errors/webex-meetings-error.js +5 -25
  42. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  43. package/dist/common/events/events-scope.js +0 -22
  44. package/dist/common/events/events-scope.js.map +1 -1
  45. package/dist/common/events/events.js +0 -23
  46. package/dist/common/events/events.js.map +1 -1
  47. package/dist/common/events/trigger-proxy.js +0 -12
  48. package/dist/common/events/trigger-proxy.js.map +1 -1
  49. package/dist/common/events/util.js +0 -15
  50. package/dist/common/events/util.js.map +1 -1
  51. package/dist/common/logs/logger-config.js +0 -4
  52. package/dist/common/logs/logger-config.js.map +1 -1
  53. package/dist/common/logs/logger-proxy.js +1 -8
  54. package/dist/common/logs/logger-proxy.js.map +1 -1
  55. package/dist/common/logs/request.js +37 -60
  56. package/dist/common/logs/request.js.map +1 -1
  57. package/dist/common/queue.js +4 -14
  58. package/dist/common/queue.js.map +1 -1
  59. package/dist/config.js +6 -6
  60. package/dist/config.js.map +1 -1
  61. package/dist/constants.js +92 -49
  62. package/dist/constants.js.map +1 -1
  63. package/dist/controls-options-manager/constants.js +14 -0
  64. package/dist/controls-options-manager/constants.js.map +1 -0
  65. package/dist/controls-options-manager/enums.js +15 -0
  66. package/dist/controls-options-manager/enums.js.map +1 -0
  67. package/dist/controls-options-manager/index.js +203 -0
  68. package/dist/controls-options-manager/index.js.map +1 -0
  69. package/dist/controls-options-manager/util.js +28 -0
  70. package/dist/controls-options-manager/util.js.map +1 -0
  71. package/dist/index.js +4 -18
  72. package/dist/index.js.map +1 -1
  73. package/dist/locus-info/controlsUtils.js +12 -29
  74. package/dist/locus-info/controlsUtils.js.map +1 -1
  75. package/dist/locus-info/embeddedAppsUtils.js +3 -26
  76. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  77. package/dist/locus-info/fullState.js +0 -15
  78. package/dist/locus-info/fullState.js.map +1 -1
  79. package/dist/locus-info/hostUtils.js +4 -12
  80. package/dist/locus-info/hostUtils.js.map +1 -1
  81. package/dist/locus-info/index.js +186 -192
  82. package/dist/locus-info/index.js.map +1 -1
  83. package/dist/locus-info/infoUtils.js +3 -37
  84. package/dist/locus-info/infoUtils.js.map +1 -1
  85. package/dist/locus-info/mediaSharesUtils.js +12 -38
  86. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  87. package/dist/locus-info/parser.js +92 -118
  88. package/dist/locus-info/parser.js.map +1 -1
  89. package/dist/locus-info/selfUtils.js +34 -91
  90. package/dist/locus-info/selfUtils.js.map +1 -1
  91. package/dist/media/index.js +64 -137
  92. package/dist/media/index.js.map +1 -1
  93. package/dist/media/properties.js +80 -114
  94. package/dist/media/properties.js.map +1 -1
  95. package/dist/media/util.js +2 -9
  96. package/dist/media/util.js.map +1 -1
  97. package/dist/mediaQualityMetrics/config.js +505 -495
  98. package/dist/mediaQualityMetrics/config.js.map +1 -1
  99. package/dist/meeting/effectsState.js +125 -190
  100. package/dist/meeting/effectsState.js.map +1 -1
  101. package/dist/meeting/in-meeting-actions.js +13 -14
  102. package/dist/meeting/in-meeting-actions.js.map +1 -1
  103. package/dist/meeting/index.js +2142 -2101
  104. package/dist/meeting/index.js.map +1 -1
  105. package/dist/meeting/muteState.js +39 -80
  106. package/dist/meeting/muteState.js.map +1 -1
  107. package/dist/meeting/request.js +224 -230
  108. package/dist/meeting/request.js.map +1 -1
  109. package/dist/meeting/request.type.js +7 -0
  110. package/dist/meeting/request.type.js.map +1 -0
  111. package/dist/meeting/state.js +21 -31
  112. package/dist/meeting/state.js.map +1 -1
  113. package/dist/meeting/util.js +43 -215
  114. package/dist/meeting/util.js.map +1 -1
  115. package/dist/meeting-info/collection.js +6 -25
  116. package/dist/meeting-info/collection.js.map +1 -1
  117. package/dist/meeting-info/index.js +14 -32
  118. package/dist/meeting-info/index.js.map +1 -1
  119. package/dist/meeting-info/meeting-info-v2.js +193 -268
  120. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  121. package/dist/meeting-info/request.js +3 -15
  122. package/dist/meeting-info/request.js.map +1 -1
  123. package/dist/meeting-info/util.js +98 -183
  124. package/dist/meeting-info/util.js.map +1 -1
  125. package/dist/meeting-info/utilv2.js +137 -228
  126. package/dist/meeting-info/utilv2.js.map +1 -1
  127. package/dist/meetings/collection.js +5 -20
  128. package/dist/meetings/collection.js.map +1 -1
  129. package/dist/meetings/index.js +490 -560
  130. package/dist/meetings/index.js.map +1 -1
  131. package/dist/meetings/request.js +24 -41
  132. package/dist/meetings/request.js.map +1 -1
  133. package/dist/meetings/util.js +116 -155
  134. package/dist/meetings/util.js.map +1 -1
  135. package/dist/member/index.js +78 -86
  136. package/dist/member/index.js.map +1 -1
  137. package/dist/member/util.js +31 -68
  138. package/dist/member/util.js.map +1 -1
  139. package/dist/members/collection.js +3 -12
  140. package/dist/members/collection.js.map +1 -1
  141. package/dist/members/index.js +94 -200
  142. package/dist/members/index.js.map +1 -1
  143. package/dist/members/request.js +16 -39
  144. package/dist/members/request.js.map +1 -1
  145. package/dist/members/util.js +9 -38
  146. package/dist/members/util.js.map +1 -1
  147. package/dist/metrics/config.js +0 -2
  148. package/dist/metrics/config.js.map +1 -1
  149. package/dist/metrics/constants.js +1 -2
  150. package/dist/metrics/constants.js.map +1 -1
  151. package/dist/metrics/index.js +55 -135
  152. package/dist/metrics/index.js.map +1 -1
  153. package/dist/multistream/mediaRequestManager.js +57 -33
  154. package/dist/multistream/mediaRequestManager.js.map +1 -1
  155. package/dist/multistream/receiveSlot.js +30 -50
  156. package/dist/multistream/receiveSlot.js.map +1 -1
  157. package/dist/multistream/receiveSlotManager.js +60 -82
  158. package/dist/multistream/receiveSlotManager.js.map +1 -1
  159. package/dist/multistream/remoteMedia.js +18 -58
  160. package/dist/multistream/remoteMedia.js.map +1 -1
  161. package/dist/multistream/remoteMediaGroup.js +6 -40
  162. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  163. package/dist/multistream/remoteMediaManager.js +360 -413
  164. package/dist/multistream/remoteMediaManager.js.map +1 -1
  165. package/dist/networkQualityMonitor/index.js +40 -59
  166. package/dist/networkQualityMonitor/index.js.map +1 -1
  167. package/dist/personal-meeting-room/index.js +21 -45
  168. package/dist/personal-meeting-room/index.js.map +1 -1
  169. package/dist/personal-meeting-room/request.js +1 -31
  170. package/dist/personal-meeting-room/request.js.map +1 -1
  171. package/dist/personal-meeting-room/util.js +0 -13
  172. package/dist/personal-meeting-room/util.js.map +1 -1
  173. package/dist/reachability/index.js +138 -182
  174. package/dist/reachability/index.js.map +1 -1
  175. package/dist/reachability/request.js +3 -18
  176. package/dist/reachability/request.js.map +1 -1
  177. package/dist/reactions/constants.js +13 -0
  178. package/dist/reactions/constants.js.map +1 -0
  179. package/dist/reactions/reactions.js +109 -0
  180. package/dist/reactions/reactions.js.map +1 -0
  181. package/dist/reactions/reactions.type.js +36 -0
  182. package/dist/reactions/reactions.type.js.map +1 -0
  183. package/dist/reconnection-manager/index.js +322 -455
  184. package/dist/reconnection-manager/index.js.map +1 -1
  185. package/dist/recording-controller/enums.js +17 -0
  186. package/dist/recording-controller/enums.js.map +1 -0
  187. package/dist/recording-controller/index.js +343 -0
  188. package/dist/recording-controller/index.js.map +1 -0
  189. package/dist/recording-controller/util.js +63 -0
  190. package/dist/recording-controller/util.js.map +1 -0
  191. package/dist/roap/index.js +39 -64
  192. package/dist/roap/index.js.map +1 -1
  193. package/dist/roap/request.js +94 -113
  194. package/dist/roap/request.js.map +1 -1
  195. package/dist/roap/turnDiscovery.js +85 -94
  196. package/dist/roap/turnDiscovery.js.map +1 -1
  197. package/dist/statsAnalyzer/global.js +1 -95
  198. package/dist/statsAnalyzer/global.js.map +1 -1
  199. package/dist/statsAnalyzer/index.js +357 -449
  200. package/dist/statsAnalyzer/index.js.map +1 -1
  201. package/dist/statsAnalyzer/mqaUtil.js +137 -81
  202. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  203. package/dist/transcription/index.js +22 -47
  204. package/dist/transcription/index.js.map +1 -1
  205. package/dist/types/breakouts/breakout.d.ts +8 -0
  206. package/dist/types/breakouts/collection.d.ts +5 -0
  207. package/dist/types/breakouts/index.d.ts +5 -0
  208. package/dist/types/breakouts/request.d.ts +22 -0
  209. package/dist/types/breakouts/utils.d.ts +1 -0
  210. package/dist/types/common/browser-detection.d.ts +9 -0
  211. package/dist/types/common/collection.d.ts +48 -0
  212. package/dist/types/common/config.d.ts +2 -0
  213. package/dist/types/common/errors/captcha-error.d.ts +15 -0
  214. package/dist/types/common/errors/intent-to-join.d.ts +16 -0
  215. package/dist/types/common/errors/join-meeting.d.ts +17 -0
  216. package/dist/types/common/errors/media.d.ts +15 -0
  217. package/dist/types/common/errors/parameter.d.ts +15 -0
  218. package/dist/types/common/errors/password-error.d.ts +15 -0
  219. package/dist/types/common/errors/permission.d.ts +14 -0
  220. package/dist/types/common/errors/reconnection-in-progress.d.ts +9 -0
  221. package/dist/types/common/errors/reconnection.d.ts +15 -0
  222. package/dist/types/common/errors/stats.d.ts +15 -0
  223. package/dist/types/common/errors/webex-errors.d.ts +69 -0
  224. package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
  225. package/dist/types/common/events/events-scope.d.ts +17 -0
  226. package/dist/types/common/events/events.d.ts +12 -0
  227. package/dist/types/common/events/trigger-proxy.d.ts +2 -0
  228. package/dist/types/common/events/util.d.ts +2 -0
  229. package/dist/types/common/logs/logger-config.d.ts +2 -0
  230. package/dist/types/common/logs/logger-proxy.d.ts +2 -0
  231. package/dist/types/common/logs/request.d.ts +34 -0
  232. package/dist/types/common/queue.d.ts +32 -0
  233. package/dist/types/config.d.ts +77 -0
  234. package/dist/types/constants.d.ts +899 -0
  235. package/dist/types/controls-options-manager/constants.d.ts +4 -0
  236. package/dist/types/controls-options-manager/enums.d.ts +5 -0
  237. package/dist/types/controls-options-manager/index.d.ts +120 -0
  238. package/dist/types/controls-options-manager/util.d.ts +7 -0
  239. package/dist/types/index.d.ts +5 -0
  240. package/dist/types/locus-info/controlsUtils.d.ts +2 -0
  241. package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
  242. package/dist/types/locus-info/fullState.d.ts +2 -0
  243. package/dist/types/locus-info/hostUtils.d.ts +2 -0
  244. package/dist/types/locus-info/index.d.ts +269 -0
  245. package/dist/types/locus-info/infoUtils.d.ts +2 -0
  246. package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
  247. package/dist/types/locus-info/parser.d.ts +212 -0
  248. package/dist/types/locus-info/selfUtils.d.ts +2 -0
  249. package/dist/types/media/index.d.ts +32 -0
  250. package/dist/types/media/properties.d.ts +107 -0
  251. package/dist/types/media/util.d.ts +2 -0
  252. package/dist/types/mediaQualityMetrics/config.d.ts +365 -0
  253. package/dist/types/meeting/effectsState.d.ts +42 -0
  254. package/dist/types/meeting/in-meeting-actions.d.ts +83 -0
  255. package/dist/types/meeting/index.d.ts +1724 -0
  256. package/dist/types/meeting/muteState.d.ts +108 -0
  257. package/dist/types/meeting/request.d.ts +261 -0
  258. package/dist/types/meeting/request.type.d.ts +11 -0
  259. package/dist/types/meeting/state.d.ts +9 -0
  260. package/dist/types/meeting/util.d.ts +2 -0
  261. package/dist/types/meeting-info/collection.d.ts +20 -0
  262. package/dist/types/meeting-info/index.d.ts +57 -0
  263. package/dist/types/meeting-info/meeting-info-v2.d.ts +93 -0
  264. package/dist/types/meeting-info/request.d.ts +22 -0
  265. package/dist/types/meeting-info/util.d.ts +2 -0
  266. package/dist/types/meeting-info/utilv2.d.ts +2 -0
  267. package/dist/types/meetings/collection.d.ts +23 -0
  268. package/dist/types/meetings/index.d.ts +297 -0
  269. package/dist/types/meetings/request.d.ts +27 -0
  270. package/dist/types/meetings/util.d.ts +18 -0
  271. package/dist/types/member/index.d.ts +146 -0
  272. package/dist/types/member/util.d.ts +2 -0
  273. package/dist/types/members/collection.d.ts +24 -0
  274. package/dist/types/members/index.d.ts +320 -0
  275. package/dist/types/members/request.d.ts +50 -0
  276. package/dist/types/members/util.d.ts +2 -0
  277. package/dist/types/metrics/config.d.ts +178 -0
  278. package/dist/types/metrics/constants.d.ts +57 -0
  279. package/dist/types/metrics/index.d.ts +160 -0
  280. package/dist/types/multistream/mediaRequestManager.d.ts +50 -0
  281. package/dist/types/multistream/receiveSlot.d.ts +66 -0
  282. package/dist/types/multistream/receiveSlotManager.d.ts +46 -0
  283. package/dist/types/multistream/remoteMedia.d.ts +93 -0
  284. package/dist/types/multistream/remoteMediaGroup.d.ts +56 -0
  285. package/dist/types/multistream/remoteMediaManager.d.ts +241 -0
  286. package/dist/types/networkQualityMonitor/index.d.ts +70 -0
  287. package/dist/types/personal-meeting-room/index.d.ts +47 -0
  288. package/dist/types/personal-meeting-room/request.d.ts +14 -0
  289. package/dist/types/personal-meeting-room/util.d.ts +2 -0
  290. package/dist/types/reachability/index.d.ts +140 -0
  291. package/dist/types/reachability/request.d.ts +35 -0
  292. package/dist/types/reactions/constants.d.ts +3 -0
  293. package/dist/types/reactions/reactions.d.ts +4 -0
  294. package/dist/types/reactions/reactions.type.d.ts +52 -0
  295. package/dist/types/reconnection-manager/index.d.ts +117 -0
  296. package/dist/types/recording-controller/enums.d.ts +7 -0
  297. package/dist/types/recording-controller/index.d.ts +193 -0
  298. package/dist/types/recording-controller/util.d.ts +13 -0
  299. package/dist/types/roap/index.d.ts +77 -0
  300. package/dist/types/roap/request.d.ts +35 -0
  301. package/dist/types/roap/turnDiscovery.d.ts +74 -0
  302. package/dist/types/statsAnalyzer/global.d.ts +36 -0
  303. package/dist/types/statsAnalyzer/index.d.ts +195 -0
  304. package/dist/types/statsAnalyzer/mqaUtil.d.ts +24 -0
  305. package/dist/types/transcription/index.d.ts +64 -0
  306. package/internal-README.md +7 -6
  307. package/package.json +25 -20
  308. package/src/breakouts/README.md +190 -0
  309. package/src/breakouts/breakout.ts +130 -0
  310. package/src/breakouts/collection.ts +19 -0
  311. package/src/breakouts/index.ts +353 -0
  312. package/src/breakouts/request.ts +55 -0
  313. package/src/breakouts/utils.ts +15 -0
  314. package/src/common/{browser-detection.js → browser-detection.ts} +9 -6
  315. package/src/common/collection.ts +9 -7
  316. package/src/common/{config.js → config.ts} +1 -1
  317. package/src/common/errors/{captcha-error.js → captcha-error.ts} +11 -7
  318. package/src/common/errors/{intent-to-join.js → intent-to-join.ts} +12 -7
  319. package/src/common/errors/{join-meeting.js → join-meeting.ts} +17 -8
  320. package/src/common/errors/{media.js → media.ts} +11 -7
  321. package/src/common/errors/parameter.ts +11 -7
  322. package/src/common/errors/{password-error.js → password-error.ts} +11 -7
  323. package/src/common/errors/{permission.js → permission.ts} +10 -6
  324. package/src/common/errors/{reconnection.js → reconnection.ts} +11 -7
  325. package/src/common/errors/{stats.js → stats.ts} +11 -7
  326. package/src/common/errors/{webex-errors.js → webex-errors.ts} +8 -7
  327. package/src/common/errors/{webex-meetings-error.js → webex-meetings-error.ts} +4 -2
  328. package/src/common/events/{events-scope.js → events-scope.ts} +6 -2
  329. package/src/common/events/{events.js → events.ts} +5 -1
  330. package/src/common/events/{trigger-proxy.js → trigger-proxy.ts} +9 -5
  331. package/src/common/events/{util.js → util.ts} +2 -3
  332. package/src/common/logs/{logger-config.js → logger-config.ts} +1 -2
  333. package/src/common/logs/logger-proxy.ts +44 -0
  334. package/src/common/logs/{request.js → request.ts} +22 -9
  335. package/src/common/queue.ts +1 -2
  336. package/src/{config.js → config.ts} +17 -12
  337. package/src/constants.ts +44 -4
  338. package/src/controls-options-manager/constants.ts +5 -0
  339. package/src/controls-options-manager/enums.ts +6 -0
  340. package/src/controls-options-manager/index.ts +183 -0
  341. package/src/controls-options-manager/util.ts +20 -0
  342. package/src/index.js +2 -1
  343. package/src/locus-info/controlsUtils.ts +114 -0
  344. package/src/locus-info/{embeddedAppsUtils.js → embeddedAppsUtils.ts} +5 -6
  345. package/src/locus-info/{fullState.js → fullState.ts} +16 -12
  346. package/src/locus-info/{hostUtils.js → hostUtils.ts} +9 -8
  347. package/src/locus-info/{index.js → index.ts} +150 -66
  348. package/src/locus-info/{infoUtils.js → infoUtils.ts} +19 -8
  349. package/src/locus-info/{mediaSharesUtils.js → mediaSharesUtils.ts} +17 -17
  350. package/src/locus-info/{parser.js → parser.ts} +67 -79
  351. package/src/locus-info/{selfUtils.js → selfUtils.ts} +123 -68
  352. package/src/media/{index.js → index.ts} +176 -157
  353. package/src/media/{properties.js → properties.ts} +48 -31
  354. package/src/media/{util.js → util.ts} +2 -2
  355. package/src/mediaQualityMetrics/config.ts +384 -0
  356. package/src/meeting/{effectsState.js → effectsState.ts} +47 -41
  357. package/src/meeting/in-meeting-actions.ts +31 -3
  358. package/src/meeting/{index.js → index.ts} +2479 -1466
  359. package/src/meeting/{muteState.js → muteState.ts} +80 -45
  360. package/src/meeting/{request.js → request.ts} +292 -142
  361. package/src/meeting/request.type.ts +13 -0
  362. package/src/meeting/{state.js → state.ts} +50 -35
  363. package/src/meeting/{util.js → util.ts} +112 -115
  364. package/src/meeting-info/{collection.js → collection.ts} +6 -2
  365. package/src/meeting-info/{index.js → index.ts} +42 -36
  366. package/src/meeting-info/meeting-info-v2.ts +273 -0
  367. package/src/meeting-info/{request.js → request.ts} +14 -4
  368. package/src/meeting-info/{util.js → util.ts} +60 -51
  369. package/src/meeting-info/{utilv2.js → utilv2.ts} +65 -58
  370. package/src/meetings/{collection.js → collection.ts} +6 -3
  371. package/src/meetings/index.ts +1159 -0
  372. package/src/meetings/{request.js → request.ts} +32 -25
  373. package/src/meetings/{util.js → util.ts} +58 -32
  374. package/src/member/{index.js → index.ts} +102 -56
  375. package/src/member/{util.js → util.ts} +52 -25
  376. package/src/members/{collection.js → collection.ts} +2 -2
  377. package/src/members/{index.js → index.ts} +221 -142
  378. package/src/members/{request.js → request.ts} +60 -16
  379. package/src/members/{util.js → util.ts} +50 -48
  380. package/src/metrics/{config.js → config.ts} +254 -83
  381. package/src/metrics/{constants.js → constants.ts} +0 -2
  382. package/src/metrics/{index.js → index.ts} +106 -74
  383. package/src/multistream/mediaRequestManager.ts +79 -15
  384. package/src/multistream/receiveSlot.ts +42 -13
  385. package/src/multistream/receiveSlotManager.ts +35 -21
  386. package/src/multistream/remoteMedia.ts +15 -5
  387. package/src/multistream/remoteMediaGroup.ts +4 -3
  388. package/src/multistream/remoteMediaManager.ts +152 -36
  389. package/src/networkQualityMonitor/{index.js → index.ts} +41 -29
  390. package/src/personal-meeting-room/{index.js → index.ts} +28 -19
  391. package/src/personal-meeting-room/{request.js → request.ts} +13 -4
  392. package/src/personal-meeting-room/{util.js → util.ts} +4 -4
  393. package/src/reachability/{index.js → index.ts} +99 -83
  394. package/src/reachability/request.ts +39 -33
  395. package/src/reactions/constants.ts +4 -0
  396. package/src/reactions/reactions.ts +104 -0
  397. package/src/reactions/reactions.type.ts +62 -0
  398. package/src/reconnection-manager/{index.js → index.ts} +195 -102
  399. package/src/recording-controller/enums.ts +8 -0
  400. package/src/recording-controller/index.ts +315 -0
  401. package/src/recording-controller/util.ts +58 -0
  402. package/src/roap/{index.js → index.ts} +73 -56
  403. package/src/roap/request.ts +157 -0
  404. package/src/roap/turnDiscovery.ts +77 -37
  405. package/src/statsAnalyzer/global.ts +37 -0
  406. package/src/statsAnalyzer/index.ts +1234 -0
  407. package/src/statsAnalyzer/mqaUtil.ts +293 -0
  408. package/src/transcription/{index.js → index.ts} +46 -39
  409. package/test/integration/spec/converged-space-meetings.js +176 -0
  410. package/test/integration/spec/journey.js +664 -463
  411. package/test/integration/spec/space-meeting.js +320 -206
  412. package/test/integration/spec/transcription.js +7 -8
  413. package/test/unit/spec/breakouts/breakout.ts +147 -0
  414. package/test/unit/spec/breakouts/collection.ts +15 -0
  415. package/test/unit/spec/breakouts/index.ts +464 -0
  416. package/test/unit/spec/breakouts/request.ts +104 -0
  417. package/test/unit/spec/breakouts/utils.js +21 -0
  418. package/test/unit/spec/common/browser-detection.js +9 -28
  419. package/test/unit/spec/controls-options-manager/index.js +124 -0
  420. package/test/unit/spec/controls-options-manager/util.js +66 -0
  421. package/test/unit/spec/fixture/locus.js +92 -90
  422. package/test/unit/spec/locus-info/controlsUtils.js +25 -5
  423. package/test/unit/spec/locus-info/embeddedAppsUtils.js +8 -6
  424. package/test/unit/spec/locus-info/index.js +104 -2
  425. package/test/unit/spec/locus-info/infoUtils.js +41 -32
  426. package/test/unit/spec/locus-info/lib/BasicSeqCmp.json +88 -430
  427. package/test/unit/spec/locus-info/lib/SeqCmp.json +513 -685
  428. package/test/unit/spec/locus-info/parser.js +3 -9
  429. package/test/unit/spec/locus-info/selfConstant.js +97 -103
  430. package/test/unit/spec/locus-info/selfUtils.js +105 -12
  431. package/test/unit/spec/media/index.ts +31 -47
  432. package/test/unit/spec/media/properties.ts +9 -9
  433. package/test/unit/spec/meeting/effectsState.js +39 -45
  434. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -2
  435. package/test/unit/spec/meeting/index.js +2214 -746
  436. package/test/unit/spec/meeting/muteState.js +43 -34
  437. package/test/unit/spec/meeting/request.js +115 -44
  438. package/test/unit/spec/meeting/utils.js +104 -171
  439. package/test/unit/spec/meeting-info/meetinginfov2.js +100 -73
  440. package/test/unit/spec/meeting-info/request.js +7 -9
  441. package/test/unit/spec/meeting-info/util.js +11 -12
  442. package/test/unit/spec/meeting-info/utilv2.js +110 -74
  443. package/test/unit/spec/meetings/collection.js +1 -1
  444. package/test/unit/spec/meetings/index.js +439 -257
  445. package/test/unit/spec/meetings/utils.js +14 -12
  446. package/test/unit/spec/member/index.js +0 -1
  447. package/test/unit/spec/member/util.js +31 -7
  448. package/test/unit/spec/members/index.js +104 -54
  449. package/test/unit/spec/members/request.js +29 -20
  450. package/test/unit/spec/members/utils.js +8 -5
  451. package/test/unit/spec/metrics/index.js +16 -21
  452. package/test/unit/spec/multistream/mediaRequestManager.ts +312 -50
  453. package/test/unit/spec/multistream/receiveSlot.ts +57 -6
  454. package/test/unit/spec/multistream/receiveSlotManager.ts +41 -13
  455. package/test/unit/spec/multistream/remoteMedia.ts +10 -2
  456. package/test/unit/spec/multistream/remoteMediaGroup.ts +5 -5
  457. package/test/unit/spec/multistream/remoteMediaManager.ts +412 -65
  458. package/test/unit/spec/networkQualityMonitor/index.js +24 -18
  459. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -7
  460. package/test/unit/spec/reachability/index.ts +58 -26
  461. package/test/unit/spec/reconnection-manager/index.js +102 -9
  462. package/test/unit/spec/recording-controller/index.js +231 -0
  463. package/test/unit/spec/recording-controller/util.js +102 -0
  464. package/test/unit/spec/roap/index.ts +2 -1
  465. package/test/unit/spec/roap/request.ts +114 -0
  466. package/test/unit/spec/roap/turnDiscovery.ts +64 -45
  467. package/test/unit/spec/stats-analyzer/index.js +86 -57
  468. package/test/utils/cmr.js +44 -42
  469. package/test/utils/constants.js +9 -0
  470. package/test/utils/testUtils.js +96 -80
  471. package/test/utils/webex-config.js +22 -18
  472. package/test/utils/webex-test-users.js +57 -50
  473. package/tsconfig.json +6 -0
  474. package/dist/media/internal-media-core-wrapper.js +0 -22
  475. package/dist/media/internal-media-core-wrapper.js.map +0 -1
  476. package/dist/multistream/multistreamMedia.js +0 -116
  477. package/dist/multistream/multistreamMedia.js.map +0 -1
  478. package/dist/peer-connection-manager/util.js +0 -124
  479. package/dist/peer-connection-manager/util.js.map +0 -1
  480. package/src/common/logs/logger-proxy.js +0 -33
  481. package/src/locus-info/controlsUtils.js +0 -102
  482. package/src/media/internal-media-core-wrapper.ts +0 -9
  483. package/src/mediaQualityMetrics/config.js +0 -382
  484. package/src/meeting-info/meeting-info-v2.js +0 -255
  485. package/src/meetings/index.js +0 -1015
  486. package/src/multistream/multistreamMedia.ts +0 -92
  487. package/src/peer-connection-manager/util.ts +0 -117
  488. package/src/roap/request.js +0 -127
  489. package/src/statsAnalyzer/global.js +0 -133
  490. package/src/statsAnalyzer/index.js +0 -1006
  491. package/src/statsAnalyzer/mqaUtil.js +0 -173
  492. package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -389
  493. /package/src/common/errors/{reconnection-in-progress.js → reconnection-in-progress.ts} +0 -0
@@ -22,8 +22,9 @@ import {
22
22
  LOCUSINFO,
23
23
  PC_BAIL_TIMEOUT,
24
24
  } from '@webex/plugin-meetings/src/constants';
25
- import {MediaConnection as MC} from '@webex/internal-media-core';
25
+ import {ConnectionState, Event, Errors, ErrorType, RemoteTrackType} from '@webex/internal-media-core';
26
26
  import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
27
+ import * as MuteStateModule from '@webex/plugin-meetings/src/meeting/muteState';
27
28
  import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
28
29
  import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
29
30
  import Meeting from '@webex/plugin-meetings/src/meeting';
@@ -36,6 +37,8 @@ import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
36
37
  import Media from '@webex/plugin-meetings/src/media/index';
37
38
  import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
38
39
  import MediaUtil from '@webex/plugin-meetings/src/media/util';
40
+ import RecordingUtil from '@webex/plugin-meetings/src/recording-controller/util';
41
+ import ControlsOptionsUtil from '@webex/plugin-meetings/src/controls-options-manager/util';
39
42
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
40
43
  import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
41
44
  import TriggerProxy from '@webex/plugin-meetings/src/common/events/trigger-proxy';
@@ -44,13 +47,18 @@ import Metrics from '@webex/plugin-meetings/src/metrics';
44
47
  import {trigger, eventType} from '@webex/plugin-meetings/src/metrics/config';
45
48
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
46
49
  import {IceGatheringFailed} from '@webex/plugin-meetings/src/common/errors/webex-errors';
50
+ import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
47
51
 
52
+ import LLM from '@webex/internal-plugin-llm';
53
+ import Mercury from '@webex/internal-plugin-mercury';
54
+ import Breakouts from '@webex/plugin-meetings/src/breakouts';
55
+ import {REACTION_RELAY_TYPES} from '../../../../src/reactions/constants';
48
56
  import locus from '../fixture/locus';
49
57
  import {
50
58
  UserNotJoinedError,
51
59
  MeetingNotActiveError,
52
60
  UserInLobbyError,
53
- NoMediaEstablishedYetError
61
+ NoMediaEstablishedYetError,
54
62
  } from '../../../../src/common/errors/webex-errors';
55
63
  import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-error';
56
64
  import ParameterError from '../../../../src/common/errors/parameter';
@@ -59,11 +67,12 @@ import CaptchaError from '../../../../src/common/errors/captcha-error';
59
67
  import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
60
68
  import DefaultSDKConfig from '../../../../src/config';
61
69
  import testUtils from '../../../utils/testUtils';
62
- import {MeetingInfoV2CaptchaError, MeetingInfoV2PasswordError} from '../../../../src/meeting-info/meeting-info-v2';
70
+ import {
71
+ MeetingInfoV2CaptchaError,
72
+ MeetingInfoV2PasswordError,
73
+ } from '../../../../src/meeting-info/meeting-info-v2';
63
74
 
64
- const {
65
- getBrowserName
66
- } = BrowserDetection();
75
+ const {getBrowserName} = BrowserDetection();
67
76
 
68
77
  // Non-stubbed function
69
78
  const {getDisplayMedia} = Media;
@@ -75,7 +84,7 @@ describe('plugin-meetings', () => {
75
84
  error: () => {},
76
85
  warn: () => {},
77
86
  trace: () => {},
78
- debug: () => {}
87
+ debug: () => {},
79
88
  };
80
89
 
81
90
  beforeEach(() => {
@@ -87,44 +96,48 @@ describe('plugin-meetings', () => {
87
96
 
88
97
  before(() => {
89
98
  const MediaStream = {
90
- getVideoTracks: () => [{
91
- applyConstraints: () => { }
92
- }]
99
+ getVideoTracks: () => [
100
+ {
101
+ applyConstraints: () => {},
102
+ },
103
+ ],
93
104
  };
94
105
 
95
106
  Object.defineProperty(global.window.navigator, 'mediaDevices', {
96
107
  writable: true,
97
108
  value: {
98
109
  getDisplayMedia: sinon.stub().returns(Promise.resolve(MediaStream)),
99
- enumerateDevices: sinon.stub().returns(Promise.resolve([
100
- {
101
- deviceId: '',
102
- kind: 'audioinput',
103
- label: '',
104
- groupId: '29d9339cc77bffdd24cb69ee80f6d3200481099bcd0f29267558672de0430777',
105
- },
106
- {
107
- deviceId: '',
108
- kind: 'videoinput',
109
- label: '',
110
- groupId: '08d4f8200e7e4a3425ecf75b7edea9ae4acd934019f2a52217554bcc8e46604d',
111
- },
112
- {
113
- deviceId: '',
114
- kind: 'audiooutput',
115
- label: '',
116
- groupId: '29d9339cc77bffdd24cb69ee80f6d3200481099bcd0f29267558672de0430777',
117
- }
118
- ])),
110
+ enumerateDevices: sinon.stub().returns(
111
+ Promise.resolve([
112
+ {
113
+ deviceId: '',
114
+ kind: 'audioinput',
115
+ label: '',
116
+ groupId: '29d9339cc77bffdd24cb69ee80f6d3200481099bcd0f29267558672de0430777',
117
+ },
118
+ {
119
+ deviceId: '',
120
+ kind: 'videoinput',
121
+ label: '',
122
+ groupId: '08d4f8200e7e4a3425ecf75b7edea9ae4acd934019f2a52217554bcc8e46604d',
123
+ },
124
+ {
125
+ deviceId: '',
126
+ kind: 'audiooutput',
127
+ label: '',
128
+ groupId: '29d9339cc77bffdd24cb69ee80f6d3200481099bcd0f29267558672de0430777',
129
+ },
130
+ ])
131
+ ),
119
132
  getSupportedConstraints: sinon.stub().returns({
120
- sampleRate: true
121
- })
133
+ sampleRate: true,
134
+ }),
122
135
  },
123
136
  });
124
137
 
125
138
  Object.defineProperty(global.window, 'MediaStream', {
126
139
  writable: true,
127
- value: MediaStream
140
+ value: MediaStream,
128
141
  });
129
142
  LoggerConfig.set({verboseEvents: true, enable: false});
130
143
  LoggerProxy.set(logger);
@@ -149,32 +162,34 @@ describe('plugin-meetings', () => {
149
162
  children: {
150
163
  meetings: Meetings,
151
164
  credentials: Credentials,
152
- support: Support
165
+ support: Support,
166
+ llm: LLM,
167
+ mercury: Mercury,
153
168
  },
154
169
  config: {
155
170
  credentials: {
156
- client_id: 'mock-client-id'
171
+ client_id: 'mock-client-id',
157
172
  },
158
173
  meetings: {
159
174
  reconnection: {
160
- enabled: false
175
+ enabled: false,
161
176
  },
162
177
  mediaSettings: {},
163
178
  metrics: {},
164
179
  stats: {},
165
- experimental: {enableUnifiedMeetings: true}
180
+ experimental: {enableUnifiedMeetings: true},
166
181
  },
167
182
  metrics: {
168
- type: ['behavioral']
169
- }
170
- }
183
+ type: ['behavioral'],
184
+ },
185
+ },
171
186
  });
172
187
 
173
188
  webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
174
189
  webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
175
190
  webex.internal.metrics.submitClientMetrics = sinon.stub().returns(Promise.resolve());
176
191
  webex.meetings.uploadLogs = sinon.stub().returns(Promise.resolve());
177
-
192
+ webex.internal.llm.on = sinon.stub();
178
193
 
179
194
  TriggerProxy.trigger = sinon.stub().returns(true);
180
195
  Metrics.postEvent = sinon.stub();
@@ -203,7 +218,7 @@ describe('plugin-meetings', () => {
203
218
  destinationType: _MEETING_ID_,
204
219
  },
205
220
  {
206
- parent: webex
221
+ parent: webex,
207
222
  }
208
223
  );
209
224
 
@@ -246,6 +261,13 @@ describe('plugin-meetings', () => {
246
261
  assert.equal(meeting.meetingInfoFailureReason, undefined);
247
262
  assert.equal(meeting.destination, testDestination);
248
263
  assert.equal(meeting.destinationType, _MEETING_ID_);
264
+ assert.instanceOf(meeting.breakouts, Breakouts);
265
+ });
266
+ it('creates MediaRequestManager instances', () => {
267
+ assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
268
+ assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
269
+ assert.instanceOf(meeting.mediaRequestManagers.screenShareAudio, MediaRequestManager);
270
+ assert.instanceOf(meeting.mediaRequestManagers.screenShareVideo, MediaRequestManager);
249
271
  });
250
272
  });
251
273
  describe('#invite', () => {
@@ -432,7 +454,6 @@ describe('plugin-meetings', () => {
432
454
  it('should return a promise resolution', async () => {
433
455
  meeting.audio = {handleClientRequest};
434
456
 
435
-
436
457
  const audio = meeting.unmuteAudio();
437
458
 
438
459
  assert.exists(audio.then);
@@ -449,8 +470,8 @@ describe('plugin-meetings', () => {
449
470
  readyState: 'live',
450
471
  enabled: true,
451
472
  getSettings: () => ({
452
- sampleRate: 48000
453
- })
473
+ sampleRate: 48000,
474
+ }),
454
475
  });
455
476
 
456
477
  beforeEach(() => {
@@ -458,7 +479,7 @@ describe('plugin-meetings', () => {
458
479
  sinon.replace(meeting, 'addMedia', () => {
459
480
  sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
460
481
  sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
461
- receiveAudio: true
482
+ receiveAudio: true,
462
483
  });
463
484
  });
464
485
  });
@@ -470,7 +491,7 @@ describe('plugin-meetings', () => {
470
491
  describe('before audio attached to meeting', () => {
471
492
  it('should throw no audio error', async () => {
472
493
  await meeting.enableBNR().catch((err) => {
473
- assert.equal(err.toString(), 'Error: Meeting doesn\'t have an audioTrack attached');
494
+ assert.equal(err.toString(), "Error: Meeting doesn't have an audioTrack attached");
474
495
  });
475
496
  });
476
497
  });
@@ -518,7 +539,7 @@ describe('plugin-meetings', () => {
518
539
 
519
540
  it('should throw no audio error', async () => {
520
541
  await meeting.disableBNR().catch((err) => {
521
- assert.equal(err.toString(), 'Error: Meeting doesn\'t have an audioTrack attached');
542
+ assert.equal(err.toString(), "Error: Meeting doesn't have an audioTrack attached");
522
543
  });
523
544
  });
524
545
  });
@@ -650,7 +671,11 @@ describe('plugin-meetings', () => {
650
671
  });
651
672
  describe('#getMediaStreams', () => {
652
673
  beforeEach(() => {
653
- sinon.stub(Media, 'getSupportedDevice').callsFake((options) => Promise.resolve({sendAudio: options.sendAudio, sendVideo: options.sendVideo}));
674
+ sinon
675
+ .stub(Media, 'getSupportedDevice')
676
+ .callsFake((options) =>
677
+ Promise.resolve({sendAudio: options.sendAudio, sendVideo: options.sendVideo})
678
+ );
654
679
  sinon.stub(Media, 'getUserMedia').returns(Promise.resolve(['stream1', 'stream2']));
655
680
  });
656
681
  afterEach(() => {
@@ -674,17 +699,20 @@ describe('plugin-meetings', () => {
674
699
  sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('480p');
675
700
  await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
676
701
 
677
- assert.calledWith(Media.getUserMedia, {
678
- ...mediaDirection,
679
- isSharing: false
680
- },
681
- {
682
- video: {
683
- width: {max: 640, ideal: 640},
684
- height: {max: 480, ideal: 480},
685
- deviceId: videoDevice
702
+ assert.calledWith(
703
+ Media.getUserMedia,
704
+ {
705
+ ...mediaDirection,
706
+ isSharing: false,
707
+ },
708
+ {
709
+ video: {
710
+ width: {max: 640, ideal: 640},
711
+ height: {max: 480, ideal: 480},
712
+ deviceId: videoDevice,
713
+ },
686
714
  }
687
- });
715
+ );
688
716
  });
689
717
  it('will set a new preferred video input device if passed in', async () => {
690
718
  // if audioVideo settings parameter specifies a new video device it
@@ -709,23 +737,33 @@ describe('plugin-meetings', () => {
709
737
  video: {
710
738
  width: {
711
739
  max: 400,
712
- ideal: 400
740
+ ideal: 400,
713
741
  },
714
742
  height: {
715
743
  max: 200,
716
- ideal: 200
717
- }
718
- }
744
+ ideal: 200,
745
+ },
746
+ frameRate: {
747
+ ideal: 15,
748
+ max: 30,
749
+ },
750
+ facingMode: {
751
+ ideal: 'user',
752
+ },
753
+ },
719
754
  };
720
755
 
721
756
  sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('200p');
722
757
  await meeting.getMediaStreams(mediaDirection, customAudioVideoSettings);
723
758
 
724
- assert.calledWith(Media.getUserMedia, {
725
- ...mediaDirection,
726
- isSharing: false
727
- },
728
- customAudioVideoSettings);
759
+ assert.calledWith(
760
+ Media.getUserMedia,
761
+ {
762
+ ...mediaDirection,
763
+ isSharing: false,
764
+ },
765
+ customAudioVideoSettings
766
+ );
729
767
  });
730
768
  it('should not access camera if sendVideo is false ', async () => {
731
769
  await meeting.getMediaStreams({sendAudio: true, sendVideo: false});
@@ -759,13 +797,12 @@ describe('plugin-meetings', () => {
759
797
  });
760
798
  });
761
799
 
762
- it('should throw error', async () => {
800
+ it("should throw error if request doesn't work", async () => {
763
801
  meeting.request = sinon.stub().returns(Promise.reject());
764
802
 
765
803
  try {
766
804
  await meeting.receiveTranscription();
767
- }
768
- catch (err) {
805
+ } catch (err) {
769
806
  assert(err, {});
770
807
  }
771
808
  });
@@ -773,15 +810,74 @@ describe('plugin-meetings', () => {
773
810
  describe('#stopReceivingTranscription', () => {
774
811
  it('should get invoked', () => {
775
812
  meeting.transcription = {
776
- closeSocket: sinon.stub()
813
+ closeSocket: sinon.stub(),
777
814
  };
778
815
 
779
816
  meeting.stopReceivingTranscription();
780
817
  assert.calledOnce(meeting.transcription.closeSocket);
781
818
  });
782
819
  });
820
+ describe('#isReactionsSupported', () => {
821
+ it('should return false if the feature is not supported for the meeting', () => {
822
+ meeting.locusInfo.controls = {reactions: {enabled: false}};
823
+
824
+ assert.equal(meeting.isReactionsSupported(), false);
825
+ });
826
+ it('should return true if the feature is not supported for the meeting', () => {
827
+ meeting.locusInfo.controls = {reactions: {enabled: true}};
828
+
829
+ assert.equal(meeting.isReactionsSupported(), true);
830
+ });
831
+ });
832
+ describe('#processRelayEvent', () => {
833
+ it('should process a Reaction event type', () => {
834
+ meeting.isReactionsSupported = sinon.stub().returns(true);
835
+ meeting.config.receiveReactions = true;
836
+ const fakeSendersName = 'Fake reactors name';
837
+ meeting.members.membersCollection.get = sinon.stub().returns({name: fakeSendersName});
838
+ const fakeReactionPayload = {
839
+ type: 'fake_type',
840
+ codepoints: 'fake_codepoints',
841
+ shortcodes: 'fake_shortcodes',
842
+ tone: {
843
+ type: 'fake_tone_type',
844
+ codepoints: 'fake_tone_codepoints',
845
+ shortcodes: 'fake_tone_shortcodes',
846
+ },
847
+ };
848
+ const fakeSenderPayload = {
849
+ participantId: 'fake_participant_id',
850
+ };
851
+ const fakeProcessedReaction = {
852
+ reaction: fakeReactionPayload,
853
+ sender: {
854
+ id: fakeSenderPayload.participantId,
855
+ name: fakeSendersName,
856
+ },
857
+ };
858
+ const fakeRelayEvent = {
859
+ data: {
860
+ relayType: REACTION_RELAY_TYPES.REACTION,
861
+ reaction: fakeReactionPayload,
862
+ sender: fakeSenderPayload,
863
+ }
864
+ };
865
+ meeting.processRelayEvent(fakeRelayEvent);
866
+ assert.calledWith(
867
+ TriggerProxy.trigger,
868
+ sinon.match.instanceOf(Meeting),
869
+ {
870
+ file: 'meeting/index',
871
+ function: 'join',
872
+ },
873
+ EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
874
+ fakeProcessedReaction
875
+ );
876
+ })
877
+ })
783
878
  describe('#join', () => {
784
879
  let sandbox = null;
880
+ const joinMeetingResult = 'JOIN_MEETINGS_OPTION_RESULT';
785
881
 
786
882
  beforeEach(() => {
787
883
  sandbox = sinon.createSandbox();
@@ -799,22 +895,47 @@ describe('plugin-meetings', () => {
799
895
  meeting.setCorrelationId = sinon.stub().returns(true);
800
896
  meeting.setLocus = sinon.stub().returns(true);
801
897
  webex.meetings.registered = true;
898
+ meeting.updateLLMConnection = sinon.stub();
802
899
  });
803
900
  describe('successful', () => {
804
901
  beforeEach(() => {
805
- sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve());
902
+ sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
806
903
  });
807
904
 
808
905
  it('should join the meeting and return promise', async () => {
809
906
  const join = meeting.join();
810
907
 
811
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.CALL_INITIATED, data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true}});
908
+ assert.calledWithMatch(Metrics.postEvent, {
909
+ event: eventType.CALL_INITIATED,
910
+ data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true},
911
+ });
812
912
 
813
913
  assert.exists(join.then);
814
- await join;
914
+ const result = await join;
915
+
815
916
  assert.calledOnce(MeetingUtil.joinMeeting);
816
917
  assert.calledOnce(meeting.setLocus);
918
+ assert.equal(result, joinMeetingResult);
919
+ });
920
+
921
+ it('should call updateLLMConnection upon joining if config value is set', async () => {
922
+ meeting.config.enableAutomaticLLM = true;
923
+ meeting.webex.internal.llm.on = sinon.stub();
924
+ meeting.processRelayEvent = sinon.stub();
925
+ await meeting.join();
926
+
927
+ assert.calledOnce(meeting.updateLLMConnection);
928
+ assert.calledOnceWithExactly(meeting.webex.internal.llm.on, 'event:relay.event', meeting.processRelayEvent);
929
+ });
930
+
931
+ it('should not call updateLLMConnection upon joining if config value is not set', async () => {
932
+ meeting.webex.internal.llm.on = sinon.stub();
933
+ await meeting.join();
934
+
935
+ assert.notCalled(meeting.updateLLMConnection);
936
+ assert.notCalled(meeting.webex.internal.llm.on);
817
937
  });
938
+
818
939
  it('should invoke `receiveTranscription()` if receiveTranscription is set to true', async () => {
819
940
  meeting.isTranscriptionSupported = sinon.stub().returns(true);
820
941
  meeting.receiveTranscription = sinon.stub().returns(Promise.resolve());
@@ -856,8 +977,7 @@ describe('plugin-meetings', () => {
856
977
  try {
857
978
  await meeting.join();
858
979
  joinSucceeded = true;
859
- }
860
- catch (e) {
980
+ } catch (e) {
861
981
  assert.instanceOf(e, IntentToJoinError);
862
982
  }
863
983
  assert.isFalse(joinSucceeded);
@@ -905,7 +1025,7 @@ describe('plugin-meetings', () => {
905
1025
  describe('#addMedia', () => {
906
1026
  const muteStateStub = {
907
1027
  handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
908
- applyClientStateLocally: sinon.stub().returns(Promise.resolve(true))
1028
+ applyClientStateLocally: sinon.stub().returns(Promise.resolve(true)),
909
1029
  };
910
1030
 
911
1031
  let fakeMediaConnection;
@@ -913,7 +1033,7 @@ describe('plugin-meetings', () => {
913
1033
  beforeEach(() => {
914
1034
  fakeMediaConnection = {
915
1035
  close: sinon.stub(),
916
- getConnectionState: sinon.stub().returns(MC.ConnectionState.Connected),
1036
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
917
1037
  initiateOffer: sinon.stub().resolves({}),
918
1038
  on: sinon.stub(),
919
1039
  };
@@ -926,7 +1046,9 @@ describe('plugin-meetings', () => {
926
1046
  meeting.setMercuryListener = sinon.stub().returns(true);
927
1047
  meeting.setupMediaConnectionListeners = sinon.stub();
928
1048
  meeting.setMercuryListener = sinon.stub();
929
- meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: {}, turnDiscoverySkippedReason: undefined});
1049
+ meeting.roap.doTurnDiscovery = sinon
1050
+ .stub()
1051
+ .resolves({turnServerInfo: {}, turnDiscoverySkippedReason: undefined});
930
1052
  });
931
1053
 
932
1054
  it('should have #addMedia', () => {
@@ -955,8 +1077,7 @@ describe('plugin-meetings', () => {
955
1077
  try {
956
1078
  await meeting.addMedia();
957
1079
  assert.fail('addMedia should have thrown an exception.');
958
- }
959
- catch (err) {
1080
+ } catch (err) {
960
1081
  assert.instanceOf(err, UserInLobbyError);
961
1082
  }
962
1083
  });
@@ -974,37 +1095,33 @@ describe('plugin-meetings', () => {
974
1095
 
975
1096
  assert.isNull(meeting.statsAnalyzer);
976
1097
  assert(Metrics.sendBehavioralMetric.calledOnce);
977
- assert.calledWith(
978
- Metrics.sendBehavioralMetric,
979
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
980
- correlation_id: meeting.correlationId,
981
- locus_id: meeting.locusUrl.split('/').pop(),
982
- reason: error.message,
983
- stack: error.stack,
984
- code: error.code,
985
- turnDiscoverySkippedReason: undefined,
986
- turnServerUsed: true
987
- }
988
- );
1098
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
1099
+ correlation_id: meeting.correlationId,
1100
+ locus_id: meeting.locusUrl.split('/').pop(),
1101
+ reason: error.message,
1102
+ stack: error.stack,
1103
+ code: error.code,
1104
+ turnDiscoverySkippedReason: undefined,
1105
+ turnServerUsed: true,
1106
+ });
989
1107
  });
990
1108
 
991
1109
  it('checks metrics called with skipped reason config', async () => {
992
- meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: 'config'});
1110
+ meeting.roap.doTurnDiscovery = sinon
1111
+ .stub()
1112
+ .resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: 'config'});
993
1113
  meeting.meetingState = 'ACTIVE';
994
1114
  await meeting.addMedia().catch((err) => {
995
1115
  assert.exists(err);
996
1116
  assert(Metrics.sendBehavioralMetric.calledOnce);
997
- assert.calledWith(
998
- Metrics.sendBehavioralMetric,
999
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
1000
- correlation_id: meeting.correlationId,
1001
- locus_id: meeting.locusUrl.split('/').pop(),
1002
- reason: err.message,
1003
- stack: err.stack,
1004
- turnDiscoverySkippedReason: 'config',
1005
- turnServerUsed: false
1006
- }
1007
- );
1117
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
1118
+ correlation_id: meeting.correlationId,
1119
+ locus_id: meeting.locusUrl.split('/').pop(),
1120
+ reason: err.message,
1121
+ stack: err.stack,
1122
+ turnDiscoverySkippedReason: 'config',
1123
+ turnServerUsed: false,
1124
+ });
1008
1125
  });
1009
1126
  });
1010
1127
 
@@ -1023,12 +1140,13 @@ describe('plugin-meetings', () => {
1023
1140
  assert(Metrics.sendBehavioralMetric.calledOnce);
1024
1141
  assert.calledWith(
1025
1142
  Metrics.sendBehavioralMetric,
1026
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, sinon.match({
1143
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
1144
+ sinon.match({
1027
1145
  correlation_id: meeting.correlationId,
1028
1146
  locus_id: meeting.locusUrl.split('/').pop(),
1029
1147
  reason: result.message,
1030
1148
  turnDiscoverySkippedReason: undefined,
1031
- turnServerUsed: true
1149
+ turnServerUsed: true,
1032
1150
  })
1033
1151
  );
1034
1152
  });
@@ -1049,10 +1167,9 @@ describe('plugin-meetings', () => {
1049
1167
 
1050
1168
  try {
1051
1169
  await meeting.addMedia({
1052
- mediaSettings: {}
1170
+ mediaSettings: {},
1053
1171
  });
1054
- }
1055
- catch (err) {
1172
+ } catch (err) {
1056
1173
  assert.fail('should not throw an error');
1057
1174
  }
1058
1175
  });
@@ -1092,11 +1209,13 @@ describe('plugin-meetings', () => {
1092
1209
  });
1093
1210
 
1094
1211
  it('should attach the media and return promise', async () => {
1095
- meeting.roap.doTurnDiscovery = sinon.stub().resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
1212
+ meeting.roap.doTurnDiscovery = sinon
1213
+ .stub()
1214
+ .resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
1096
1215
 
1097
1216
  meeting.meetingState = 'ACTIVE';
1098
1217
  const media = meeting.addMedia({
1099
- mediaSettings: {}
1218
+ mediaSettings: {},
1100
1219
  });
1101
1220
 
1102
1221
  assert.exists(media);
@@ -1105,12 +1224,17 @@ describe('plugin-meetings', () => {
1105
1224
  assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
1106
1225
  assert.calledOnce(meeting.mediaProperties.setMediaDirection);
1107
1226
  assert.calledOnce(Media.createMediaConnection);
1108
- assert.calledWith(Media.createMediaConnection, sinon.match.any, sinon.match({turnServerInfo: undefined}));
1227
+ assert.calledWith(
1228
+ Media.createMediaConnection,
1229
+ false,
1230
+ meeting.getMediaConnectionDebugId(),
1231
+ sinon.match({turnServerInfo: undefined})
1232
+ );
1109
1233
  assert.calledOnce(meeting.setMercuryListener);
1110
1234
  assert.calledOnce(fakeMediaConnection.initiateOffer);
1111
1235
  /* statsAnalyzer is initiated inside of addMedia so there isn't
1112
- * a good way to mock it without mocking the constructor
1113
- */
1236
+ * a good way to mock it without mocking the constructor
1237
+ */
1114
1238
  });
1115
1239
 
1116
1240
  it('should pass the turn server info to the peer connection', async () => {
@@ -1121,17 +1245,16 @@ describe('plugin-meetings', () => {
1121
1245
  meeting.meetingState = 'ACTIVE';
1122
1246
  Media.createMediaConnection.resetHistory();
1123
1247
 
1124
-
1125
1248
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
1126
1249
  turnServerInfo: {
1127
1250
  url: FAKE_TURN_URL,
1128
1251
  username: FAKE_TURN_USER,
1129
- password: FAKE_TURN_PASSWORD
1252
+ password: FAKE_TURN_PASSWORD,
1130
1253
  },
1131
- turnServerSkippedReason: undefined
1254
+ turnServerSkippedReason: undefined,
1132
1255
  });
1133
1256
  const media = meeting.addMedia({
1134
- mediaSettings: {}
1257
+ mediaSettings: {},
1135
1258
  });
1136
1259
 
1137
1260
  assert.exists(media);
@@ -1139,25 +1262,35 @@ describe('plugin-meetings', () => {
1139
1262
  assert.calledOnce(meeting.roap.doTurnDiscovery);
1140
1263
  assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
1141
1264
  assert.calledOnce(Media.createMediaConnection);
1142
- assert.calledWith(Media.createMediaConnection, sinon.match.any, sinon.match({
1143
- turnServerInfo: {
1144
- url: FAKE_TURN_URL,
1145
- username: FAKE_TURN_USER,
1146
- password: FAKE_TURN_PASSWORD
1147
- }
1148
- }));
1265
+ assert.calledWith(
1266
+ Media.createMediaConnection,
1267
+ false,
1268
+ meeting.getMediaConnectionDebugId(),
1269
+ sinon.match({
1270
+ turnServerInfo: {
1271
+ url: FAKE_TURN_URL,
1272
+ username: FAKE_TURN_USER,
1273
+ password: FAKE_TURN_PASSWORD,
1274
+ },
1275
+ })
1276
+ );
1149
1277
  assert.calledOnce(fakeMediaConnection.initiateOffer);
1150
1278
  });
1151
1279
 
1152
1280
  it('should attach the media and return WebExMeetingsErrors when connection does not reach CONNECTED state', async () => {
1153
1281
  meeting.meetingState = 'ACTIVE';
1154
- fakeMediaConnection.getConnectionState = sinon.stub().returns(MC.ConnectionState.Connecting);
1282
+ fakeMediaConnection.getConnectionState = sinon
1283
+ .stub()
1284
+ .returns(ConnectionState.Connecting);
1155
1285
  const clock = sinon.useFakeTimers();
1156
1286
  const media = meeting.addMedia({
1157
- mediaSettings: {}
1287
+ mediaSettings: {},
1158
1288
  });
1159
1289
 
1160
- await clock.tickAsync(4000 /* meetingState timer, hardcoded inside addMedia */ + PC_BAIL_TIMEOUT /* connection state timer */);
1290
+ await clock.tickAsync(
1291
+ 4000 /* meetingState timer, hardcoded inside addMedia */ +
1292
+ PC_BAIL_TIMEOUT /* connection state timer */
1293
+ );
1161
1294
  await testUtils.flushPromises();
1162
1295
 
1163
1296
  assert.exists(media);
@@ -1174,9 +1307,10 @@ describe('plugin-meetings', () => {
1174
1307
 
1175
1308
  let errorThrown = false;
1176
1309
 
1177
- await meeting.addMedia({
1178
- mediaSettings: {}
1179
- })
1310
+ await meeting
1311
+ .addMedia({
1312
+ mediaSettings: {},
1313
+ })
1180
1314
  .catch((error) => {
1181
1315
  assert.equal(error.code, IceGatheringFailed.CODE);
1182
1316
  errorThrown = true;
@@ -1188,19 +1322,15 @@ describe('plugin-meetings', () => {
1188
1322
  it('should send ADD_MEDIA_SUCCESS metrics', async () => {
1189
1323
  meeting.meetingState = 'ACTIVE';
1190
1324
  await meeting.addMedia({
1191
- mediaSettings: {}
1325
+ mediaSettings: {},
1192
1326
  });
1193
1327
 
1194
1328
  assert.calledOnce(Metrics.sendBehavioralMetric);
1195
- assert.calledWith(
1196
- Metrics.sendBehavioralMetric,
1197
- BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS,
1198
- {
1199
- correlation_id: meeting.correlationId,
1200
- locus_id: meeting.locusUrl.split('/').pop(),
1201
- connectionType: 'udp'
1202
- }
1203
- );
1329
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
1330
+ correlation_id: meeting.correlationId,
1331
+ locus_id: meeting.locusUrl.split('/').pop(),
1332
+ connectionType: 'udp',
1333
+ });
1204
1334
  });
1205
1335
 
1206
1336
  describe('handles StatsAnalyzer events', () => {
@@ -1218,7 +1348,7 @@ describe('plugin-meetings', () => {
1218
1348
  sinon.stub(StatsAnalyzerModule, 'StatsAnalyzer').returns(statsAnalyzerStub);
1219
1349
 
1220
1350
  await meeting.addMedia({
1221
- mediaSettings: {}
1351
+ mediaSettings: {},
1222
1352
  });
1223
1353
  });
1224
1354
 
@@ -1227,51 +1357,79 @@ describe('plugin-meetings', () => {
1227
1357
  });
1228
1358
 
1229
1359
  it('LOCAL_MEDIA_STARTED triggers "meeting:media:local:start" event and sends metrics', async () => {
1230
- statsAnalyzerStub.emit({file: 'test', function: 'test'}, StatsAnalyzerModule.EVENTS.LOCAL_MEDIA_STARTED, {type: 'audio'});
1360
+ statsAnalyzerStub.emit(
1361
+ {file: 'test', function: 'test'},
1362
+ StatsAnalyzerModule.EVENTS.LOCAL_MEDIA_STARTED,
1363
+ {type: 'audio'}
1364
+ );
1231
1365
 
1232
1366
  assert.calledWith(
1233
1367
  TriggerProxy.trigger,
1234
1368
  sinon.match.instanceOf(Meeting),
1235
1369
  {
1236
1370
  file: 'meeting/index',
1237
- function: 'addMedia'
1371
+ function: 'addMedia',
1238
1372
  },
1239
1373
  EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
1240
1374
  {
1241
- type: 'audio'
1375
+ type: 'audio',
1242
1376
  }
1243
1377
  );
1244
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.SENDING_MEDIA_START, data: {mediaType: 'audio'}});
1378
+ assert.calledWithMatch(Metrics.postEvent, {
1379
+ event: eventType.SENDING_MEDIA_START,
1380
+ data: {mediaType: 'audio'},
1381
+ });
1245
1382
  });
1246
1383
 
1247
1384
  it('LOCAL_MEDIA_STOPPED triggers the right metrics', async () => {
1248
- statsAnalyzerStub.emit({file: 'test', function: 'test'}, StatsAnalyzerModule.EVENTS.LOCAL_MEDIA_STOPPED, {type: 'video'});
1385
+ statsAnalyzerStub.emit(
1386
+ {file: 'test', function: 'test'},
1387
+ StatsAnalyzerModule.EVENTS.LOCAL_MEDIA_STOPPED,
1388
+ {type: 'video'}
1389
+ );
1249
1390
 
1250
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.SENDING_MEDIA_STOP, data: {mediaType: 'video'}});
1391
+ assert.calledWithMatch(Metrics.postEvent, {
1392
+ event: eventType.SENDING_MEDIA_STOP,
1393
+ data: {mediaType: 'video'},
1394
+ });
1251
1395
  });
1252
1396
 
1253
1397
  it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics', async () => {
1254
- statsAnalyzerStub.emit({file: 'test', function: 'test'}, StatsAnalyzerModule.EVENTS.REMOTE_MEDIA_STARTED, {type: 'video'});
1398
+ statsAnalyzerStub.emit(
1399
+ {file: 'test', function: 'test'},
1400
+ StatsAnalyzerModule.EVENTS.REMOTE_MEDIA_STARTED,
1401
+ {type: 'video'}
1402
+ );
1255
1403
 
1256
1404
  assert.calledWith(
1257
1405
  TriggerProxy.trigger,
1258
1406
  sinon.match.instanceOf(Meeting),
1259
1407
  {
1260
1408
  file: 'meeting/index',
1261
- function: 'addMedia'
1409
+ function: 'addMedia',
1262
1410
  },
1263
1411
  EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
1264
1412
  {
1265
- type: 'video'
1413
+ type: 'video',
1266
1414
  }
1267
1415
  );
1268
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.RECEIVING_MEDIA_START, data: {mediaType: 'video'}});
1416
+ assert.calledWithMatch(Metrics.postEvent, {
1417
+ event: eventType.RECEIVING_MEDIA_START,
1418
+ data: {mediaType: 'video'},
1419
+ });
1269
1420
  });
1270
1421
 
1271
1422
  it('REMOTE_MEDIA_STOPPED triggers the right metrics', async () => {
1272
- statsAnalyzerStub.emit({file: 'test', function: 'test'}, StatsAnalyzerModule.EVENTS.REMOTE_MEDIA_STOPPED, {type: 'audio'});
1423
+ statsAnalyzerStub.emit(
1424
+ {file: 'test', function: 'test'},
1425
+ StatsAnalyzerModule.EVENTS.REMOTE_MEDIA_STOPPED,
1426
+ {type: 'audio'}
1427
+ );
1273
1428
 
1274
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.RECEIVING_MEDIA_STOP, data: {mediaType: 'audio'}});
1429
+ assert.calledWithMatch(Metrics.postEvent, {
1430
+ event: eventType.RECEIVING_MEDIA_STOP,
1431
+ data: {mediaType: 'audio'},
1432
+ });
1275
1433
  });
1276
1434
 
1277
1435
  it('MEDIA_QUALITY triggers the right metrics', async () => {
@@ -1283,7 +1441,10 @@ describe('plugin-meetings', () => {
1283
1441
  {data: fakeData, networkType: 'wifi'}
1284
1442
  );
1285
1443
 
1286
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.MEDIA_QUALITY, data: {intervalData: fakeData, networkType: 'wifi'}});
1444
+ assert.calledWithMatch(Metrics.postEvent, {
1445
+ event: eventType.MEDIA_QUALITY,
1446
+ data: {intervalData: fakeData, networkType: 'wifi'},
1447
+ });
1287
1448
  });
1288
1449
  });
1289
1450
  });
@@ -1346,7 +1507,9 @@ describe('plugin-meetings', () => {
1346
1507
  sandbox = sinon.createSandbox();
1347
1508
  meeting.meetingFiniteStateMachine.ring();
1348
1509
  meeting.meetingFiniteStateMachine.join();
1349
- meeting.meetingRequest.leaveMeeting = sinon.stub().returns(Promise.resolve({body: 'test'}));
1510
+ meeting.meetingRequest.leaveMeeting = sinon
1511
+ .stub()
1512
+ .returns(Promise.resolve({body: 'test'}));
1350
1513
  meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
1351
1514
  // the 3 need to be promises because we do closeLocalStream.then(closeLocalShare.then) etc in the src code
1352
1515
  meeting.closeLocalStream = sinon.stub().returns(Promise.resolve());
@@ -1361,6 +1524,7 @@ describe('plugin-meetings', () => {
1361
1524
  meeting.unsetRemoteStream = sinon.stub().returns(true);
1362
1525
  meeting.unsetPeerConnections = sinon.stub().returns(true);
1363
1526
  meeting.logger.error = sinon.stub().returns(true);
1527
+ meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
1364
1528
 
1365
1529
  // A meeting needs to be joined to leave
1366
1530
  meeting.meetingState = 'ACTIVE';
@@ -1415,7 +1579,7 @@ describe('plugin-meetings', () => {
1415
1579
  correlationId: meeting.correlationId,
1416
1580
  selfId: meeting.selfId,
1417
1581
  resourceId: null,
1418
- deviceUrl: meeting.deviceUrl
1582
+ deviceUrl: meeting.deviceUrl,
1419
1583
  });
1420
1584
  });
1421
1585
  it('should leave the meeting on the resource', async () => {
@@ -1428,13 +1592,13 @@ describe('plugin-meetings', () => {
1428
1592
  correlationId: meeting.correlationId,
1429
1593
  selfId: meeting.selfId,
1430
1594
  resourceId: meeting.resourceId,
1431
- deviceUrl: meeting.deviceUrl
1595
+ deviceUrl: meeting.deviceUrl,
1432
1596
  });
1433
1597
  });
1434
1598
  });
1435
- describe('#share', () => {
1436
- it('should have #share', () => {
1437
- assert.exists(meeting.share);
1599
+ describe('#requestScreenShareFloor', () => {
1600
+ it('should have #requestScreenShareFloor', () => {
1601
+ assert.exists(meeting.requestScreenShareFloor);
1438
1602
  });
1439
1603
  beforeEach(() => {
1440
1604
  meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
@@ -1442,7 +1606,7 @@ describe('plugin-meetings', () => {
1442
1606
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
1443
1607
  });
1444
1608
  it('should send the share', async () => {
1445
- const share = meeting.share();
1609
+ const share = meeting.requestScreenShareFloor();
1446
1610
 
1447
1611
  assert.exists(share.then);
1448
1612
  await share;
@@ -1455,7 +1619,9 @@ describe('plugin-meetings', () => {
1455
1619
 
1456
1620
  beforeEach(() => {
1457
1621
  _mediaDirection = meeting.mediaProperties.mediaDirection || {};
1458
- sinon.stub(meeting.mediaProperties, 'mediaDirection').value({sendAudio: true, sendVideo: true, sendShare: false});
1622
+ sinon
1623
+ .stub(meeting.mediaProperties, 'mediaDirection')
1624
+ .value({sendAudio: true, sendVideo: true, sendShare: false});
1459
1625
  });
1460
1626
 
1461
1627
  afterEach(() => {
@@ -1492,7 +1658,11 @@ describe('plugin-meetings', () => {
1492
1658
  it('properly assigns default values', async () => {
1493
1659
  await meeting.shareScreen({sharePreferences: {highFrameRate: true}});
1494
1660
 
1495
- assert.calledWith(Media.getDisplayMedia, {sendShare: true, sendAudio: false, sharePreferences: {highFrameRate: true}});
1661
+ assert.calledWith(Media.getDisplayMedia, {
1662
+ sendShare: true,
1663
+ sendAudio: false,
1664
+ sharePreferences: {highFrameRate: true},
1665
+ });
1496
1666
  });
1497
1667
  });
1498
1668
 
@@ -1522,18 +1692,21 @@ describe('plugin-meetings', () => {
1522
1692
  sendShare,
1523
1693
  receiveShare,
1524
1694
  stream,
1525
- skipSignalingCheck: true
1695
+ skipSignalingCheck: true,
1526
1696
  });
1527
1697
 
1528
1698
  assert.notCalled(meeting.canUpdateMedia);
1529
1699
  });
1530
1700
 
1531
-
1532
1701
  it('skips canUpdateMedia() check on contentTracks.onended', () => {
1533
1702
  const {mediaProperties} = meeting;
1703
+ let registeredListener = null;
1534
1704
  const fakeTrack = {
1535
1705
  getSettings: sinon.stub().returns({}),
1536
- onended: sinon.stub()
1706
+ onended: sinon.stub(),
1707
+ addEventListener: sinon.stub().callsFake((event, listener) => {
1708
+ registeredListener = listener;
1709
+ }),
1537
1710
  };
1538
1711
 
1539
1712
  sandbox.stub(mediaProperties, 'setLocalShareTrack');
@@ -1542,17 +1715,20 @@ describe('plugin-meetings', () => {
1542
1715
  sandbox.stub(meeting, 'stopShare').resolves(true);
1543
1716
  meeting.setLocalShareTrack(fakeTrack);
1544
1717
 
1545
- fakeTrack.onended();
1718
+ assert.calledOnce(fakeTrack.addEventListener);
1719
+ assert.calledWith(fakeTrack.addEventListener, 'ended', sinon.match.any);
1720
+ assert.isNotNull(registeredListener);
1721
+
1722
+ registeredListener();
1546
1723
 
1547
1724
  assert.calledWith(meeting.stopShare, {skipSignalingCheck: true});
1548
1725
  });
1549
1726
 
1550
-
1551
1727
  it('stopShare accepts and passes along optional parameters', () => {
1552
1728
  const args = {
1553
1729
  abc: 123,
1554
1730
  receiveShare: false,
1555
- sendShare: false
1731
+ sendShare: false,
1556
1732
  };
1557
1733
 
1558
1734
  sandbox.stub(meeting, 'updateShare').returns(Promise.resolve());
@@ -1577,24 +1753,22 @@ describe('plugin-meetings', () => {
1577
1753
  });
1578
1754
 
1579
1755
  it('handleShareTrackEnded triggers an event', () => {
1580
- const stream = 'stream';
1581
1756
  const {EVENT_TYPES} = CONSTANTS;
1582
1757
 
1583
1758
  sandbox.stub(meeting, 'stopShare').resolves(true);
1584
1759
 
1585
- meeting.handleShareTrackEnded(stream);
1760
+ meeting.handleShareTrackEnded();
1586
1761
 
1587
1762
  assert.calledWith(
1588
1763
  TriggerProxy.trigger,
1589
1764
  sinon.match.instanceOf(Meeting),
1590
1765
  {
1591
1766
  file: 'meeting/index',
1592
- function: 'handleShareTrackEnded'
1767
+ function: 'handleShareTrackEnded',
1593
1768
  },
1594
1769
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
1595
1770
  {
1596
- stream,
1597
- type: EVENT_TYPES.LOCAL_SHARE
1771
+ type: EVENT_TYPES.LOCAL_SHARE,
1598
1772
  }
1599
1773
  );
1600
1774
  });
@@ -1607,20 +1781,22 @@ describe('plugin-meetings', () => {
1607
1781
  const {resolution} = config;
1608
1782
  const shareOptions = {
1609
1783
  sendShare: true,
1610
- sendAudio: false
1784
+ sendAudio: false,
1611
1785
  };
1612
1786
  const fireFoxOptions = {
1613
1787
  audio: false,
1614
1788
  video: {
1615
1789
  audio: shareOptions.sendAudio,
1616
- video: shareOptions.sendShare
1617
- }
1790
+ video: shareOptions.sendShare,
1791
+ },
1618
1792
  };
1619
1793
 
1620
1794
  const MediaStream = {
1621
- getVideoTracks: () => [{
1622
- applyConstraints: () => {}
1623
- }]
1795
+ getVideoTracks: () => [
1796
+ {
1797
+ applyConstraints: () => {},
1798
+ },
1799
+ ],
1624
1800
  };
1625
1801
 
1626
1802
  const MediaConstraint = {
@@ -1628,7 +1804,7 @@ describe('plugin-meetings', () => {
1628
1804
  aspectRatio: config.aspectRatio,
1629
1805
  frameRate: config.screenFrameRate,
1630
1806
  width: null,
1631
- height: null
1807
+ height: null,
1632
1808
  };
1633
1809
 
1634
1810
  const browserConditionalValue = (value) => {
@@ -1644,31 +1820,23 @@ describe('plugin-meetings', () => {
1644
1820
  if (!global.navigator) {
1645
1821
  global.navigator = {
1646
1822
  mediaDevices: {
1647
- getDisplayMedia: null
1648
- }
1823
+ getDisplayMedia: null,
1824
+ },
1649
1825
  };
1650
1826
  }
1651
1827
  _getDisplayMedia = global.navigator.mediaDevices.getDisplayMedia;
1652
- Object.defineProperty(
1653
- global.navigator.mediaDevices,
1654
- 'getDisplayMedia',
1655
- {
1656
- value: sinon.stub().returns(Promise.resolve(MediaStream)),
1657
- writable: true
1658
- }
1659
- );
1828
+ Object.defineProperty(global.navigator.mediaDevices, 'getDisplayMedia', {
1829
+ value: sinon.stub().returns(Promise.resolve(MediaStream)),
1830
+ writable: true,
1831
+ });
1660
1832
  });
1661
1833
 
1662
1834
  after(() => {
1663
1835
  // clean up for browser
1664
- Object.defineProperty(
1665
- global.navigator.mediaDevices,
1666
- 'getDisplayMedia',
1667
- {
1668
- value: _getDisplayMedia,
1669
- writable: true
1670
- }
1671
- );
1836
+ Object.defineProperty(global.navigator.mediaDevices, 'getDisplayMedia', {
1837
+ value: _getDisplayMedia,
1838
+ writable: true,
1839
+ });
1672
1840
  });
1673
1841
 
1674
1842
  // eslint-disable-next-line max-len
@@ -1680,39 +1848,48 @@ describe('plugin-meetings', () => {
1680
1848
  maxWidth: SHARE_WIDTH,
1681
1849
  maxHeight: SHARE_HEIGHT,
1682
1850
  idealWidth: SHARE_WIDTH,
1683
- idealHeight: SHARE_HEIGHT
1851
+ idealHeight: SHARE_HEIGHT,
1684
1852
  };
1685
1853
 
1686
1854
  // If sharePreferences.shareConstraints is defined it ignores
1687
1855
  // default SDK config settings
1688
- getDisplayMedia({
1689
- ...shareOptions,
1690
- sharePreferences: {shareConstraints}
1691
- }, config);
1856
+ getDisplayMedia(
1857
+ {
1858
+ ...shareOptions,
1859
+ sharePreferences: {shareConstraints},
1860
+ },
1861
+ config
1862
+ );
1692
1863
 
1693
1864
  // eslint-disable-next-line no-undef
1694
- assert.calledWith(navigator.mediaDevices.getDisplayMedia,
1865
+ assert.calledWith(
1866
+ navigator.mediaDevices.getDisplayMedia,
1695
1867
  browserConditionalValue({
1696
1868
  default: {
1697
- video: {...shareConstraints}
1869
+ video: {...shareConstraints},
1698
1870
  },
1699
1871
  // Firefox is being handled differently
1700
- firefox: fireFoxOptions
1701
- }));
1872
+ firefox: fireFoxOptions,
1873
+ })
1874
+ );
1702
1875
  });
1703
1876
 
1704
1877
  // eslint-disable-next-line max-len
1705
1878
  it('will use default resolution if shareConstraints is undefined and highFrameRate is defined', () => {
1706
1879
  // If highFrameRate is defined it ignores default SDK config settings
1707
- getDisplayMedia({
1708
- ...shareOptions,
1709
- sharePreferences: {
1710
- highFrameRate: true
1711
- }
1712
- }, config);
1880
+ getDisplayMedia(
1881
+ {
1882
+ ...shareOptions,
1883
+ sharePreferences: {
1884
+ highFrameRate: true,
1885
+ },
1886
+ },
1887
+ config
1888
+ );
1713
1889
 
1714
1890
  // eslint-disable-next-line no-undef
1715
- assert.calledWith(navigator.mediaDevices.getDisplayMedia,
1891
+ assert.calledWith(
1892
+ navigator.mediaDevices.getDisplayMedia,
1716
1893
  browserConditionalValue({
1717
1894
  default: {
1718
1895
  video: {
@@ -1723,11 +1900,12 @@ describe('plugin-meetings', () => {
1723
1900
  maxWidth: resolution.maxWidth,
1724
1901
  maxHeight: resolution.maxHeight,
1725
1902
  idealWidth: resolution.idealWidth,
1726
- idealHeight: resolution.idealHeight
1727
- }
1903
+ idealHeight: resolution.idealHeight,
1904
+ },
1728
1905
  },
1729
- firefox: fireFoxOptions
1730
- }));
1906
+ firefox: fireFoxOptions,
1907
+ })
1908
+ );
1731
1909
  });
1732
1910
 
1733
1911
  // eslint-disable-next-line max-len
@@ -1736,17 +1914,19 @@ describe('plugin-meetings', () => {
1736
1914
  const {screenResolution} = config;
1737
1915
 
1738
1916
  // eslint-disable-next-line no-undef
1739
- assert.calledWith(navigator.mediaDevices.getDisplayMedia,
1917
+ assert.calledWith(
1918
+ navigator.mediaDevices.getDisplayMedia,
1740
1919
  browserConditionalValue({
1741
1920
  default: {
1742
1921
  video: {
1743
1922
  ...MediaConstraint,
1744
1923
  width: screenResolution.idealWidth,
1745
- height: screenResolution.idealHeight
1746
- }
1924
+ height: screenResolution.idealHeight,
1925
+ },
1747
1926
  },
1748
- firefox: fireFoxOptions
1749
- }));
1927
+ firefox: fireFoxOptions,
1928
+ })
1929
+ );
1750
1930
  });
1751
1931
 
1752
1932
  // Test screenResolution
@@ -1759,14 +1939,15 @@ describe('plugin-meetings', () => {
1759
1939
  maxWidth: SHARE_WIDTH,
1760
1940
  maxHeight: SHARE_HEIGHT,
1761
1941
  idealWidth: SHARE_WIDTH,
1762
- idealHeight: SHARE_HEIGHT
1763
- }
1942
+ idealHeight: SHARE_HEIGHT,
1943
+ },
1764
1944
  };
1765
1945
 
1766
1946
  getDisplayMedia(shareOptions, customConfig);
1767
1947
 
1768
1948
  // eslint-disable-next-line no-undef
1769
- assert.calledWith(navigator.mediaDevices.getDisplayMedia,
1949
+ assert.calledWith(
1950
+ navigator.mediaDevices.getDisplayMedia,
1770
1951
  browserConditionalValue({
1771
1952
  default: {
1772
1953
  video: {
@@ -1776,11 +1957,12 @@ describe('plugin-meetings', () => {
1776
1957
  maxWidth: SHARE_WIDTH,
1777
1958
  maxHeight: SHARE_HEIGHT,
1778
1959
  idealWidth: SHARE_WIDTH,
1779
- idealHeight: SHARE_HEIGHT
1780
- }
1960
+ idealHeight: SHARE_HEIGHT,
1961
+ },
1781
1962
  },
1782
- firefox: fireFoxOptions
1783
- }));
1963
+ firefox: fireFoxOptions,
1964
+ })
1965
+ );
1784
1966
  });
1785
1967
 
1786
1968
  // Test screenFrameRate
@@ -1793,14 +1975,15 @@ describe('plugin-meetings', () => {
1793
1975
  maxWidth: SHARE_WIDTH,
1794
1976
  maxHeight: SHARE_HEIGHT,
1795
1977
  idealWidth: SHARE_WIDTH,
1796
- idealHeight: SHARE_HEIGHT
1797
- }
1978
+ idealHeight: SHARE_HEIGHT,
1979
+ },
1798
1980
  };
1799
1981
 
1800
1982
  getDisplayMedia(shareOptions, customConfig);
1801
1983
 
1802
1984
  // eslint-disable-next-line no-undef
1803
- assert.calledWith(navigator.mediaDevices.getDisplayMedia,
1985
+ assert.calledWith(
1986
+ navigator.mediaDevices.getDisplayMedia,
1804
1987
  browserConditionalValue({
1805
1988
  default: {
1806
1989
  video: {
@@ -1811,11 +1994,12 @@ describe('plugin-meetings', () => {
1811
1994
  maxWidth: SHARE_WIDTH,
1812
1995
  maxHeight: SHARE_HEIGHT,
1813
1996
  idealWidth: SHARE_WIDTH,
1814
- idealHeight: SHARE_HEIGHT
1815
- }
1997
+ idealHeight: SHARE_HEIGHT,
1998
+ },
1816
1999
  },
1817
- firefox: fireFoxOptions
1818
- }));
2000
+ firefox: fireFoxOptions,
2001
+ })
2002
+ );
1819
2003
  });
1820
2004
  });
1821
2005
 
@@ -1857,22 +2041,35 @@ describe('plugin-meetings', () => {
1857
2041
  receiveVideo: true,
1858
2042
  receiveShare: true,
1859
2043
  };
1860
- meeting.mediaProperties.webrtcMediaConnection = {updateSendReceiveOptions: sinon.stub()};
2044
+ meeting.mediaProperties.webrtcMediaConnection = {
2045
+ updateSendReceiveOptions: sinon.stub(),
2046
+ };
1861
2047
  sinon.stub(MeetingUtil, 'getTrack').returns({audioTrack: FAKE_AUDIO_TRACK});
1862
2048
  });
1863
- it('calls this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions', () => meeting.updateAudio({
1864
- sendAudio: true,
1865
- receiveAudio: true,
1866
- stream: {id: 'fake stream'}
1867
- }).then(() => {
1868
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
1869
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions, {
1870
- send: {audio: FAKE_AUDIO_TRACK},
1871
- receive: {
1872
- audio: true, video: true, screenShareVideo: true, remoteQualityLevel: 'HIGH'
1873
- }
1874
- });
1875
- }));
2049
+ it('calls this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions', () =>
2050
+ meeting
2051
+ .updateAudio({
2052
+ sendAudio: true,
2053
+ receiveAudio: true,
2054
+ stream: {id: 'fake stream'},
2055
+ })
2056
+ .then(() => {
2057
+ assert.calledOnce(
2058
+ meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions
2059
+ );
2060
+ assert.calledWith(
2061
+ meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions,
2062
+ {
2063
+ send: {audio: FAKE_AUDIO_TRACK},
2064
+ receive: {
2065
+ audio: true,
2066
+ video: true,
2067
+ screenShareVideo: true,
2068
+ remoteQualityLevel: 'HIGH',
2069
+ },
2070
+ }
2071
+ );
2072
+ }));
1876
2073
  });
1877
2074
  afterEach(() => {
1878
2075
  sinon.restore();
@@ -1892,7 +2089,7 @@ describe('plugin-meetings', () => {
1892
2089
 
1893
2090
  meeting.locusInfo.self = {
1894
2091
  enableDTMF: true,
1895
- url: url2
2092
+ url: url2,
1896
2093
  };
1897
2094
 
1898
2095
  await meeting.sendDTMF(tones);
@@ -1900,7 +2097,7 @@ describe('plugin-meetings', () => {
1900
2097
  assert.calledWith(meeting.meetingRequest.sendDTMF, {
1901
2098
  locusUrl: meeting.locusInfo.self.url,
1902
2099
  deviceUrl: meeting.deviceUrl,
1903
- tones
2100
+ tones,
1904
2101
  });
1905
2102
  });
1906
2103
 
@@ -1914,7 +2111,7 @@ describe('plugin-meetings', () => {
1914
2111
  it('should throw an error', () => {
1915
2112
  meeting.locusInfo.self = {
1916
2113
  enableDTMF: false,
1917
- url: url2
2114
+ url: url2,
1918
2115
  };
1919
2116
 
1920
2117
  assert.isRejected(meeting.sendDTMF('123'));
@@ -1938,8 +2135,8 @@ describe('plugin-meetings', () => {
1938
2135
  screenshareVideo: {
1939
2136
  id: 'fake share track',
1940
2137
  getSettings: sinon.stub().returns({}),
2138
+ addEventListener: sinon.stub()
1941
2139
  },
1942
-
1943
2140
  };
1944
2141
 
1945
2142
  beforeEach(() => {
@@ -1971,21 +2168,22 @@ describe('plugin-meetings', () => {
1971
2168
  receiveVideo: true,
1972
2169
  sendShare: true,
1973
2170
  receiveShare: true,
1974
- isSharing: true
2171
+ isSharing: true,
1975
2172
  };
1976
2173
 
1977
2174
  sandbox.stub(meeting, 'canUpdateMedia').returns(false);
1978
2175
  meeting.mediaProperties.webrtcMediaConnection = {
1979
- updateSendReceiveOptions: sinon.stub().resolves({})
2176
+ updateSendReceiveOptions: sinon.stub().resolves({}),
1980
2177
  };
1981
2178
 
1982
2179
  let myPromiseResolved = false;
1983
2180
 
1984
- meeting.updateMedia({
1985
- localStream: mockLocalStream,
1986
- localShare: mockLocalShare,
1987
- mediaSettings
1988
- })
2181
+ meeting
2182
+ .updateMedia({
2183
+ localStream: mockLocalStream,
2184
+ localShare: mockLocalShare,
2185
+ mediaSettings,
2186
+ })
1989
2187
  .then(() => {
1990
2188
  myPromiseResolved = true;
1991
2189
  });
@@ -2002,63 +2200,255 @@ describe('plugin-meetings', () => {
2002
2200
 
2003
2201
  // and check that updateSendReceiveOptions is called with the original args
2004
2202
  assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2005
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions, {
2006
- send: {
2007
- audio: FAKE_TRACKS.audio,
2008
- video: FAKE_TRACKS.video,
2009
- screenShareVideo: FAKE_TRACKS.screenshareVideo,
2010
- },
2011
- receive: {
2012
- audio: true,
2013
- video: true,
2014
- screenShareVideo: true,
2015
- remoteQualityLevel: 'HIGH'
2203
+ assert.calledWith(
2204
+ meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions,
2205
+ {
2206
+ send: {
2207
+ audio: FAKE_TRACKS.audio,
2208
+ video: FAKE_TRACKS.video,
2209
+ screenShareVideo: FAKE_TRACKS.screenshareVideo,
2210
+ },
2211
+ receive: {
2212
+ audio: true,
2213
+ video: true,
2214
+ screenShareVideo: true,
2215
+ remoteQualityLevel: 'HIGH',
2216
+ },
2016
2217
  }
2017
- });
2218
+ );
2018
2219
  assert.isTrue(myPromiseResolved);
2019
2220
  });
2020
- });
2021
2221
 
2022
- describe('#changeVideoLayout', () => {
2023
- describe('when media direction has recieve video and there is remoteStream', () => {
2024
- let mediaDirection;
2025
- const layoutTypeSingle = 'Single';
2222
+ it('should request floor only after roap transaction is completed', async () => {
2223
+ const eventListeners = {};
2026
2224
 
2027
- beforeEach(() => {
2028
- mediaDirection = {
2029
- sendAudio: true,
2030
- sendVideo: true,
2031
- sendShare: false,
2032
- receiveVideo: true
2033
- };
2034
- meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
2035
- meeting.updateVideo = sinon.stub().returns(Promise.resolve());
2036
- meeting.mediaProperties.mediaDirection = mediaDirection;
2037
- meeting.mediaProperties.remoteVideoTrack = sinon.stub().returns({mockTrack: 'mockTrack'});
2225
+ meeting.webex.meetings.reachability = {
2226
+ isAnyClusterReachable: sandbox.stub().resolves(true)
2227
+ };
2038
2228
 
2039
- meeting.meetingRequest.changeVideoLayoutDebounced = sinon.stub().returns(Promise.resolve());
2229
+ const fakeMediaConnection = {
2230
+ close: sinon.stub(),
2231
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
2232
+ initiateOffer: sinon.stub().resolves({}),
2040
2233
 
2041
- meeting.locusInfo.self = {
2042
- url: url2
2043
- };
2044
- });
2234
+ // mock the on() method and store all the listeners
2235
+ on: sinon.stub().callsFake((event, listener) => {
2236
+ eventListeners[event] = listener;
2237
+ }),
2238
+
2239
+ updateSendReceiveOptions: sinon.stub().callsFake(() => {
2240
+ // trigger ROAP_STARTED before updateSendReceiveOptions() resolves
2241
+ if (eventListeners[Event.ROAP_STARTED]) {
2242
+ eventListeners[Event.ROAP_STARTED]();
2243
+ } else {
2244
+ throw new Error('ROAP_STARTED listener not registered')
2245
+ }
2246
+ return Promise.resolve();
2247
+ }),
2248
+ };
2045
2249
 
2046
- it('should listen once for CONTROLS_MEETING_LAYOUT_UPDATED', async () => {
2047
- // const spy = sinon.spy(TriggerProxy, 'trigger');
2048
- const spy = sinon.spy(meeting.locusInfo, 'once');
2250
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2251
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
2252
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
2049
2253
 
2050
- await meeting.changeVideoLayout('Equal');
2254
+ const requestScreenShareFloorStub = sandbox.stub(meeting, 'requestScreenShareFloor').resolves({});
2051
2255
 
2052
- assert.calledWith(spy, LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED);
2053
- });
2256
+ let myPromiseResolved = false;
2054
2257
 
2055
- it('should have receiveVideo true and remote video track should exist', () => {
2056
- assert.equal(meeting.mediaProperties.mediaDirection.receiveVideo, true);
2057
- assert.exists(meeting.mediaProperties.remoteVideoTrack);
2258
+ meeting.meetingState = 'ACTIVE';
2259
+ await meeting.addMedia({
2260
+ mediaSettings: {},
2058
2261
  });
2059
2262
 
2060
- it('has layoutType which exists in the list of allowed layoutTypes and should call meetingRequest changeVideoLayoutDebounced method', async () => {
2061
- const layoutType = 'Equal';
2263
+ meeting
2264
+ .updateMedia({
2265
+ localShare: mockLocalShare,
2266
+ mediaSettings: {
2267
+ sendShare: true,
2268
+ },
2269
+ })
2270
+ .then(() => {
2271
+ myPromiseResolved = true;
2272
+ });
2273
+
2274
+ await testUtils.flushPromises();
2275
+
2276
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2277
+ assert.isFalse(myPromiseResolved);
2278
+
2279
+ // verify that requestScreenShareFloorStub was not called yet
2280
+ assert.notCalled(requestScreenShareFloorStub);
2281
+
2282
+ eventListeners[Event.ROAP_DONE]();
2283
+ await testUtils.flushPromises();
2284
+
2285
+ // now it should have been called
2286
+ assert.calledOnce(requestScreenShareFloorStub);
2287
+ });
2288
+ });
2289
+
2290
+ describe('#updateShare', () => {
2291
+ const mockLocalShare = {id: 'mock local share stream'};
2292
+ let eventListeners;
2293
+ let fakeMediaConnection;
2294
+ let requestScreenShareFloorStub;
2295
+
2296
+ const FAKE_TRACKS = {
2297
+ screenshareVideo: {
2298
+ id: 'fake share track',
2299
+ getSettings: sinon.stub().returns({}),
2300
+ addEventListener: sinon.stub(),
2301
+ },
2302
+ };
2303
+
2304
+ beforeEach(async () => {
2305
+ eventListeners = {};
2306
+
2307
+ sinon.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
2308
+ if (stream === mockLocalShare) {
2309
+ return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
2310
+ }
2311
+
2312
+ return {audioTrack: null, videoTrack: null};
2313
+ });
2314
+
2315
+ meeting.webex.meetings.reachability = {
2316
+ isAnyClusterReachable: sinon.stub().resolves(true)
2317
+ };
2318
+
2319
+ fakeMediaConnection = {
2320
+ close: sinon.stub(),
2321
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
2322
+ initiateOffer: sinon.stub().resolves({}),
2323
+
2324
+ // mock the on() method and store all the listeners
2325
+ on: sinon.stub().callsFake((event, listener) => {
2326
+ eventListeners[event] = listener;
2327
+ }),
2328
+
2329
+ updateSendReceiveOptions: sinon.stub().callsFake(() => {
2330
+ // trigger ROAP_STARTED before updateSendReceiveOptions() resolves
2331
+ if (eventListeners[Event.ROAP_STARTED]) {
2332
+ eventListeners[Event.ROAP_STARTED]();
2333
+ } else {
2334
+ throw new Error('ROAP_STARTED listener not registered')
2335
+ }
2336
+ return Promise.resolve();
2337
+ }),
2338
+ };
2339
+
2340
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2341
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
2342
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
2343
+
2344
+ requestScreenShareFloorStub = sinon.stub(meeting, 'requestScreenShareFloor').resolves({});
2345
+
2346
+ meeting.meetingState = 'ACTIVE';
2347
+ await meeting.addMedia({
2348
+ mediaSettings: {},
2349
+ });
2350
+ });
2351
+
2352
+ afterEach(() => {
2353
+ sinon.restore();
2354
+ });
2355
+
2356
+ it('when starting share, it should request floor only after roap transaction is completed', async () => {
2357
+ let myPromiseResolved = false;
2358
+
2359
+ meeting
2360
+ .updateShare({
2361
+ sendShare: true,
2362
+ receiveShare: true,
2363
+ stream: mockLocalShare,
2364
+ })
2365
+ .then(() => {
2366
+ myPromiseResolved = true;
2367
+ });
2368
+
2369
+ await testUtils.flushPromises();
2370
+
2371
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2372
+ assert.isFalse(myPromiseResolved);
2373
+
2374
+ // verify that requestScreenShareFloorStub was not called yet
2375
+ assert.notCalled(requestScreenShareFloorStub);
2376
+
2377
+ eventListeners[Event.ROAP_DONE]();
2378
+ await testUtils.flushPromises();
2379
+
2380
+ // now it should have been called
2381
+ assert.calledOnce(requestScreenShareFloorStub);
2382
+ });
2383
+
2384
+ it('when changing screen share stream and no roap transaction happening, it requests floor immediately', async () => {
2385
+ let myPromiseResolved = false;
2386
+
2387
+ // simulate a case when no roap transaction is triggered by updateSendReceiveOptions
2388
+ meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions = sinon.stub().resolves({});
2389
+
2390
+ meeting
2391
+ .updateShare({
2392
+ sendShare: true,
2393
+ receiveShare: true,
2394
+ stream: mockLocalShare,
2395
+ })
2396
+ .then(() => {
2397
+ myPromiseResolved = true;
2398
+ });
2399
+
2400
+ await testUtils.flushPromises();
2401
+
2402
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2403
+ assert.calledOnce(requestScreenShareFloorStub);
2404
+ assert.isTrue(myPromiseResolved);
2405
+ });
2406
+ });
2407
+
2408
+ describe('#changeVideoLayout', () => {
2409
+ describe('when media direction has recieve video and there is remoteStream', () => {
2410
+ let mediaDirection;
2411
+ const layoutTypeSingle = 'Single';
2412
+
2413
+ beforeEach(() => {
2414
+ mediaDirection = {
2415
+ sendAudio: true,
2416
+ sendVideo: true,
2417
+ sendShare: false,
2418
+ receiveVideo: true,
2419
+ };
2420
+ meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
2421
+ meeting.updateVideo = sinon.stub().returns(Promise.resolve());
2422
+ meeting.mediaProperties.mediaDirection = mediaDirection;
2423
+ meeting.mediaProperties.remoteVideoTrack = sinon
2424
+ .stub()
2425
+ .returns({mockTrack: 'mockTrack'});
2426
+
2427
+ meeting.meetingRequest.changeVideoLayoutDebounced = sinon
2428
+ .stub()
2429
+ .returns(Promise.resolve());
2430
+
2431
+ meeting.locusInfo.self = {
2432
+ url: url2,
2433
+ };
2434
+ });
2435
+
2436
+ it('should listen once for CONTROLS_MEETING_LAYOUT_UPDATED', async () => {
2437
+ // const spy = sinon.spy(TriggerProxy, 'trigger');
2438
+ const spy = sinon.spy(meeting.locusInfo, 'once');
2439
+
2440
+ await meeting.changeVideoLayout('Equal');
2441
+
2442
+ assert.calledWith(spy, LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED);
2443
+ });
2444
+
2445
+ it('should have receiveVideo true and remote video track should exist', () => {
2446
+ assert.equal(meeting.mediaProperties.mediaDirection.receiveVideo, true);
2447
+ assert.exists(meeting.mediaProperties.remoteVideoTrack);
2448
+ });
2449
+
2450
+ it('has layoutType which exists in the list of allowed layoutTypes and should call meetingRequest changeVideoLayoutDebounced method', async () => {
2451
+ const layoutType = 'Equal';
2062
2452
 
2063
2453
  await meeting.changeVideoLayout(layoutType);
2064
2454
 
@@ -2068,11 +2458,11 @@ describe('plugin-meetings', () => {
2068
2458
  deviceUrl: meeting.deviceUrl,
2069
2459
  layoutType,
2070
2460
  main: undefined,
2071
- content: undefined
2461
+ content: undefined,
2072
2462
  });
2073
2463
  });
2074
2464
 
2075
- it('doesn\'t have layoutType which exists in the list of allowed layoutTypes should throw an error', async () => {
2465
+ it("doesn't have layoutType which exists in the list of allowed layoutTypes should throw an error", async () => {
2076
2466
  const layoutType = 'Invalid Layout';
2077
2467
 
2078
2468
  assert.isRejected(meeting.changeVideoLayout(layoutType));
@@ -2086,12 +2476,14 @@ describe('plugin-meetings', () => {
2086
2476
  deviceUrl: meeting.deviceUrl,
2087
2477
  layoutType: undefined,
2088
2478
  main: {width: 100, height: 200},
2089
- content: undefined
2479
+ content: undefined,
2090
2480
  });
2091
2481
  });
2092
2482
 
2093
2483
  it('throws if trying to send renderInfo for content when not receiving content', async () => {
2094
- assert.isRejected(meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 1280, height: 720}}));
2484
+ assert.isRejected(
2485
+ meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 1280, height: 720}})
2486
+ );
2095
2487
  });
2096
2488
 
2097
2489
  it('calls changeVideoLayoutDebounced with renderInfo for main and content', async () => {
@@ -2103,7 +2495,7 @@ describe('plugin-meetings', () => {
2103
2495
  deviceUrl: meeting.deviceUrl,
2104
2496
  layoutType: layoutTypeSingle,
2105
2497
  main: {width: 100, height: 200},
2106
- content: undefined
2498
+ content: undefined,
2107
2499
  });
2108
2500
 
2109
2501
  meeting.mediaProperties.mediaDirection.receiveShare = true;
@@ -2117,18 +2509,21 @@ describe('plugin-meetings', () => {
2117
2509
  deviceUrl: meeting.deviceUrl,
2118
2510
  layoutType: layoutTypeSingle,
2119
2511
  main: {width: 100, height: 200},
2120
- content: {width: 500, height: 600}
2512
+ content: {width: 500, height: 600},
2121
2513
  });
2122
2514
 
2123
2515
  // and now call with both
2124
- await meeting.changeVideoLayout(layoutTypeSingle, {main: {width: 300, height: 400}, content: {width: 700, height: 800}});
2516
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2517
+ main: {width: 300, height: 400},
2518
+ content: {width: 700, height: 800},
2519
+ });
2125
2520
 
2126
2521
  assert.calledWith(meeting.meetingRequest.changeVideoLayoutDebounced, {
2127
2522
  locusUrl: meeting.locusInfo.self.url,
2128
2523
  deviceUrl: meeting.deviceUrl,
2129
2524
  layoutType: layoutTypeSingle,
2130
2525
  main: {width: 300, height: 400},
2131
- content: {width: 700, height: 800}
2526
+ content: {width: 700, height: 800},
2132
2527
  });
2133
2528
 
2134
2529
  // and now set just the layoutType, the previous main and content values should be used
@@ -2141,7 +2536,7 @@ describe('plugin-meetings', () => {
2141
2536
  deviceUrl: meeting.deviceUrl,
2142
2537
  layoutType,
2143
2538
  main: {width: 300, height: 400},
2144
- content: {width: 700, height: 800}
2539
+ content: {width: 700, height: 800},
2145
2540
  });
2146
2541
  });
2147
2542
 
@@ -2153,7 +2548,7 @@ describe('plugin-meetings', () => {
2153
2548
  deviceUrl: meeting.deviceUrl,
2154
2549
  layoutType: layoutTypeSingle,
2155
2550
  main: {width: 1024, height: 768},
2156
- content: undefined
2551
+ content: undefined,
2157
2552
  });
2158
2553
  meeting.meetingRequest.changeVideoLayoutDebounced.resetHistory();
2159
2554
 
@@ -2175,28 +2570,39 @@ describe('plugin-meetings', () => {
2175
2570
  meeting.mediaProperties.mediaDirection.receiveShare = true;
2176
2571
  meeting.mediaProperties.remoteShare = sinon.stub().returns({mockTrack: 'mockTrack'});
2177
2572
 
2178
- await meeting.changeVideoLayout(layoutTypeSingle, {main: {width: 500, height: 510}, content: {width: 1024, height: 768}});
2573
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2574
+ main: {width: 500, height: 510},
2575
+ content: {width: 1024, height: 768},
2576
+ });
2179
2577
 
2180
2578
  assert.calledWith(meeting.meetingRequest.changeVideoLayoutDebounced, {
2181
2579
  locusUrl: meeting.locusInfo.self.url,
2182
2580
  deviceUrl: meeting.deviceUrl,
2183
2581
  layoutType: layoutTypeSingle,
2184
2582
  main: {width: 500, height: 510},
2185
- content: {width: 1024, height: 768}
2583
+ content: {width: 1024, height: 768},
2186
2584
  });
2187
2585
  meeting.meetingRequest.changeVideoLayoutDebounced.resetHistory();
2188
2586
 
2189
2587
  // now send main with width/height different by just 2px - it should be ignored
2190
- await meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 1026, height: 768}});
2588
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2589
+ content: {width: 1026, height: 768},
2590
+ });
2191
2591
  assert.notCalled(meeting.meetingRequest.changeVideoLayoutDebounced);
2192
2592
 
2193
- await meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 1022, height: 768}});
2593
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2594
+ content: {width: 1022, height: 768},
2595
+ });
2194
2596
  assert.notCalled(meeting.meetingRequest.changeVideoLayoutDebounced);
2195
2597
 
2196
- await meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 1024, height: 770}});
2598
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2599
+ content: {width: 1024, height: 770},
2600
+ });
2197
2601
  assert.notCalled(meeting.meetingRequest.changeVideoLayoutDebounced);
2198
2602
 
2199
- await meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 1024, height: 766}});
2603
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2604
+ content: {width: 1024, height: 766},
2605
+ });
2200
2606
  assert.notCalled(meeting.meetingRequest.changeVideoLayoutDebounced);
2201
2607
  });
2202
2608
 
@@ -2204,14 +2610,17 @@ describe('plugin-meetings', () => {
2204
2610
  meeting.mediaProperties.mediaDirection.receiveShare = true;
2205
2611
  meeting.mediaProperties.remoteShare = sinon.stub().returns({mockTrack: 'mockTrack'});
2206
2612
 
2207
- await meeting.changeVideoLayout(layoutTypeSingle, {main: {width: 500.5, height: 510.09}, content: {width: 1024.2, height: 768.85}});
2613
+ await meeting.changeVideoLayout(layoutTypeSingle, {
2614
+ main: {width: 500.5, height: 510.09},
2615
+ content: {width: 1024.2, height: 768.85},
2616
+ });
2208
2617
 
2209
2618
  assert.calledWith(meeting.meetingRequest.changeVideoLayoutDebounced, {
2210
2619
  locusUrl: meeting.locusInfo.self.url,
2211
2620
  deviceUrl: meeting.deviceUrl,
2212
2621
  layoutType: layoutTypeSingle,
2213
2622
  main: {width: 501, height: 510},
2214
- content: {width: 1024, height: 769}
2623
+ content: {width: 1024, height: 769},
2215
2624
  });
2216
2625
  });
2217
2626
  });
@@ -2223,7 +2632,7 @@ describe('plugin-meetings', () => {
2223
2632
  sendAudio: true,
2224
2633
  sendVideo: true,
2225
2634
  sendShare: false,
2226
- receiveVideo: true
2635
+ receiveVideo: true,
2227
2636
  };
2228
2637
 
2229
2638
  meeting.mediaProperties.mediaDirection = mediaDirection;
@@ -2237,7 +2646,7 @@ describe('plugin-meetings', () => {
2237
2646
  sendAudio: true,
2238
2647
  sendVideo: true,
2239
2648
  sendShare: false,
2240
- receiveVideo: false
2649
+ receiveVideo: false,
2241
2650
  };
2242
2651
 
2243
2652
  meeting.mediaProperties.mediaDirection = mediaDirection;
@@ -2251,8 +2660,8 @@ describe('plugin-meetings', () => {
2251
2660
 
2252
2661
  const fakeTrack = {getSettings: () => ({height: 720})};
2253
2662
  const USER_AGENT_CHROME_MAC =
2254
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
2255
- 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36';
2663
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
2664
+ 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36';
2256
2665
 
2257
2666
  beforeEach(() => {
2258
2667
  mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
@@ -2268,34 +2677,36 @@ describe('plugin-meetings', () => {
2268
2677
  assert.exists(meeting.setLocalVideoQuality);
2269
2678
  });
2270
2679
 
2271
- it('should call getMediaStreams with the proper level', () => meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2272
- delete mediaDirection.receiveVideo;
2273
- assert.calledWith(meeting.getMediaStreams,
2274
- mediaDirection,
2275
- CONSTANTS.VIDEO_RESOLUTIONS[CONSTANTS.QUALITY_LEVELS.LOW]);
2276
- }));
2680
+ it('should call getMediaStreams with the proper level', () =>
2681
+ meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2682
+ delete mediaDirection.receiveVideo;
2683
+ assert.calledWith(
2684
+ meeting.getMediaStreams,
2685
+ mediaDirection,
2686
+ CONSTANTS.VIDEO_RESOLUTIONS[CONSTANTS.QUALITY_LEVELS.LOW]
2687
+ );
2688
+ }));
2277
2689
 
2278
2690
  it('when browser is chrome then it should stop previous video track', () => {
2279
2691
  meeting.mediaProperties.videoTrack = fakeTrack;
2280
- assert.equal(
2281
- BrowserDetection(USER_AGENT_CHROME_MAC).getBrowserName(),
2282
- 'Chrome'
2283
- );
2284
- meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW)
2285
- .then(() => {
2286
- assert.calledWith(Media.stopTracks, fakeTrack);
2287
- });
2692
+ assert.equal(BrowserDetection(USER_AGENT_CHROME_MAC).getBrowserName(), 'Chrome');
2693
+ meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2694
+ assert.calledWith(Media.stopTracks, fakeTrack);
2695
+ });
2288
2696
  });
2289
2697
 
2290
- it('should set mediaProperty with the proper level', () => meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2291
- assert.equal(meeting.mediaProperties.localQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
2292
- }));
2698
+ it('should set mediaProperty with the proper level', () =>
2699
+ meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2700
+ assert.equal(meeting.mediaProperties.localQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
2701
+ }));
2293
2702
 
2294
2703
  it('when device does not support 1080p then it should set localQualityLevel with highest possible resolution', () => {
2295
- meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS['1080p'])
2296
- .then(() => {
2297
- assert.equal(meeting.mediaProperties.localQualityLevel, CONSTANTS.QUALITY_LEVELS['720p']);
2298
- });
2704
+ meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS['1080p']).then(() => {
2705
+ assert.equal(
2706
+ meeting.mediaProperties.localQualityLevel,
2707
+ CONSTANTS.QUALITY_LEVELS['720p']
2708
+ );
2709
+ });
2299
2710
  });
2300
2711
 
2301
2712
  it('should error if set to a invalid level', () => {
@@ -2321,13 +2732,15 @@ describe('plugin-meetings', () => {
2321
2732
  assert.exists(meeting.setRemoteQualityLevel);
2322
2733
  });
2323
2734
 
2324
- it('should set mediaProperty with the proper level', () => meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2325
- assert.equal(meeting.mediaProperties.remoteQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
2326
- }));
2735
+ it('should set mediaProperty with the proper level', () =>
2736
+ meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2737
+ assert.equal(meeting.mediaProperties.remoteQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
2738
+ }));
2327
2739
 
2328
- it('should call updateMedia', () => meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2329
- assert.calledOnce(meeting.updateMedia);
2330
- }));
2740
+ it('should call updateMedia', () =>
2741
+ meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
2742
+ assert.calledOnce(meeting.updateMedia);
2743
+ }));
2331
2744
 
2332
2745
  it('should error if set to a invalid level', () => {
2333
2746
  assert.isRejected(meeting.setRemoteQualityLevel('invalid'));
@@ -2341,8 +2754,12 @@ describe('plugin-meetings', () => {
2341
2754
 
2342
2755
  describe('#usePhoneAudio', () => {
2343
2756
  beforeEach(() => {
2344
- meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.resolve({body: {locus: 'testData'}}));
2345
- meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.resolve({body: {locus: 'testData'}}));
2757
+ meeting.meetingRequest.dialIn = sinon
2758
+ .stub()
2759
+ .returns(Promise.resolve({body: {locus: 'testData'}}));
2760
+ meeting.meetingRequest.dialOut = sinon
2761
+ .stub()
2762
+ .returns(Promise.resolve({body: {locus: 'testData'}}));
2346
2763
  meeting.locusInfo.onFullLocus = sinon.stub().returns(Promise.resolve());
2347
2764
  });
2348
2765
 
@@ -2354,7 +2771,7 @@ describe('plugin-meetings', () => {
2354
2771
  correlationId: meeting.correlationId,
2355
2772
  dialInUrl: DIAL_IN_URL,
2356
2773
  locusUrl: meeting.locusUrl,
2357
- clientUrl: meeting.deviceUrl
2774
+ clientUrl: meeting.deviceUrl,
2358
2775
  });
2359
2776
  assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
2360
2777
  assert.notCalled(meeting.meetingRequest.dialOut);
@@ -2369,7 +2786,7 @@ describe('plugin-meetings', () => {
2369
2786
  correlationId: meeting.correlationId,
2370
2787
  dialInUrl: DIAL_IN_URL,
2371
2788
  locusUrl: meeting.locusUrl,
2372
- clientUrl: meeting.deviceUrl
2789
+ clientUrl: meeting.deviceUrl,
2373
2790
  });
2374
2791
  assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
2375
2792
  assert.notCalled(meeting.meetingRequest.dialOut);
@@ -2386,7 +2803,7 @@ describe('plugin-meetings', () => {
2386
2803
  dialOutUrl: DIAL_OUT_URL,
2387
2804
  locusUrl: meeting.locusUrl,
2388
2805
  clientUrl: meeting.deviceUrl,
2389
- phoneNumber
2806
+ phoneNumber,
2390
2807
  });
2391
2808
  assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
2392
2809
  assert.notCalled(meeting.meetingRequest.dialIn);
@@ -2402,7 +2819,7 @@ describe('plugin-meetings', () => {
2402
2819
  dialOutUrl: DIAL_OUT_URL,
2403
2820
  locusUrl: meeting.locusUrl,
2404
2821
  clientUrl: meeting.deviceUrl,
2405
- phoneNumber
2822
+ phoneNumber,
2406
2823
  });
2407
2824
  assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
2408
2825
  assert.notCalled(meeting.meetingRequest.dialIn);
@@ -2413,11 +2830,14 @@ describe('plugin-meetings', () => {
2413
2830
 
2414
2831
  meeting.meetingRequest.dialIn = sinon.stub().returns(Promise.reject(error));
2415
2832
 
2416
- return meeting.usePhoneAudio().then(() => Promise.reject(new Error('Promise resolved when it should have rejected'))).catch((e) => {
2417
- assert.equal(e, error);
2833
+ return meeting
2834
+ .usePhoneAudio()
2835
+ .then(() => Promise.reject(new Error('Promise resolved when it should have rejected')))
2836
+ .catch((e) => {
2837
+ assert.equal(e, error);
2418
2838
 
2419
- return Promise.resolve();
2420
- });
2839
+ return Promise.resolve();
2840
+ });
2421
2841
  });
2422
2842
 
2423
2843
  it('rejects if the request failed (dial out)', async () => {
@@ -2425,11 +2845,14 @@ describe('plugin-meetings', () => {
2425
2845
 
2426
2846
  meeting.meetingRequest.dialOut = sinon.stub().returns(Promise.reject(error));
2427
2847
 
2428
- return meeting.usePhoneAudio('+441234567890').then(() => Promise.reject(new Error('Promise resolved when it should have rejected'))).catch((e) => {
2429
- assert.equal(e, error);
2848
+ return meeting
2849
+ .usePhoneAudio('+441234567890')
2850
+ .then(() => Promise.reject(new Error('Promise resolved when it should have rejected')))
2851
+ .catch((e) => {
2852
+ assert.equal(e, error);
2430
2853
 
2431
- return Promise.resolve();
2432
- });
2854
+ return Promise.resolve();
2855
+ });
2433
2856
  });
2434
2857
  });
2435
2858
 
@@ -2448,34 +2871,42 @@ describe('plugin-meetings', () => {
2448
2871
  locusUrl: 'some_locus_url',
2449
2872
  sipUrl: 'some_sip_url', // or sipMeetingUri
2450
2873
  meetingNumber: '123456', // this.config.experimental.enableUnifiedMeetings
2451
- hostId: 'some_host_id' // this.owner;
2874
+ hostId: 'some_host_id', // this.owner;
2452
2875
  };
2453
2876
  const FAKE_SDK_CAPTCHA_INFO = {
2454
2877
  captchaId: FAKE_CAPTCHA_ID,
2455
2878
  verificationImageURL: FAKE_CAPTCHA_IMAGE_URL,
2456
2879
  verificationAudioURL: FAKE_CAPTCHA_AUDIO_URL,
2457
- refreshURL: FAKE_CAPTCHA_REFRESH_URL
2880
+ refreshURL: FAKE_CAPTCHA_REFRESH_URL,
2458
2881
  };
2459
2882
  const FAKE_WBXAPPAPI_CAPTCHA_INFO = {
2460
2883
  captchaID: `${FAKE_CAPTCHA_ID}-2`,
2461
2884
  verificationImageURL: `${FAKE_CAPTCHA_IMAGE_URL}-2`,
2462
2885
  verificationAudioURL: `${FAKE_CAPTCHA_AUDIO_URL}-2`,
2463
- refreshURL: `${FAKE_CAPTCHA_REFRESH_URL}-2`
2886
+ refreshURL: `${FAKE_CAPTCHA_REFRESH_URL}-2`,
2464
2887
  };
2465
2888
 
2466
-
2467
2889
  it('calls meetingInfoProvider with all the right parameters and parses the result', async () => {
2468
- meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2890
+ meeting.attrs.meetingInfoProvider = {
2891
+ fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
2892
+ };
2469
2893
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2470
2894
  meeting.destination = FAKE_DESTINATION;
2471
2895
  meeting.destinationType = FAKE_TYPE;
2472
2896
  meeting.parseMeetingInfo = sinon.stub().returns(undefined);
2473
2897
 
2474
2898
  await meeting.fetchMeetingInfo({
2475
- password: FAKE_PASSWORD, captchaCode: FAKE_CAPTCHA_CODE
2899
+ password: FAKE_PASSWORD,
2900
+ captchaCode: FAKE_CAPTCHA_CODE,
2476
2901
  });
2477
2902
 
2478
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, FAKE_PASSWORD, {code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID});
2903
+ assert.calledWith(
2904
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
2905
+ FAKE_DESTINATION,
2906
+ FAKE_TYPE,
2907
+ FAKE_PASSWORD,
2908
+ {code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID}
2909
+ );
2479
2910
 
2480
2911
  assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
2481
2912
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
@@ -2483,11 +2914,18 @@ describe('plugin-meetings', () => {
2483
2914
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
2484
2915
  assert.equal(meeting.requiredCaptcha, null);
2485
2916
  assert.calledTwice(TriggerProxy.trigger);
2486
- assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
2917
+ assert.calledWith(
2918
+ TriggerProxy.trigger,
2919
+ meeting,
2920
+ {file: 'meetings', function: 'fetchMeetingInfo'},
2921
+ 'meeting:meetingInfoAvailable'
2922
+ );
2487
2923
  });
2488
2924
 
2489
2925
  it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
2490
- meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2926
+ meeting.attrs.meetingInfoProvider = {
2927
+ fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
2928
+ };
2491
2929
  meeting.destination = FAKE_DESTINATION;
2492
2930
  meeting.destinationType = FAKE_TYPE;
2493
2931
  meeting.parseMeetingInfo = sinon.stub().returns(undefined);
@@ -2504,7 +2942,13 @@ describe('plugin-meetings', () => {
2504
2942
  assert.isUndefined(meeting.fetchMeetingInfoTimeoutId);
2505
2943
 
2506
2944
  // meeting info provider
2507
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
2945
+ assert.calledWith(
2946
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
2947
+ FAKE_DESTINATION,
2948
+ FAKE_TYPE,
2949
+ null,
2950
+ null
2951
+ );
2508
2952
 
2509
2953
  // parseMeeting info
2510
2954
  assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
@@ -2515,31 +2959,48 @@ describe('plugin-meetings', () => {
2515
2959
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
2516
2960
 
2517
2961
  assert.calledTwice(TriggerProxy.trigger);
2518
- assert.calledWith(TriggerProxy.trigger, meeting, {file: 'meetings', function: 'fetchMeetingInfo'}, 'meeting:meetingInfoAvailable');
2962
+ assert.calledWith(
2963
+ TriggerProxy.trigger,
2964
+ meeting,
2965
+ {file: 'meetings', function: 'fetchMeetingInfo'},
2966
+ 'meeting:meetingInfoAvailable'
2967
+ );
2519
2968
  });
2520
2969
 
2521
2970
  it('fails if captchaCode is provided when captcha not needed', async () => {
2522
- meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2971
+ meeting.attrs.meetingInfoProvider = {
2972
+ fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
2973
+ };
2523
2974
  meeting.requiredCaptcha = null;
2524
2975
  meeting.destination = FAKE_DESTINATION;
2525
2976
  meeting.destinationType = FAKE_TYPE;
2526
2977
 
2527
- await assert.isRejected(meeting.fetchMeetingInfo({
2528
- captchaCode: FAKE_CAPTCHA_CODE
2529
- }), Error, 'fetchMeetingInfo() called with captchaCode when captcha was not required');
2978
+ await assert.isRejected(
2979
+ meeting.fetchMeetingInfo({
2980
+ captchaCode: FAKE_CAPTCHA_CODE,
2981
+ }),
2982
+ Error,
2983
+ 'fetchMeetingInfo() called with captchaCode when captcha was not required'
2984
+ );
2530
2985
 
2531
2986
  assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
2532
2987
  });
2533
2988
 
2534
2989
  it('fails if password is provided when not required', async () => {
2535
- meeting.attrs.meetingInfoProvider = {fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO})};
2990
+ meeting.attrs.meetingInfoProvider = {
2991
+ fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
2992
+ };
2536
2993
  meeting.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
2537
2994
  meeting.destination = FAKE_DESTINATION;
2538
2995
  meeting.destinationType = FAKE_TYPE;
2539
2996
 
2540
- await assert.isRejected(meeting.fetchMeetingInfo({
2541
- password: FAKE_PASSWORD
2542
- }), Error, 'fetchMeetingInfo() called with password when password was not required');
2997
+ await assert.isRejected(
2998
+ meeting.fetchMeetingInfo({
2999
+ password: FAKE_PASSWORD,
3000
+ }),
3001
+ Error,
3002
+ 'fetchMeetingInfo() called with password when password was not required'
3003
+ );
2543
3004
 
2544
3005
  assert.notCalled(meeting.attrs.meetingInfoProvider.fetchMeetingInfo);
2545
3006
  });
@@ -2548,15 +3009,26 @@ describe('plugin-meetings', () => {
2548
3009
  meeting.destination = FAKE_DESTINATION;
2549
3010
  meeting.destinationType = FAKE_TYPE;
2550
3011
  meeting.attrs.meetingInfoProvider = {
2551
- fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO))
3012
+ fetchMeetingInfo: sinon
3013
+ .stub()
3014
+ .throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO)),
2552
3015
  };
2553
3016
 
2554
3017
  await assert.isRejected(meeting.fetchMeetingInfo({}), PasswordError);
2555
3018
 
2556
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, null, null);
3019
+ assert.calledWith(
3020
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3021
+ FAKE_DESTINATION,
3022
+ FAKE_TYPE,
3023
+ null,
3024
+ null
3025
+ );
2557
3026
 
2558
3027
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2559
- assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
3028
+ assert.equal(
3029
+ meeting.meetingInfoFailureReason,
3030
+ MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
3031
+ );
2560
3032
  assert.equal(meeting.requiredCaptcha, null);
2561
3033
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2562
3034
  });
@@ -2565,25 +3037,38 @@ describe('plugin-meetings', () => {
2565
3037
  meeting.destination = FAKE_DESTINATION;
2566
3038
  meeting.destinationType = FAKE_TYPE;
2567
3039
  meeting.attrs.meetingInfoProvider = {
2568
- fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
3040
+ fetchMeetingInfo: sinon
3041
+ .stub()
3042
+ .throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO)),
2569
3043
  };
2570
3044
  meeting.requiredCaptcha = null;
2571
3045
 
2572
- await assert.isRejected(meeting.fetchMeetingInfo({
2573
- password: 'aaa'
2574
- }), CaptchaError);
2575
-
2576
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
3046
+ await assert.isRejected(
3047
+ meeting.fetchMeetingInfo({
3048
+ password: 'aaa',
3049
+ }),
3050
+ CaptchaError
3051
+ );
2577
3052
 
3053
+ assert.calledWith(
3054
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3055
+ FAKE_DESTINATION,
3056
+ FAKE_TYPE,
3057
+ 'aaa',
3058
+ null
3059
+ );
2578
3060
 
2579
3061
  assert.deepEqual(meeting.meetingInfo, {});
2580
- assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
3062
+ assert.equal(
3063
+ meeting.meetingInfoFailureReason,
3064
+ MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
3065
+ );
2581
3066
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2582
3067
  assert.deepEqual(meeting.requiredCaptcha, {
2583
3068
  captchaId: FAKE_CAPTCHA_ID,
2584
3069
  verificationImageURL: FAKE_CAPTCHA_IMAGE_URL,
2585
3070
  verificationAudioURL: FAKE_CAPTCHA_AUDIO_URL,
2586
- refreshURL: FAKE_CAPTCHA_REFRESH_URL
3071
+ refreshURL: FAKE_CAPTCHA_REFRESH_URL,
2587
3072
  });
2588
3073
  });
2589
3074
 
@@ -2591,15 +3076,27 @@ describe('plugin-meetings', () => {
2591
3076
  meeting.destination = FAKE_DESTINATION;
2592
3077
  meeting.destinationType = FAKE_TYPE;
2593
3078
  meeting.attrs.meetingInfoProvider = {
2594
- fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
3079
+ fetchMeetingInfo: sinon
3080
+ .stub()
3081
+ .throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO)),
2595
3082
  };
2596
3083
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2597
3084
 
2598
- await assert.isRejected(meeting.fetchMeetingInfo({
2599
- password: 'aaa', captchaCode: 'bbb'
2600
- }), CaptchaError);
3085
+ await assert.isRejected(
3086
+ meeting.fetchMeetingInfo({
3087
+ password: 'aaa',
3088
+ captchaCode: 'bbb',
3089
+ }),
3090
+ CaptchaError
3091
+ );
2601
3092
 
2602
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', {code: 'bbb', id: FAKE_CAPTCHA_ID});
3093
+ assert.calledWith(
3094
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3095
+ FAKE_DESTINATION,
3096
+ FAKE_TYPE,
3097
+ 'aaa',
3098
+ {code: 'bbb', id: FAKE_CAPTCHA_ID}
3099
+ );
2603
3100
 
2604
3101
  assert.deepEqual(meeting.meetingInfo, {});
2605
3102
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
@@ -2611,20 +3108,24 @@ describe('plugin-meetings', () => {
2611
3108
  meeting.destination = FAKE_DESTINATION;
2612
3109
  meeting.destinationType = FAKE_TYPE;
2613
3110
  meeting.attrs.meetingInfoProvider = {
2614
- fetchMeetingInfo: sinon.stub().resolves(
2615
- {
2616
- statusCode: 200,
2617
- body: FAKE_MEETING_INFO
2618
- }
2619
- )
3111
+ fetchMeetingInfo: sinon.stub().resolves({
3112
+ statusCode: 200,
3113
+ body: FAKE_MEETING_INFO,
3114
+ }),
2620
3115
  };
2621
3116
  meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
2622
3117
 
2623
3118
  await meeting.fetchMeetingInfo({
2624
- password: 'aaa'
3119
+ password: 'aaa',
2625
3120
  });
2626
3121
 
2627
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', null);
3122
+ assert.calledWith(
3123
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3124
+ FAKE_DESTINATION,
3125
+ FAKE_TYPE,
3126
+ 'aaa',
3127
+ null
3128
+ );
2628
3129
 
2629
3130
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2630
3131
  assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
@@ -2638,36 +3139,50 @@ describe('plugin-meetings', () => {
2638
3139
  const refreshedCaptcha = {
2639
3140
  captchaID: FAKE_WBXAPPAPI_CAPTCHA_INFO.captchaID,
2640
3141
  verificationImageURL: FAKE_WBXAPPAPI_CAPTCHA_INFO.verificationImageURL,
2641
- verificationAudioURL: FAKE_WBXAPPAPI_CAPTCHA_INFO.verificationAudioURL
3142
+ verificationAudioURL: FAKE_WBXAPPAPI_CAPTCHA_INFO.verificationAudioURL,
2642
3143
  };
2643
3144
 
2644
3145
  meeting.attrs.meetingInfoProvider = {
2645
- fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO))
3146
+ fetchMeetingInfo: sinon
3147
+ .stub()
3148
+ .throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO)),
2646
3149
  };
2647
- meeting.meetingRequest.refreshCaptcha = sinon.stub().returns(Promise.resolve(
2648
- {
2649
- body: refreshedCaptcha
2650
- }
2651
- ));
3150
+ meeting.meetingRequest.refreshCaptcha = sinon.stub().returns(
3151
+ Promise.resolve({
3152
+ body: refreshedCaptcha,
3153
+ })
3154
+ );
2652
3155
  meeting.passwordStatus = PASSWORD_STATUS.REQUIRED;
2653
3156
  meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
2654
3157
  meeting.destination = FAKE_DESTINATION;
2655
3158
  meeting.destinationType = FAKE_TYPE;
2656
3159
 
2657
- await assert.isRejected(meeting.fetchMeetingInfo({
2658
- password: 'aaa', captchaCode: 'bbb'
2659
- }));
3160
+ await assert.isRejected(
3161
+ meeting.fetchMeetingInfo({
3162
+ password: 'aaa',
3163
+ captchaCode: 'bbb',
3164
+ })
3165
+ );
2660
3166
 
2661
- assert.calledWith(meeting.attrs.meetingInfoProvider.fetchMeetingInfo, FAKE_DESTINATION, FAKE_TYPE, 'aaa', {code: 'bbb', id: FAKE_CAPTCHA_ID});
3167
+ assert.calledWith(
3168
+ meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
3169
+ FAKE_DESTINATION,
3170
+ FAKE_TYPE,
3171
+ 'aaa',
3172
+ {code: 'bbb', id: FAKE_CAPTCHA_ID}
3173
+ );
2662
3174
 
2663
3175
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
2664
- assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD);
3176
+ assert.equal(
3177
+ meeting.meetingInfoFailureReason,
3178
+ MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
3179
+ );
2665
3180
  assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
2666
3181
  assert.deepEqual(meeting.requiredCaptcha, {
2667
3182
  captchaId: refreshedCaptcha.captchaID,
2668
3183
  verificationImageURL: refreshedCaptcha.verificationImageURL,
2669
3184
  verificationAudioURL: refreshedCaptcha.verificationAudioURL,
2670
- refreshURL: FAKE_SDK_CAPTCHA_INFO.refreshURL // refresh url doesn't change
3185
+ refreshURL: FAKE_SDK_CAPTCHA_INFO.refreshURL, // refresh url doesn't change
2671
3186
  });
2672
3187
  });
2673
3188
  });
@@ -2677,48 +3192,56 @@ describe('plugin-meetings', () => {
2677
3192
  assert.isRejected(meeting.refreshCaptcha(), Error);
2678
3193
  });
2679
3194
  it('sends correct request to captcha service refresh url', async () => {
2680
- const REFRESH_URL = 'https://something.webex.com/captchaservice/v1/captchas/refresh?blablabla=something&captchaID=xxx';
2681
- const EXPECTED_REFRESH_URL = 'https://something.webex.com/captchaservice/v1/captchas/refresh?blablabla=something&captchaID=xxx&siteFullName=something.webex.com';
3195
+ const REFRESH_URL =
3196
+ 'https://something.webex.com/captchaservice/v1/captchas/refresh?blablabla=something&captchaID=xxx';
3197
+ const EXPECTED_REFRESH_URL =
3198
+ 'https://something.webex.com/captchaservice/v1/captchas/refresh?blablabla=something&captchaID=xxx&siteFullName=something.webex.com';
2682
3199
 
2683
3200
  const FAKE_SDK_CAPTCHA_INFO = {
2684
3201
  captchaId: 'some id',
2685
3202
  verificationImageURL: 'some image url',
2686
3203
  verificationAudioURL: 'some audio url',
2687
- refreshURL: REFRESH_URL
3204
+ refreshURL: REFRESH_URL,
2688
3205
  };
2689
3206
 
2690
3207
  const FAKE_REFRESHED_CAPTCHA = {
2691
3208
  captchaID: 'some id',
2692
3209
  verificationImageURL: 'some image url',
2693
- verificationAudioURL: 'some audio url'
3210
+ verificationAudioURL: 'some audio url',
2694
3211
  };
2695
3212
 
2696
3213
  // setup the meeting so that a captcha is required
2697
3214
  meeting.attrs.meetingInfoProvider = {
2698
- fetchMeetingInfo: sinon.stub().throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO))
3215
+ fetchMeetingInfo: sinon
3216
+ .stub()
3217
+ .throws(new MeetingInfoV2CaptchaError(423005, FAKE_SDK_CAPTCHA_INFO)),
2699
3218
  };
2700
3219
 
2701
- await assert.isRejected(meeting.fetchMeetingInfo({
2702
- password: ''
2703
- }), CaptchaError);
3220
+ await assert.isRejected(
3221
+ meeting.fetchMeetingInfo({
3222
+ password: '',
3223
+ }),
3224
+ CaptchaError
3225
+ );
2704
3226
 
2705
3227
  assert.deepEqual(meeting.requiredCaptcha, FAKE_SDK_CAPTCHA_INFO);
2706
- meeting.meetingRequest.refreshCaptcha = sinon.stub().returns(Promise.resolve({body: FAKE_REFRESHED_CAPTCHA}));
3228
+ meeting.meetingRequest.refreshCaptcha = sinon
3229
+ .stub()
3230
+ .returns(Promise.resolve({body: FAKE_REFRESHED_CAPTCHA}));
2707
3231
 
2708
3232
  // test the captcha refresh
2709
3233
  await meeting.refreshCaptcha();
2710
3234
 
2711
- assert.calledWith(meeting.meetingRequest.refreshCaptcha,
2712
- {
2713
- captchaRefreshUrl: EXPECTED_REFRESH_URL,
2714
- captchaId: FAKE_SDK_CAPTCHA_INFO.captchaId
2715
- });
3235
+ assert.calledWith(meeting.meetingRequest.refreshCaptcha, {
3236
+ captchaRefreshUrl: EXPECTED_REFRESH_URL,
3237
+ captchaId: FAKE_SDK_CAPTCHA_INFO.captchaId,
3238
+ });
2716
3239
 
2717
3240
  assert.deepEqual(meeting.requiredCaptcha, {
2718
3241
  captchaId: FAKE_REFRESHED_CAPTCHA.captchaID,
2719
3242
  verificationImageURL: FAKE_REFRESHED_CAPTCHA.verificationImageURL,
2720
3243
  verificationAudioURL: FAKE_REFRESHED_CAPTCHA.verificationAudioURL,
2721
- refreshURL: FAKE_SDK_CAPTCHA_INFO.refreshURL // refresh url doesn't change
3244
+ refreshURL: FAKE_SDK_CAPTCHA_INFO.refreshURL, // refresh url doesn't change
2722
3245
  });
2723
3246
  });
2724
3247
  });
@@ -2732,7 +3255,7 @@ describe('plugin-meetings', () => {
2732
3255
  assert(Metrics.sendBehavioralMetric.calledOnce);
2733
3256
  assert.calledWith(
2734
3257
  Metrics.sendBehavioralMetric,
2735
- BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS,
3258
+ BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS
2736
3259
  );
2737
3260
  assert.equal(result.isPasswordValid, true);
2738
3261
  assert.equal(result.requiredCaptcha, null);
@@ -2807,7 +3330,9 @@ describe('plugin-meetings', () => {
2807
3330
  sandbox = sinon.createSandbox();
2808
3331
  meeting.meetingFiniteStateMachine.ring();
2809
3332
  meeting.meetingFiniteStateMachine.join();
2810
- meeting.meetingRequest.endMeetingForAll = sinon.stub().returns(Promise.resolve({body: 'test'}));
3333
+ meeting.meetingRequest.endMeetingForAll = sinon
3334
+ .stub()
3335
+ .returns(Promise.resolve({body: 'test'}));
2811
3336
  meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
2812
3337
  meeting.closeLocalStream = sinon.stub().returns(Promise.resolve());
2813
3338
  meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
@@ -2821,6 +3346,7 @@ describe('plugin-meetings', () => {
2821
3346
  meeting.unsetRemoteStream = sinon.stub().returns(true);
2822
3347
  meeting.unsetPeerConnections = sinon.stub().returns(true);
2823
3348
  meeting.logger.error = sinon.stub().returns(true);
3349
+ meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
2824
3350
 
2825
3351
  // A meeting needs to be joined to end
2826
3352
  meeting.meetingState = 'ACTIVE';
@@ -2859,7 +3385,11 @@ describe('plugin-meetings', () => {
2859
3385
  sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
2860
3386
 
2861
3387
  sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
2862
- sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}})));
3388
+ sandbox
3389
+ .stub(MeetingUtil, 'joinMeeting')
3390
+ .returns(
3391
+ Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}}))
3392
+ );
2863
3393
  });
2864
3394
 
2865
3395
  afterEach(() => {
@@ -2870,8 +3400,7 @@ describe('plugin-meetings', () => {
2870
3400
  it('should throw an error if resourceId not passed', async () => {
2871
3401
  try {
2872
3402
  await meeting.moveTo();
2873
- }
2874
- catch (err) {
3403
+ } catch (err) {
2875
3404
  assert.instanceOf(err, ParameterError);
2876
3405
  assert.equal(err.sdkMessage, 'Cannot move call without a resourceId.');
2877
3406
  }
@@ -2888,17 +3417,17 @@ describe('plugin-meetings', () => {
2888
3417
  share: true,
2889
3418
  share_audio: false,
2890
3419
  video: false,
2891
- whiteboard: false
3420
+ whiteboard: false,
2892
3421
  },
2893
3422
  tx: {
2894
3423
  audio: false,
2895
3424
  share: false,
2896
3425
  share_audio: false,
2897
3426
  video: false,
2898
- whiteboard: false
2899
- }
2900
- }
2901
- }
3427
+ whiteboard: false,
3428
+ },
3429
+ },
3430
+ },
2902
3431
  });
2903
3432
  assert.calledWithMatch(Metrics.postEvent, {event: eventType.MOVE_MEDIA});
2904
3433
  });
@@ -2907,17 +3436,19 @@ describe('plugin-meetings', () => {
2907
3436
  sinon.spy(MeetingUtil, 'joinMeetingOptions');
2908
3437
  await meeting.moveTo('resourceId');
2909
3438
 
2910
- assert.calledWith(MeetingUtil.joinMeetingOptions, meeting, {resourceId: 'resourceId', moveToResource: true});
3439
+ assert.calledWith(MeetingUtil.joinMeetingOptions, meeting, {
3440
+ resourceId: 'resourceId',
3441
+ moveToResource: true,
3442
+ });
2911
3443
  });
2912
3444
 
2913
3445
  it('should reconnectMedia after DX joins after moveTo', async () => {
2914
3446
  await meeting.moveTo('resourceId');
2915
3447
 
2916
-
2917
3448
  await meeting.locusInfo.emitScoped(
2918
3449
  {
2919
3450
  file: 'locus-info',
2920
- function: 'updateSelf'
3451
+ function: 'updateSelf',
2921
3452
  },
2922
3453
  'SELF_OBSERVING'
2923
3454
  );
@@ -2933,36 +3464,30 @@ describe('plugin-meetings', () => {
2933
3464
  assert.called(meeting.mediaProperties.setMediaDirection);
2934
3465
  assert.called(meeting.mediaProperties.unsetMediaTracks);
2935
3466
 
2936
- assert.calledWith(meeting.reconnectionManager.reconnectMedia,
2937
- {
2938
- mediaDirection: {
2939
- sendVideo: false,
2940
- receiveVideo: false,
2941
- sendAudio: false,
2942
- receiveAudio: false,
2943
- sendShare: false,
2944
- receiveShare: true
2945
- }
2946
- });
3467
+ assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
3468
+ mediaDirection: {
3469
+ sendVideo: false,
3470
+ receiveVideo: false,
3471
+ sendAudio: false,
3472
+ receiveAudio: false,
3473
+ sendShare: false,
3474
+ receiveShare: true,
3475
+ },
3476
+ });
2947
3477
  });
2948
3478
 
2949
3479
  it('should throw an error if moveTo call fails', async () => {
2950
3480
  MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
2951
3481
  try {
2952
3482
  await meeting.moveTo('resourceId');
2953
- }
2954
- catch {
3483
+ } catch {
2955
3484
  assert.calledOnce(Metrics.sendBehavioralMetric);
2956
- assert.calledWith(
2957
- Metrics.sendBehavioralMetric,
2958
- BEHAVIORAL_METRICS.MOVE_TO_FAILURE,
2959
- {
2960
- correlation_id: meeting.correlationId,
2961
- locus_id: meeting.locusUrl.split('/').pop(),
2962
- reason: sinon.match.any,
2963
- stack: sinon.match.any
2964
- }
2965
- );
3485
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_TO_FAILURE, {
3486
+ correlation_id: meeting.correlationId,
3487
+ locus_id: meeting.locusUrl.split('/').pop(),
3488
+ reason: sinon.match.any,
3489
+ stack: sinon.match.any,
3490
+ });
2966
3491
  }
2967
3492
  Metrics.sendBehavioralMetric.reset();
2968
3493
  meeting.reconnectionManager.reconnectMedia = sinon.stub().returns(Promise.reject());
@@ -2972,23 +3497,18 @@ describe('plugin-meetings', () => {
2972
3497
  await meeting.locusInfo.emitScoped(
2973
3498
  {
2974
3499
  file: 'locus-info',
2975
- function: 'updateSelf'
3500
+ function: 'updateSelf',
2976
3501
  },
2977
3502
  'SELF_OBSERVING'
2978
3503
  );
2979
- }
2980
- catch {
3504
+ } catch {
2981
3505
  assert.calledOnce(Metrics.sendBehavioralMetric);
2982
- assert.calledWith(
2983
- Metrics.sendBehavioralMetric,
2984
- BEHAVIORAL_METRICS.MOVE_TO_FAILURE,
2985
- {
2986
- correlation_id: meeting.correlationId,
2987
- locus_id: meeting.locusUrl.split('/').pop(),
2988
- reason: sinon.match.any,
2989
- stack: sinon.match.any
2990
- }
2991
- );
3506
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_TO_FAILURE, {
3507
+ correlation_id: meeting.correlationId,
3508
+ locus_id: meeting.locusUrl.split('/').pop(),
3509
+ reason: sinon.match.any,
3510
+ stack: sinon.match.any,
3511
+ });
2992
3512
  }
2993
3513
  });
2994
3514
  });
@@ -2998,7 +3518,11 @@ describe('plugin-meetings', () => {
2998
3518
 
2999
3519
  beforeEach(() => {
3000
3520
  sandbox = sinon.createSandbox();
3001
- sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}})));
3521
+ sandbox
3522
+ .stub(MeetingUtil, 'joinMeeting')
3523
+ .returns(
3524
+ Promise.resolve(MeetingUtil.parseLocusJoin({body: {locus, mediaConnections: []}}))
3525
+ );
3002
3526
  sandbox.stub(MeetingUtil, 'leaveMeeting').returns(Promise.resolve());
3003
3527
  });
3004
3528
 
@@ -3010,8 +3534,7 @@ describe('plugin-meetings', () => {
3010
3534
  it('should throw an error if resourceId not passed', async () => {
3011
3535
  try {
3012
3536
  await meeting.moveFrom();
3013
- }
3014
- catch (err) {
3537
+ } catch (err) {
3015
3538
  assert.instanceOf(err, ParameterError);
3016
3539
 
3017
3540
  assert.equal(err.sdkMessage, 'Cannot move call without a resourceId.');
@@ -3032,35 +3555,193 @@ describe('plugin-meetings', () => {
3032
3555
  assert.calledWith(MeetingUtil.leaveMeeting, meeting, {
3033
3556
  resourceId: 'resourceId',
3034
3557
  correlationId: meeting.correlationId,
3035
- moveMeeting: true
3558
+ moveMeeting: true,
3036
3559
  });
3037
3560
 
3038
3561
  assert.calledOnce(Metrics.sendBehavioralMetric);
3039
- assert.calledWith(
3040
- Metrics.sendBehavioralMetric,
3041
- BEHAVIORAL_METRICS.MOVE_FROM_SUCCESS,
3042
- );
3562
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_FROM_SUCCESS);
3043
3563
  });
3044
3564
 
3045
3565
  it('should throw an error if moveFrom call fails', async () => {
3046
3566
  MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
3047
3567
  try {
3048
3568
  await meeting.moveFrom('resourceId');
3049
- }
3050
- catch {
3569
+ } catch {
3051
3570
  assert.calledOnce(Metrics.sendBehavioralMetric);
3052
- assert.calledWith(
3053
- Metrics.sendBehavioralMetric,
3054
- BEHAVIORAL_METRICS.MOVE_FROM_FAILURE,
3055
- {
3056
- correlation_id: meeting.correlationId,
3057
- locus_id: meeting.locusUrl.split('/').pop(),
3058
- reason: sinon.match.any,
3059
- stack: sinon.match.any
3060
- }
3061
- );
3571
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_FROM_FAILURE, {
3572
+ correlation_id: meeting.correlationId,
3573
+ locus_id: meeting.locusUrl.split('/').pop(),
3574
+ reason: sinon.match.any,
3575
+ stack: sinon.match.any,
3576
+ });
3577
+ }
3578
+ });
3579
+ });
3580
+ describe('Local tracks publishing', () => {
3581
+ let audioTrack;
3582
+ let videoTrack;
3583
+ let videoShareTrack;
3584
+ let createMuteStateStub;
3585
+
3586
+ beforeEach(() => {
3587
+ audioTrack = {
3588
+ id: 'audio track',
3589
+ getSettings: sinon.stub().returns({}),
3590
+ };
3591
+ videoTrack = {
3592
+ id: 'video track',
3593
+ getSettings: sinon.stub().returns({}),
3594
+ };
3595
+ videoShareTrack = {
3596
+ id: 'share track',
3597
+ addEventListener: sinon.stub(),
3598
+ removeEventListener: sinon.stub(),
3599
+ getSettings: sinon.stub().returns({}),
3600
+ };
3601
+ meeting.requestScreenShareFloor = sinon.stub().resolves({});
3602
+ meeting.releaseScreenShareFloor = sinon.stub().resolves({});
3603
+ meeting.mediaProperties.mediaDirection = {sendAudio: false, sendVideo: false, sendShare: false};
3604
+ meeting.mediaProperties.webrtcMediaConnection = {
3605
+ publishTrack: sinon.stub().resolves({}),
3606
+ unpublishTrack: sinon.stub().resolves({}),
3607
+ };
3608
+
3609
+ createMuteStateStub = sinon.stub(MuteStateModule, 'createMuteState').returns({id: 'fake mute state instance'});
3610
+ })
3611
+ describe('#publishTracks', () => {
3612
+ it('fails if there is no media connection', async () => {
3613
+ meeting.mediaProperties.webrtcMediaConnection = undefined;
3614
+ await assert.isRejected(meeting.publishTracks({audio: {id: 'some audio track'}}));
3615
+ });
3616
+
3617
+ const checkAudioPublished = () => {
3618
+ assert.calledWith(createMuteStateStub, 'audio', meeting, meeting.mediaProperties.mediaDirection);
3619
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, audioTrack, 'main');
3620
+ assert.equal(meeting.mediaProperties.audioTrack, audioTrack);
3621
+ assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, true);
3062
3622
  }
3623
+
3624
+ const checkVideoPublished = () => {
3625
+ assert.calledWith(createMuteStateStub, 'video', meeting, meeting.mediaProperties.mediaDirection);
3626
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, videoTrack, 'main');
3627
+ assert.equal(meeting.mediaProperties.videoTrack, videoTrack);
3628
+ assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, true);
3629
+ }
3630
+
3631
+ const checkScreenShareVideoPublished = () => {
3632
+ assert.calledOnce(meeting.requestScreenShareFloor);
3633
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, videoShareTrack, 'slides');
3634
+ assert.equal(meeting.mediaProperties.shareTrack, videoShareTrack);
3635
+ assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
3636
+ }
3637
+
3638
+ it('requests screen share floor and publishes the screen share video track', async () => {
3639
+ await meeting.publishTracks({screenShare: {video: videoShareTrack}});
3640
+
3641
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
3642
+ checkScreenShareVideoPublished();
3643
+ });
3644
+
3645
+ it('creates MuteState instance and publishes the track for main audio', async () => {
3646
+ await meeting.publishTracks({microphone: audioTrack});
3647
+
3648
+ assert.calledOnce(createMuteStateStub);
3649
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
3650
+ checkAudioPublished();
3651
+ });
3652
+
3653
+ it('creates MuteState instance and publishes the track for main video', async () => {
3654
+ await meeting.publishTracks({camera: videoTrack});
3655
+
3656
+ assert.calledOnce(createMuteStateStub);
3657
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
3658
+ checkVideoPublished();
3659
+ });
3660
+
3661
+ it('publishes audio, video and screen share together', async () => {
3662
+ await meeting.publishTracks({
3663
+ microphone: audioTrack,
3664
+ camera: videoTrack,
3665
+ screenShare: {
3666
+ video: videoShareTrack,
3667
+ }
3668
+ });
3669
+
3670
+ assert.calledTwice(createMuteStateStub);
3671
+ assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
3672
+ checkAudioPublished();
3673
+ checkVideoPublished();
3674
+ checkScreenShareVideoPublished();
3675
+ })
3063
3676
  });
3677
+
3678
+ describe('unpublishTracks', () => {
3679
+ beforeEach(async () => {
3680
+ await meeting.publishTracks({
3681
+ microphone: audioTrack,
3682
+ camera: videoTrack,
3683
+ screenShare: {video: videoShareTrack}
3684
+ });
3685
+ });
3686
+
3687
+ const checkAudioUnpublished = () => {
3688
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack, audioTrack, 'main');
3689
+
3690
+ assert.equal(meeting.mediaProperties.audioTrack, null);
3691
+ assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, false);
3692
+ };
3693
+
3694
+ const checkVideoUnpublished = () => {
3695
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack, videoTrack, 'main');
3696
+
3697
+ assert.equal(meeting.mediaProperties.videoTrack, null);
3698
+ assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, false);
3699
+ }
3700
+
3701
+ const checkScreenShareVideoUnpublished = () => {
3702
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack, videoShareTrack, 'slides');
3703
+
3704
+ assert.calledOnce(meeting.requestScreenShareFloor);
3705
+
3706
+ assert.equal(meeting.mediaProperties.shareTrack, null);
3707
+ assert.equal(meeting.mediaProperties.mediaDirection.sendShare, false);
3708
+ }
3709
+
3710
+ it('fails if there is no media connection', async () => {
3711
+ meeting.mediaProperties.webrtcMediaConnection = undefined;
3712
+ await assert.isRejected(meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack]));
3713
+ });
3714
+
3715
+ it('un-publishes the tracks correctly (all 3 together)', async () => {
3716
+ await meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack]);
3717
+
3718
+ assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
3719
+ checkAudioUnpublished();
3720
+ checkVideoUnpublished();
3721
+ checkScreenShareVideoUnpublished();
3722
+ });
3723
+
3724
+ it('un-publishes the audio track correctly', async () => {
3725
+ await meeting.unpublishTracks([audioTrack]);
3726
+
3727
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
3728
+ checkAudioUnpublished();
3729
+ });
3730
+
3731
+ it('un-publishes the video track correctly', async () => {
3732
+ await meeting.unpublishTracks([videoTrack]);
3733
+
3734
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
3735
+ checkVideoUnpublished();
3736
+ })
3737
+
3738
+ it('un-publishes the screen share video track correctly', async () => {
3739
+ await meeting.unpublishTracks([videoShareTrack]);
3740
+
3741
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
3742
+ checkScreenShareVideoUnpublished();
3743
+ })
3744
+ })
3064
3745
  });
3065
3746
  });
3066
3747
 
@@ -3127,7 +3808,9 @@ describe('plugin-meetings', () => {
3127
3808
  meeting.config.reconnection.enabled = true;
3128
3809
  meeting.currentMediaStatus = {audio: true};
3129
3810
  meeting.reconnectionManager = new ReconnectionManager(meeting);
3130
- meeting.reconnectionManager.reconnect = sinon.stub().returns(Promise.reject(new Error()));
3811
+ meeting.reconnectionManager.reconnect = sinon
3812
+ .stub()
3813
+ .returns(Promise.reject(new Error()));
3131
3814
  meeting.reconnectionManager.reset = sinon.stub().returns(true);
3132
3815
  });
3133
3816
 
@@ -3152,7 +3835,7 @@ describe('plugin-meetings', () => {
3152
3835
  correlation_id: meeting.correlationId,
3153
3836
  locus_id: meeting.locusUrl.split('/').pop(),
3154
3837
  reason: sinon.match.any,
3155
- stack: sinon.match.any
3838
+ stack: sinon.match.any,
3156
3839
  }
3157
3840
  );
3158
3841
  });
@@ -3164,7 +3847,7 @@ describe('plugin-meetings', () => {
3164
3847
  sinon.match.instanceOf(Meeting),
3165
3848
  {file: 'meeting/index', function: 'reconnect'},
3166
3849
  EVENTS.REQUEST_UPLOAD_LOGS,
3167
- sinon.match.instanceOf(Meeting),
3850
+ sinon.match.instanceOf(Meeting)
3168
3851
  );
3169
3852
  });
3170
3853
 
@@ -3241,6 +3924,7 @@ describe('plugin-meetings', () => {
3241
3924
  });
3242
3925
  describe('#setLocalShareTrack', () => {
3243
3926
  it('should trigger a media:ready event with local share stream', () => {
3927
+ let registeredListener = null;
3244
3928
  const track = {
3245
3929
  getSettings: sinon.stub().returns({
3246
3930
  aspectRatio: '1.7',
@@ -3248,16 +3932,17 @@ describe('plugin-meetings', () => {
3248
3932
  height: 1980,
3249
3933
  width: 1080,
3250
3934
  displaySurface: true,
3251
- cursor: true
3252
- })
3935
+ cursor: true,
3936
+ }),
3937
+ addEventListener: sinon.stub().callsFake((event, listener) => {
3938
+ registeredListener = listener;
3939
+ }),
3253
3940
  };
3254
- const getVideoTracks = sinon.stub().returns([track]);
3255
3941
 
3256
3942
  meeting.mediaProperties.setLocalShareTrack = sinon.stub().returns(true);
3257
- meeting.mediaProperties.shareTrack = {getVideoTracks, getSettings: track.getSettings};
3258
3943
  meeting.stopShare = sinon.stub().resolves(true);
3259
3944
  meeting.mediaProperties.mediaDirection = {};
3260
- meeting.setLocalShareTrack(test1);
3945
+ meeting.setLocalShareTrack(track);
3261
3946
  assert.calledTwice(TriggerProxy.trigger);
3262
3947
  assert.calledWith(
3263
3948
  TriggerProxy.trigger,
@@ -3267,7 +3952,8 @@ describe('plugin-meetings', () => {
3267
3952
  );
3268
3953
  assert.calledOnce(meeting.mediaProperties.setLocalShareTrack);
3269
3954
  assert.equal(meeting.mediaProperties.localStream, undefined);
3270
- meeting.mediaProperties.shareTrack.onended();
3955
+ assert.isNotNull(registeredListener);
3956
+ registeredListener();
3271
3957
  assert.calledOnce(meeting.stopShare);
3272
3958
  });
3273
3959
  });
@@ -3281,33 +3967,51 @@ describe('plugin-meetings', () => {
3281
3967
  // mock the on() method and store all the listeners
3282
3968
  on: sinon.stub().callsFake((event, listener) => {
3283
3969
  eventListeners[event] = listener;
3284
- })
3970
+ }),
3285
3971
  };
3286
3972
  });
3287
3973
 
3288
3974
  it('should register for all the correct RoapMediaConnection events', () => {
3289
3975
  meeting.setupMediaConnectionListeners();
3290
- assert.isFunction(eventListeners[MC.Event.ROAP_STARTED]);
3291
- assert.isFunction(eventListeners[MC.Event.ROAP_DONE]);
3292
- assert.isFunction(eventListeners[MC.Event.ROAP_FAILURE]);
3293
- assert.isFunction(eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]);
3294
- assert.isFunction(eventListeners[MC.Event.REMOTE_TRACK_ADDED]);
3295
- assert.isFunction(eventListeners[MC.Event.CONNECTION_STATE_CHANGED]);
3976
+ assert.isFunction(eventListeners[Event.ROAP_STARTED]);
3977
+ assert.isFunction(eventListeners[Event.ROAP_DONE]);
3978
+ assert.isFunction(eventListeners[Event.ROAP_FAILURE]);
3979
+ assert.isFunction(eventListeners[Event.ROAP_MESSAGE_TO_SEND]);
3980
+ assert.isFunction(eventListeners[Event.REMOTE_TRACK_ADDED]);
3981
+ assert.isFunction(eventListeners[Event.CONNECTION_STATE_CHANGED]);
3296
3982
  });
3297
3983
 
3298
3984
  it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
3299
3985
  meeting.setupMediaConnectionListeners();
3300
- eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.AUDIO});
3986
+ eventListeners[Event.REMOTE_TRACK_ADDED]({
3987
+ track: 'track',
3988
+ type: RemoteTrackType.AUDIO,
3989
+ });
3301
3990
  assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
3302
- assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'remoteAudio', stream: true});
3991
+ assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {
3992
+ type: 'remoteAudio',
3993
+ stream: true,
3994
+ });
3303
3995
 
3304
- eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.VIDEO});
3996
+ eventListeners[Event.REMOTE_TRACK_ADDED]({
3997
+ track: 'track',
3998
+ type: RemoteTrackType.VIDEO,
3999
+ });
3305
4000
  assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
3306
- assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {type: 'remoteVideo', stream: true});
4001
+ assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {
4002
+ type: 'remoteVideo',
4003
+ stream: true,
4004
+ });
3307
4005
 
3308
- eventListeners[MC.Event.REMOTE_TRACK_ADDED]({track: 'track', type: MC.RemoteTrackType.SCREENSHARE_VIDEO});
4006
+ eventListeners[Event.REMOTE_TRACK_ADDED]({
4007
+ track: 'track',
4008
+ type: RemoteTrackType.SCREENSHARE_VIDEO,
4009
+ });
3309
4010
  assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
3310
- assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {type: 'remoteShare', stream: true});
4011
+ assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {
4012
+ type: 'remoteShare',
4013
+ stream: true,
4014
+ });
3311
4015
  });
3312
4016
 
3313
4017
  describe('should send correct metrics for ROAP_FAILURE event', () => {
@@ -3321,10 +4025,19 @@ describe('plugin-meetings', () => {
3321
4025
 
3322
4026
  const checkMetricSent = (event) => {
3323
4027
  assert.calledOnce(Metrics.postEvent);
3324
- assert.calledWithMatch(Metrics.postEvent, {event, meetingId: meeting.id, data: {canProceed: false}});
4028
+ assert.calledWithMatch(Metrics.postEvent, {
4029
+ event,
4030
+ meetingId: meeting.id,
4031
+ data: {canProceed: false},
4032
+ });
3325
4033
  };
3326
4034
 
3327
- const checkBehavioralMetricSent = (metricName, expectedCode, expectedReason, expectedMetadataType) => {
4035
+ const checkBehavioralMetricSent = (
4036
+ metricName,
4037
+ expectedCode,
4038
+ expectedReason,
4039
+ expectedMetadataType
4040
+ ) => {
3328
4041
  assert.calledOnce(Metrics.sendBehavioralMetric);
3329
4042
  assert.calledWith(
3330
4043
  Metrics.sendBehavioralMetric,
@@ -3333,65 +4046,101 @@ describe('plugin-meetings', () => {
3333
4046
  code: expectedCode,
3334
4047
  correlation_id: meeting.correlationId,
3335
4048
  reason: expectedReason,
3336
- stack: sinon.match.any
4049
+ stack: sinon.match.any,
3337
4050
  },
3338
4051
  {
3339
- type: expectedMetadataType
4052
+ type: expectedMetadataType,
3340
4053
  }
3341
4054
  );
3342
4055
  };
3343
4056
 
3344
4057
  it('should send metrics for SdpOfferCreationError error', () => {
3345
- const fakeError = new MC.Errors.SdpOfferCreationError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
4058
+ const fakeError = new Errors.SdpOfferCreationError(fakeErrorMessage, {
4059
+ name: fakeErrorName,
4060
+ cause: {name: fakeRootCauseName},
4061
+ });
3346
4062
 
3347
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
4063
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3348
4064
 
3349
4065
  checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3350
- checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpOfferCreationError, fakeErrorMessage, fakeRootCauseName);
4066
+ checkBehavioralMetricSent(
4067
+ BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
4068
+ Errors.ErrorCode.SdpOfferCreationError,
4069
+ fakeErrorMessage,
4070
+ fakeRootCauseName
4071
+ );
3351
4072
  });
3352
4073
 
3353
4074
  it('should send metrics for SdpOfferHandlingError error', () => {
3354
- const fakeError = new MC.Errors.SdpOfferHandlingError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
4075
+ const fakeError = new Errors.SdpOfferHandlingError(fakeErrorMessage, {
4076
+ name: fakeErrorName,
4077
+ cause: {name: fakeRootCauseName},
4078
+ });
3355
4079
 
3356
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
4080
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3357
4081
 
3358
4082
  checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
3359
- checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpOfferHandlingError, fakeErrorMessage, fakeRootCauseName);
4083
+ checkBehavioralMetricSent(
4084
+ BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
4085
+ Errors.ErrorCode.SdpOfferHandlingError,
4086
+ fakeErrorMessage,
4087
+ fakeRootCauseName
4088
+ );
3360
4089
  });
3361
4090
 
3362
4091
  it('should send metrics for SdpAnswerHandlingError error', () => {
3363
- const fakeError = new MC.Errors.SdpAnswerHandlingError(fakeErrorMessage, {name: fakeErrorName, cause: {name: fakeRootCauseName}});
4092
+ const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
4093
+ name: fakeErrorName,
4094
+ cause: {name: fakeRootCauseName},
4095
+ });
3364
4096
 
3365
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
4097
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3366
4098
 
3367
4099
  checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
3368
- checkBehavioralMetricSent(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, MC.Errors.ErrorCode.SdpAnswerHandlingError, fakeErrorMessage, fakeRootCauseName);
4100
+ checkBehavioralMetricSent(
4101
+ BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
4102
+ Errors.ErrorCode.SdpAnswerHandlingError,
4103
+ fakeErrorMessage,
4104
+ fakeRootCauseName
4105
+ );
3369
4106
  });
3370
4107
 
3371
4108
  it('should send metrics for SdpError error', () => {
3372
4109
  // SdpError is usually without a cause
3373
- const fakeError = new MC.Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
4110
+ const fakeError = new Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
3374
4111
 
3375
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
4112
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3376
4113
 
3377
4114
  checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3378
4115
  // expectedMetadataType is the error name in this case
3379
- checkBehavioralMetricSent(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, MC.Errors.ErrorCode.SdpError, fakeErrorMessage, fakeErrorName);
4116
+ checkBehavioralMetricSent(
4117
+ BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
4118
+ Errors.ErrorCode.SdpError,
4119
+ fakeErrorMessage,
4120
+ fakeErrorName
4121
+ );
3380
4122
  });
3381
4123
 
3382
4124
  it('should send metrics for IceGatheringError error', () => {
3383
4125
  // IceGatheringError is usually without a cause
3384
- const fakeError = new MC.Errors.IceGatheringError(fakeErrorMessage, {name: fakeErrorName});
4126
+ const fakeError = new Errors.IceGatheringError(fakeErrorMessage, {
4127
+ name: fakeErrorName,
4128
+ });
3385
4129
 
3386
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
4130
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3387
4131
 
3388
4132
  checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3389
4133
  // expectedMetadataType is the error name in this case
3390
- checkBehavioralMetricSent(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, MC.Errors.ErrorCode.IceGatheringError, fakeErrorMessage, fakeErrorName);
4134
+ checkBehavioralMetricSent(
4135
+ BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
4136
+ Errors.ErrorCode.IceGatheringError,
4137
+ fakeErrorMessage,
4138
+ fakeErrorName
4139
+ );
3391
4140
  });
3392
4141
  });
3393
4142
 
3394
- describe('handles MC.Event.ROAP_MESSAGE_TO_SEND correctly', () => {
4143
+ describe('handles Event.ROAP_MESSAGE_TO_SEND correctly', () => {
3395
4144
  let sendRoapOKStub;
3396
4145
  let sendRoapMediaRequestStub;
3397
4146
  let sendRoapAnswerStub;
@@ -3399,7 +4148,9 @@ describe('plugin-meetings', () => {
3399
4148
 
3400
4149
  beforeEach(() => {
3401
4150
  sendRoapOKStub = sinon.stub(meeting.roap, 'sendRoapOK').resolves({});
3402
- sendRoapMediaRequestStub = sinon.stub(meeting.roap, 'sendRoapMediaRequest').resolves({});
4151
+ sendRoapMediaRequestStub = sinon
4152
+ .stub(meeting.roap, 'sendRoapMediaRequest')
4153
+ .resolves({});
3403
4154
  sendRoapAnswerStub = sinon.stub(meeting.roap, 'sendRoapAnswer').resolves({});
3404
4155
  sendRoapErrorStub = sinon.stub(meeting.roap, 'sendRoapError').resolves({});
3405
4156
 
@@ -3407,106 +4158,140 @@ describe('plugin-meetings', () => {
3407
4158
  });
3408
4159
 
3409
4160
  it('handles OK message correctly', () => {
3410
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({roapMessage: {messageType: 'OK', seq: 1}});
4161
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
4162
+ roapMessage: {messageType: 'OK', seq: 1},
4163
+ });
3411
4164
 
3412
4165
  assert.calledOnce(Metrics.postEvent);
3413
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.REMOTE_SDP_RECEIVED, meetingId: meeting.id});
4166
+ assert.calledWithMatch(Metrics.postEvent, {
4167
+ event: eventType.REMOTE_SDP_RECEIVED,
4168
+ meetingId: meeting.id,
4169
+ });
3414
4170
 
3415
4171
  assert.calledOnce(sendRoapOKStub);
3416
- assert.calledWith(sendRoapOKStub, {seq: 1, mediaId: meeting.mediaId, correlationId: meeting.correlationId});
4172
+ assert.calledWith(sendRoapOKStub, {
4173
+ seq: 1,
4174
+ mediaId: meeting.mediaId,
4175
+ correlationId: meeting.correlationId,
4176
+ });
3417
4177
  });
3418
4178
 
3419
4179
  it('handles OFFER message correctly', () => {
3420
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4180
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3421
4181
  roapMessage: {
3422
4182
  messageType: 'OFFER',
3423
4183
  seq: 1,
3424
4184
  sdp: 'fake sdp',
3425
4185
  tieBreaker: 12345,
3426
- }
4186
+ },
3427
4187
  });
3428
4188
 
3429
4189
  assert.calledOnce(Metrics.postEvent);
3430
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.LOCAL_SDP_GENERATED, meetingId: meeting.id});
4190
+ assert.calledWithMatch(Metrics.postEvent, {
4191
+ event: eventType.LOCAL_SDP_GENERATED,
4192
+ meetingId: meeting.id,
4193
+ });
3431
4194
 
3432
4195
  assert.calledOnce(sendRoapMediaRequestStub);
3433
4196
  assert.calledWith(sendRoapMediaRequestStub, {
3434
- seq: 1, sdp: 'fake sdp', tieBreaker: 12345, meeting, reconnect: false
4197
+ seq: 1,
4198
+ sdp: 'fake sdp',
4199
+ tieBreaker: 12345,
4200
+ meeting,
4201
+ reconnect: false,
3435
4202
  });
3436
4203
  });
3437
4204
 
3438
4205
  it('handles ANSWER message correctly', () => {
3439
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4206
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3440
4207
  roapMessage: {
3441
4208
  messageType: 'ANSWER',
3442
4209
  seq: 10,
3443
4210
  sdp: 'fake sdp answer',
3444
4211
  tieBreaker: 12345,
3445
- }
4212
+ },
3446
4213
  });
3447
4214
 
3448
4215
  assert.calledOnce(Metrics.postEvent);
3449
- assert.calledWithMatch(Metrics.postEvent, {event: eventType.REMOTE_SDP_RECEIVED, meetingId: meeting.id});
4216
+ assert.calledWithMatch(Metrics.postEvent, {
4217
+ event: eventType.REMOTE_SDP_RECEIVED,
4218
+ meetingId: meeting.id,
4219
+ });
3450
4220
 
3451
4221
  assert.calledOnce(sendRoapAnswerStub);
3452
4222
  assert.calledWith(sendRoapAnswerStub, {
3453
- seq: 10, sdp: 'fake sdp answer', mediaId: meeting.mediaId, correlationId: meeting.correlationId
4223
+ seq: 10,
4224
+ sdp: 'fake sdp answer',
4225
+ mediaId: meeting.mediaId,
4226
+ correlationId: meeting.correlationId,
3454
4227
  });
3455
4228
  });
3456
4229
 
3457
4230
  it('sends metrics if fails to send roap ANSWER message', async () => {
3458
4231
  sendRoapAnswerStub.rejects(new Error('sending answer failed'));
3459
4232
 
3460
- await eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4233
+ await eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3461
4234
  roapMessage: {
3462
4235
  messageType: 'ANSWER',
3463
4236
  seq: 10,
3464
4237
  sdp: 'fake sdp answer',
3465
4238
  tieBreaker: 12345,
3466
- }
4239
+ },
3467
4240
  });
3468
4241
  await testUtils.flushPromises();
3469
4242
 
3470
4243
  assert.calledOnce(Metrics.sendBehavioralMetric);
3471
- assert.calledWithMatch(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE, {
3472
- correlation_id: meeting.correlationId,
3473
- locus_id: meeting.locusUrl.split('/').pop(),
3474
- reason: 'sending answer failed'
3475
- });
4244
+ assert.calledWithMatch(
4245
+ Metrics.sendBehavioralMetric,
4246
+ BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE,
4247
+ {
4248
+ correlation_id: meeting.correlationId,
4249
+ locus_id: meeting.locusUrl.split('/').pop(),
4250
+ reason: 'sending answer failed',
4251
+ }
4252
+ );
3476
4253
  });
3477
4254
 
3478
- [MC.ErrorType.CONFLICT, MC.ErrorType.DOUBLECONFLICT].forEach((errorType) =>
4255
+ [ErrorType.CONFLICT, ErrorType.DOUBLECONFLICT].forEach((errorType) =>
3479
4256
  it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
3480
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4257
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3481
4258
  roapMessage: {
3482
4259
  messageType: 'ERROR',
3483
4260
  seq: 10,
3484
4261
  errorType,
3485
4262
  tieBreaker: 12345,
3486
- }
4263
+ },
3487
4264
  });
3488
4265
 
3489
4266
  assert.calledOnce(Metrics.sendBehavioralMetric);
3490
- assert.calledWithMatch(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
3491
- correlation_id: meeting.correlationId,
3492
- locus_id: meeting.locusUrl.split('/').pop(),
3493
- sequence: 10
3494
- });
4267
+ assert.calledWithMatch(
4268
+ Metrics.sendBehavioralMetric,
4269
+ BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION,
4270
+ {
4271
+ correlation_id: meeting.correlationId,
4272
+ locus_id: meeting.locusUrl.split('/').pop(),
4273
+ sequence: 10,
4274
+ }
4275
+ );
3495
4276
 
3496
4277
  assert.calledOnce(sendRoapErrorStub);
3497
4278
  assert.calledWith(sendRoapErrorStub, {
3498
- seq: 10, errorType, mediaId: meeting.mediaId, correlationId: meeting.correlationId
4279
+ seq: 10,
4280
+ errorType,
4281
+ mediaId: meeting.mediaId,
4282
+ correlationId: meeting.correlationId,
3499
4283
  });
3500
- }));
4284
+ })
4285
+ );
3501
4286
 
3502
4287
  it('handles ERROR message indicating other errors correctly', () => {
3503
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4288
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3504
4289
  roapMessage: {
3505
4290
  messageType: 'ERROR',
3506
4291
  seq: 10,
3507
- errorType: MC.ErrorType.FAILED,
4292
+ errorType: ErrorType.FAILED,
3508
4293
  tieBreaker: 12345,
3509
- }
4294
+ },
3510
4295
  });
3511
4296
 
3512
4297
  assert.notCalled(Metrics.sendBehavioralMetric);
@@ -3514,9 +4299,9 @@ describe('plugin-meetings', () => {
3514
4299
  assert.calledOnce(sendRoapErrorStub);
3515
4300
  assert.calledWith(sendRoapErrorStub, {
3516
4301
  seq: 10,
3517
- errorType: MC.ErrorType.FAILED,
4302
+ errorType: ErrorType.FAILED,
3518
4303
  mediaId: meeting.mediaId,
3519
- correlationId: meeting.correlationId
4304
+ correlationId: meeting.correlationId,
3520
4305
  });
3521
4306
  });
3522
4307
  });
@@ -3550,6 +4335,83 @@ describe('plugin-meetings', () => {
3550
4335
  );
3551
4336
  done();
3552
4337
  });
4338
+
4339
+ it('listens to the breakouts changed event', () => {
4340
+ meeting.breakouts.updateBreakoutSessions = sinon.stub();
4341
+
4342
+ const payload = 'payload';
4343
+
4344
+ meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_MEETING_BREAKOUTS_CHANGED', payload);
4345
+
4346
+ assert.calledOnceWithExactly(meeting.breakouts.updateBreakoutSessions, payload);
4347
+ assert.calledWith(
4348
+ TriggerProxy.trigger,
4349
+ meeting,
4350
+ {file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
4351
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
4352
+ );
4353
+ });
4354
+ });
4355
+
4356
+ describe('#setUpBreakoutsListener', () => {
4357
+ it('listens to the closing event from breakouts and triggers the closing event', () => {
4358
+ TriggerProxy.trigger.reset();
4359
+ meeting.breakouts.trigger('BREAKOUTS_CLOSING');
4360
+
4361
+ assert.calledWith(
4362
+ TriggerProxy.trigger,
4363
+ meeting,
4364
+ {file: 'meeting/index', function: 'setUpBreakoutsListener'},
4365
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
4366
+ );
4367
+ });
4368
+
4369
+ it('listens to the message event from breakouts and triggers the message event', () => {
4370
+ TriggerProxy.trigger.reset();
4371
+
4372
+ const messageEvent = 'message';
4373
+
4374
+ meeting.breakouts.trigger('MESSAGE', messageEvent);
4375
+
4376
+ assert.calledWith(
4377
+ TriggerProxy.trigger,
4378
+ meeting,
4379
+ {file: 'meeting/index', function: 'setUpBreakoutsListener'},
4380
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
4381
+ messageEvent
4382
+ );
4383
+ });
4384
+
4385
+ it('listens to the members update event from breakouts and triggers the breakouts update event', () => {
4386
+ TriggerProxy.trigger.reset();
4387
+ meeting.breakouts.trigger('MEMBERS_UPDATE');
4388
+
4389
+ assert.calledWith(
4390
+ TriggerProxy.trigger,
4391
+ meeting,
4392
+ {file: 'meeting/index', function: 'setUpBreakoutsListener'},
4393
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
4394
+ );
4395
+ });
4396
+ });
4397
+
4398
+ describe('#setupLocusControlsListener', () => {
4399
+ it('listens to the locus breakouts update event', () => {
4400
+ const locus = {
4401
+ breakout: 'breakout'
4402
+ };
4403
+
4404
+ meeting.breakouts.updateBreakout = sinon.stub();
4405
+ meeting.locusInfo.emit({function: 'test', file: 'test'}, 'CONTROLS_MEETING_BREAKOUT_UPDATED', locus);
4406
+
4407
+ assert.calledOnceWithExactly(meeting.breakouts.updateBreakout, locus.breakout);
4408
+ assert.calledWith(
4409
+ TriggerProxy.trigger,
4410
+ meeting,
4411
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
4412
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
4413
+ );
4414
+ });
3553
4415
  });
3554
4416
 
3555
4417
  describe('#setUpLocusUrlListener', () => {
@@ -3557,21 +4419,57 @@ describe('plugin-meetings', () => {
3557
4419
  const newLocusUrl = 'newLocusUrl/12345';
3558
4420
 
3559
4421
  meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
4422
+ meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
4423
+ meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
4424
+
4425
+ meeting.breakouts.locusUrlUpdate = sinon.stub();
3560
4426
 
3561
4427
  meeting.locusInfo.emit({function: 'test', file: 'test'}, 'LOCUS_INFO_UPDATE_URL', newLocusUrl);
3562
4428
  assert.calledWith(
3563
4429
  meeting.members.locusUrlUpdate,
3564
4430
  newLocusUrl
3565
4431
  );
4432
+ assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
4433
+ assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
4434
+ assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
4435
+ assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
3566
4436
  assert.equal(meeting.locusUrl, newLocusUrl);
3567
4437
  assert(meeting.locusId, '12345');
3568
4438
  done();
3569
4439
  });
3570
4440
  });
4441
+
4442
+ describe('#setUpLocusServicesListener', () => {
4443
+ it('listens to the locus services update event', (done) => {
4444
+ const newLocusServices = {
4445
+ services: {
4446
+ record: {
4447
+ url: 'url',
4448
+ }
4449
+ },
4450
+ };
4451
+
4452
+ meeting.recordingController = {setServiceUrl: sinon.stub().returns(undefined), setSessionId: sinon.stub().returns(undefined)};
4453
+
4454
+ meeting.locusInfo.emit(
4455
+ {function: 'test', file: 'test'},
4456
+ 'LINKS_SERVICES',
4457
+ newLocusServices
4458
+ );
4459
+
4460
+ assert.calledWith(meeting.recordingController.setServiceUrl, newLocusServices.services.record.url);
4461
+ assert.calledOnce(meeting.recordingController.setSessionId);
4462
+ done();
4463
+ });
4464
+ });
3571
4465
  describe('#setUpLocusInfoMediaInactiveListener', () => {
3572
4466
  it('listens to disconnect due to un activity ', (done) => {
3573
4467
  TriggerProxy.trigger.reset();
3574
- meeting.locusInfo.emit({function: 'test', file: 'test'}, EVENTS.DISCONNECT_DUE_TO_INACTIVITY, {reason: 'inactive'});
4468
+ meeting.locusInfo.emit(
4469
+ {function: 'test', file: 'test'},
4470
+ EVENTS.DISCONNECT_DUE_TO_INACTIVITY,
4471
+ {reason: 'inactive'}
4472
+ );
3575
4473
  assert.calledTwice(TriggerProxy.trigger);
3576
4474
 
3577
4475
  assert.calledWith(
@@ -3598,7 +4496,11 @@ describe('plugin-meetings', () => {
3598
4496
  sinon.stub(meeting, 'reconnect');
3599
4497
 
3600
4498
  meeting.config.reconnection.autoRejoin = true;
3601
- meeting.locusInfo.emit({function: 'test', file: 'test'}, EVENTS.DISCONNECT_DUE_TO_INACTIVITY, {reason: 'inactive'});
4499
+ meeting.locusInfo.emit(
4500
+ {function: 'test', file: 'test'},
4501
+ EVENTS.DISCONNECT_DUE_TO_INACTIVITY,
4502
+ {reason: 'inactive'}
4503
+ );
3602
4504
  assert.calledOnce(TriggerProxy.trigger);
3603
4505
 
3604
4506
  assert.calledWith(
@@ -3623,7 +4525,10 @@ describe('plugin-meetings', () => {
3623
4525
  sinon.stub(meeting.reconnectionManager, 'cleanUp');
3624
4526
  sinon.spy(MeetingUtil, 'cleanUp');
3625
4527
 
3626
- meeting.locusInfo.emit({function: 'test', file: 'test'}, EVENTS.DESTROY_MEETING, {shouldLeave: false, reason: 'ended'});
4528
+ meeting.locusInfo.emit({function: 'test', file: 'test'}, EVENTS.DESTROY_MEETING, {
4529
+ shouldLeave: false,
4530
+ reason: 'ended',
4531
+ });
3627
4532
  assert.calledOnce(TriggerProxy.trigger);
3628
4533
  assert.calledOnce(MeetingUtil.cleanUp);
3629
4534
  assert.calledWith(
@@ -3631,12 +4536,12 @@ describe('plugin-meetings', () => {
3631
4536
  meeting,
3632
4537
  {
3633
4538
  file: 'meeting/index',
3634
- function: 'setUpLocusInfoMeetingListener'
4539
+ function: 'setUpLocusInfoMeetingListener',
3635
4540
  },
3636
4541
  EVENTS.DESTROY_MEETING,
3637
4542
  {
3638
4543
  reason: 'ended',
3639
- meetingId: meeting.id
4544
+ meetingId: meeting.id,
3640
4545
  }
3641
4546
  );
3642
4547
  done();
@@ -3656,22 +4561,36 @@ describe('plugin-meetings', () => {
3656
4561
  sandbox = null;
3657
4562
  });
3658
4563
 
3659
- describe('#stopFloorRequest', () => {
3660
- it('should have #stopFloorRequest', () => {
3661
- assert.exists(meeting.stopFloorRequest);
4564
+ describe('#releaseScreenShareFloor', () => {
4565
+ it('should have #releaseScreenShareFloor', () => {
4566
+ assert.exists(meeting.releaseScreenShareFloor);
3662
4567
  });
3663
4568
  beforeEach(() => {
3664
- meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
4569
+ meeting.selfId = 'some self id';
4570
+ meeting.locusInfo.mediaShares = [
4571
+ {name: 'content', url: url1, floor: {beneficiary: {id: meeting.selfId}}},
4572
+ ];
3665
4573
  meeting.locusInfo.self = {url: url2};
4574
+ meeting.mediaProperties = {mediaDirection: {sendShare: true}};
3666
4575
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
3667
4576
  });
3668
- it('should call change meeting floor', async () => {
3669
- const share = meeting.share();
4577
+ it('should call changeMeetingFloor()', async () => {
4578
+ const share = meeting.releaseScreenShareFloor();
3670
4579
 
3671
4580
  assert.exists(share.then);
3672
4581
  await share;
3673
4582
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
3674
4583
  });
4584
+ it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
4585
+ // change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
4586
+ meeting.selfId = 'new self id';
4587
+
4588
+ const share = meeting.releaseScreenShareFloor();
4589
+
4590
+ assert.exists(share.then);
4591
+ await share;
4592
+ assert.notCalled(meeting.meetingRequest.changeMeetingFloor);
4593
+ });
3675
4594
  });
3676
4595
 
3677
4596
  describe('#setSipUri', () => {
@@ -3753,8 +4672,8 @@ describe('plugin-meetings', () => {
3753
4672
  permissionToken: 'abc',
3754
4673
  sipMeetingUri: test1,
3755
4674
  sipUrl: test1,
3756
- owner: test2
3757
- }
4675
+ owner: test2,
4676
+ },
3758
4677
  };
3759
4678
 
3760
4679
  meeting.parseMeetingInfo(FAKE_MEETING_INFO);
@@ -3765,7 +4684,7 @@ describe('plugin-meetings', () => {
3765
4684
  meetingNumber: '12345',
3766
4685
  meetingJoinUrl: url2,
3767
4686
  owner: test2,
3768
- permissionToken: 'abc'
4687
+ permissionToken: 'abc',
3769
4688
  };
3770
4689
 
3771
4690
  checkParseMeetingInfo(expectedInfoToParse);
@@ -3779,8 +4698,8 @@ describe('plugin-meetings', () => {
3779
4698
  info: {
3780
4699
  webExMeetingId: 'locusMeetingId',
3781
4700
  sipUri: 'locusSipUri',
3782
- owner: 'locusOwner'
3783
- }
4701
+ owner: 'locusOwner',
4702
+ },
3784
4703
  };
3785
4704
  const FAKE_MEETING_INFO = {
3786
4705
  body: {
@@ -3791,8 +4710,8 @@ describe('plugin-meetings', () => {
3791
4710
  permissionToken: 'abc',
3792
4711
  sipMeetingUri: test1,
3793
4712
  sipUrl: test1,
3794
- owner: test2
3795
- }
4713
+ owner: test2,
4714
+ },
3796
4715
  };
3797
4716
 
3798
4717
  meeting.parseMeetingInfo(FAKE_MEETING_INFO, FAKE_LOCUS_MEETING);
@@ -3803,7 +4722,7 @@ describe('plugin-meetings', () => {
3803
4722
  meetingNumber: 'locusMeetingId',
3804
4723
  meetingJoinUrl: url2,
3805
4724
  owner: 'locusOwner',
3806
- permissionToken: 'abc'
4725
+ permissionToken: 'abc',
3807
4726
  };
3808
4727
 
3809
4728
  checkParseMeetingInfo(expectedInfoToParse);
@@ -3820,8 +4739,8 @@ describe('plugin-meetings', () => {
3820
4739
  permissionToken: 'abc',
3821
4740
  sipMeetingUri: test1,
3822
4741
  sipUrl: test1,
3823
- owner: test2
3824
- }
4742
+ owner: test2,
4743
+ },
3825
4744
  };
3826
4745
 
3827
4746
  meeting.parseMeetingInfo(FAKE_MEETING_INFO);
@@ -3832,7 +4751,7 @@ describe('plugin-meetings', () => {
3832
4751
  meetingNumber: '12345',
3833
4752
  meetingJoinUrl: url2,
3834
4753
  owner: test2,
3835
- permissionToken: 'abc'
4754
+ permissionToken: 'abc',
3836
4755
  };
3837
4756
 
3838
4757
  checkParseMeetingInfo(expectedInfoToParse);
@@ -3850,8 +4769,8 @@ describe('plugin-meetings', () => {
3850
4769
  permissionToken: 'abc',
3851
4770
  sipMeetingUri: test1,
3852
4771
  sipUrl: test1,
3853
- owner: test2
3854
- }
4772
+ owner: test2,
4773
+ },
3855
4774
  };
3856
4775
 
3857
4776
  meeting.parseMeetingInfo(FAKE_MEETING_INFO, FAKE_STRING_DESTINATION);
@@ -3862,7 +4781,7 @@ describe('plugin-meetings', () => {
3862
4781
  meetingNumber: '12345',
3863
4782
  meetingJoinUrl: url2,
3864
4783
  owner: test2,
3865
- permissionToken: 'abc'
4784
+ permissionToken: 'abc',
3866
4785
  };
3867
4786
 
3868
4787
  checkParseMeetingInfo(expectedInfoToParse);
@@ -3878,7 +4797,11 @@ describe('plugin-meetings', () => {
3878
4797
  meeting.type = 'CALL';
3879
4798
  meeting.parseLocus({url: url1, participants: [{id: uuid1}], self: {id: uuid2}});
3880
4799
  assert.calledOnce(meeting.setLocus);
3881
- assert.calledWith(meeting.setLocus, {url: url1, participants: [{id: uuid1}], self: {id: uuid2}});
4800
+ assert.calledWith(meeting.setLocus, {
4801
+ url: url1,
4802
+ participants: [{id: uuid1}],
4803
+ self: {id: uuid2},
4804
+ });
3882
4805
  assert.calledOnce(MeetingUtil.getLocusPartner);
3883
4806
  assert.calledWith(MeetingUtil.getLocusPartner, [{id: uuid1}], {id: uuid2});
3884
4807
  assert.deepEqual(meeting.partner, {person: {sipUrl: uuid3}});
@@ -3927,7 +4850,7 @@ describe('plugin-meetings', () => {
3927
4850
  meeting,
3928
4851
  {
3929
4852
  file: 'meeting/index',
3930
- function: 'setUpLocusInfoAssignHostListener'
4853
+ function: 'setUpLocusInfoAssignHostListener',
3931
4854
  },
3932
4855
  'meeting:actionsUpdate',
3933
4856
  meeting.inMeetingActions.get()
@@ -3946,30 +4869,47 @@ describe('plugin-meetings', () => {
3946
4869
  let inMeetingActionsSetSpy;
3947
4870
  let canUserLockSpy;
3948
4871
  let canUserUnlockSpy;
3949
- let canUserRecordSpy;
4872
+ let canUserStartSpy;
3950
4873
  let canUserStopSpy;
3951
4874
  let canUserPauseSpy;
3952
4875
  let canUserResumeSpy;
4876
+ let canSetMuteOnEntrySpy;
4877
+ let canUnsetMuteOnEntrySpy;
4878
+ let canSetDisallowUnmuteSpy;
4879
+ let canUnsetDisallowUnmuteSpy;
3953
4880
  let canUserRaiseHandSpy;
3954
4881
  let bothLeaveAndEndMeetingAvailableSpy;
3955
4882
  let canUserLowerAllHandsSpy;
3956
4883
  let canUserLowerSomeoneElsesHandSpy;
3957
4884
  let waitingForOthersToJoinSpy;
4885
+ let handleDataChannelUrlChangeSpy;
4886
+ let canEnableReactionsSpy;
4887
+ let canSendReactionsSpy;
3958
4888
 
3959
4889
  beforeEach(() => {
3960
4890
  locusInfoOnSpy = sinon.spy(meeting.locusInfo, 'on');
3961
4891
  canUserLockSpy = sinon.spy(MeetingUtil, 'canUserLock');
3962
4892
  canUserUnlockSpy = sinon.spy(MeetingUtil, 'canUserUnlock');
3963
- canUserRecordSpy = sinon.spy(MeetingUtil, 'canUserRecord');
3964
- canUserStopSpy = sinon.spy(MeetingUtil, 'canUserStop');
3965
- canUserPauseSpy = sinon.spy(MeetingUtil, 'canUserPause');
3966
- canUserResumeSpy = sinon.spy(MeetingUtil, 'canUserResume');
4893
+ canUserStartSpy = sinon.spy(RecordingUtil, 'canUserStart');
4894
+ canUserStopSpy = sinon.spy(RecordingUtil, 'canUserStop');
4895
+ canUserPauseSpy = sinon.spy(RecordingUtil, 'canUserPause');
4896
+ canUserResumeSpy = sinon.spy(RecordingUtil, 'canUserResume');
4897
+ canSetMuteOnEntrySpy = sinon.spy(ControlsOptionsUtil, 'canSetMuteOnEntry');
4898
+ canUnsetMuteOnEntrySpy = sinon.spy(ControlsOptionsUtil, 'canUnsetMuteOnEntry');
4899
+ canSetDisallowUnmuteSpy = sinon.spy(ControlsOptionsUtil, 'canSetDisallowUnmute');
4900
+ canUnsetDisallowUnmuteSpy = sinon.spy(ControlsOptionsUtil, 'canUnsetDisallowUnmute');
3967
4901
  inMeetingActionsSetSpy = sinon.spy(meeting.inMeetingActions, 'set');
3968
4902
  canUserRaiseHandSpy = sinon.spy(MeetingUtil, 'canUserRaiseHand');
3969
4903
  canUserLowerAllHandsSpy = sinon.spy(MeetingUtil, 'canUserLowerAllHands');
3970
- bothLeaveAndEndMeetingAvailableSpy = sinon.spy(MeetingUtil, 'bothLeaveAndEndMeetingAvailable');
4904
+ bothLeaveAndEndMeetingAvailableSpy = sinon.spy(
4905
+ MeetingUtil,
4906
+ 'bothLeaveAndEndMeetingAvailable'
4907
+ );
3971
4908
  canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
3972
4909
  waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
4910
+ handleDataChannelUrlChangeSpy = sinon.spy(meeting, 'handleDataChannelUrlChange');
4911
+ canEnableReactionsSpy = sinon.spy(MeetingUtil, 'canEnableReactions');
4912
+ canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
3973
4913
  });
3974
4914
 
3975
4915
  afterEach(() => {
@@ -3978,7 +4918,6 @@ describe('plugin-meetings', () => {
3978
4918
  waitingForOthersToJoinSpy.restore();
3979
4919
  });
3980
4920
 
3981
-
3982
4921
  it('registers the correct MEETING_INFO_UPDATED event', () => {
3983
4922
  meeting.setUpLocusInfoMeetingInfoListener();
3984
4923
 
@@ -3991,30 +4930,38 @@ describe('plugin-meetings', () => {
3991
4930
 
3992
4931
  const payload = {
3993
4932
  info: {
3994
- userDisplayHints: ['LOCK_CONTROL_UNLOCK']
3995
- }
4933
+ userDisplayHints: ['LOCK_CONTROL_UNLOCK'],
4934
+ datachannelUrl: 'some url',
4935
+ },
3996
4936
  };
3997
4937
 
3998
4938
  callback(payload);
3999
4939
 
4000
4940
  assert.calledWith(canUserLockSpy, payload.info.userDisplayHints);
4001
4941
  assert.calledWith(canUserUnlockSpy, payload.info.userDisplayHints);
4002
- assert.calledWith(canUserRecordSpy, payload.info.userDisplayHints);
4942
+ assert.calledWith(canUserStartSpy, payload.info.userDisplayHints);
4003
4943
  assert.calledWith(canUserStopSpy, payload.info.userDisplayHints);
4004
4944
  assert.calledWith(canUserPauseSpy, payload.info.userDisplayHints);
4005
4945
  assert.calledWith(canUserResumeSpy, payload.info.userDisplayHints);
4946
+ assert.calledWith(canSetMuteOnEntrySpy, payload.info.userDisplayHints);
4947
+ assert.calledWith(canUnsetMuteOnEntrySpy, payload.info.userDisplayHints);
4948
+ assert.calledWith(canSetDisallowUnmuteSpy, payload.info.userDisplayHints);
4949
+ assert.calledWith(canUnsetDisallowUnmuteSpy, payload.info.userDisplayHints);
4006
4950
  assert.calledWith(canUserRaiseHandSpy, payload.info.userDisplayHints);
4007
4951
  assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, payload.info.userDisplayHints);
4008
4952
  assert.calledWith(canUserLowerAllHandsSpy, payload.info.userDisplayHints);
4009
4953
  assert.calledWith(canUserLowerSomeoneElsesHandSpy, payload.info.userDisplayHints);
4010
4954
  assert.calledWith(waitingForOthersToJoinSpy, payload.info.userDisplayHints);
4955
+ assert.calledWith(handleDataChannelUrlChangeSpy, payload.info.datachannelUrl);
4956
+ assert.calledWith(canEnableReactionsSpy, null, payload.info.userDisplayHints);
4957
+ assert.calledWith(canSendReactionsSpy, null, payload.info.userDisplayHints);
4011
4958
 
4012
4959
  assert.calledWith(
4013
4960
  TriggerProxy.trigger,
4014
4961
  meeting,
4015
4962
  {
4016
4963
  file: 'meeting/index',
4017
- function: 'setUpLocusInfoMeetingInfoListener'
4964
+ function: 'setUpLocusInfoMeetingInfoListener',
4018
4965
  },
4019
4966
  'meeting:actionsUpdate',
4020
4967
  meeting.inMeetingActions.get()
@@ -4028,6 +4975,123 @@ describe('plugin-meetings', () => {
4028
4975
  });
4029
4976
  });
4030
4977
 
4978
+ describe('#handleDataChannelUrlChange', () => {
4979
+ let updateLLMConnectionSpy;
4980
+
4981
+ beforeEach(() => {
4982
+ updateLLMConnectionSpy = sinon.spy(meeting, 'updateLLMConnection');
4983
+ });
4984
+
4985
+ const check = async (url, expectedCalled) => {
4986
+ meeting.handleDataChannelUrlChange(url);
4987
+
4988
+ assert.notCalled(updateLLMConnectionSpy);
4989
+
4990
+ await testUtils.waitUntil(0);
4991
+
4992
+ if (expectedCalled) {
4993
+ assert.calledWith(updateLLMConnectionSpy);
4994
+ } else {
4995
+ assert.notCalled(updateLLMConnectionSpy);
4996
+ }
4997
+ };
4998
+
4999
+ it('calls deferred updateLLMConnection if datachannelURL is set and the enableAutomaticLLM is true', async () => {
5000
+ meeting.config.enableAutomaticLLM = true;
5001
+ check('some url', true);
5002
+ });
5003
+
5004
+ it('does not call updateLLMConnection if datachannelURL is undefined', async () => {
5005
+ meeting.config.enableAutomaticLLM = true;
5006
+ check(undefined, false);
5007
+ });
5008
+
5009
+ it('does not call updateLLMConnection if enableAutomaticLLM is false', async () => {
5010
+ check('some url', false);
5011
+ });
5012
+ });
5013
+
5014
+ describe('#updateLLMConnection', () => {
5015
+ beforeEach(() => {
5016
+ webex.internal.llm.isConnected = sinon.stub().returns(false);
5017
+ webex.internal.llm.getLocusUrl = sinon.stub();
5018
+ webex.internal.llm.registerAndConnect = sinon
5019
+ .stub()
5020
+ .returns(Promise.resolve('something'));
5021
+ webex.internal.llm.disconnectLLM = sinon.stub().returns(Promise.resolve());
5022
+ });
5023
+
5024
+ it('does not connect if the call is not joined yet', async () => {
5025
+ meeting.joinedWith = {state: 'any other state'};
5026
+ webex.internal.llm.getLocusUrl.returns('a url');
5027
+
5028
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
5029
+
5030
+ const result = await meeting.updateLLMConnection();
5031
+
5032
+ assert.notCalled(webex.internal.llm.registerAndConnect);
5033
+ assert.notCalled(webex.internal.llm.disconnectLLM);
5034
+ assert.equal(result, undefined);
5035
+ });
5036
+
5037
+ it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
5038
+ meeting.joinedWith = {state: 'JOINED'};
5039
+ webex.internal.llm.isConnected.returns(true);
5040
+ webex.internal.llm.getLocusUrl.returns('a url');
5041
+
5042
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
5043
+
5044
+ const result = await meeting.updateLLMConnection();
5045
+
5046
+ assert.notCalled(webex.internal.llm.registerAndConnect);
5047
+ assert.notCalled(webex.internal.llm.disconnectLLM);
5048
+ assert.equal(result, undefined);
5049
+ });
5050
+
5051
+ it('connects if not already connected', async () => {
5052
+ meeting.joinedWith = {state: 'JOINED'};
5053
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
5054
+
5055
+ const result = await meeting.updateLLMConnection();
5056
+
5057
+ assert.notCalled(webex.internal.llm.disconnectLLM);
5058
+ assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a datachannel url');
5059
+ assert.equal(result, 'something');
5060
+ });
5061
+
5062
+ it('disconnects if first if the locus url has changed', async () => {
5063
+ meeting.joinedWith = {state: 'JOINED'};
5064
+ webex.internal.llm.isConnected.returns(true);
5065
+ webex.internal.llm.getLocusUrl.returns('a url');
5066
+
5067
+ meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
5068
+
5069
+ const result = await meeting.updateLLMConnection();
5070
+
5071
+ assert.calledWith(webex.internal.llm.disconnectLLM);
5072
+ assert.calledWith(
5073
+ webex.internal.llm.registerAndConnect,
5074
+ 'a different url',
5075
+ 'a datachannel url'
5076
+ );
5077
+ assert.equal(result, 'something');
5078
+ });
5079
+
5080
+ it('disconnects when the state is not JOINED', async () => {
5081
+ meeting.joinedWith = {state: 'any other state'};
5082
+ webex.internal.llm.isConnected.returns(true);
5083
+ webex.internal.llm.getLocusUrl.returns('a url');
5084
+
5085
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
5086
+
5087
+ const result = await meeting.updateLLMConnection();
5088
+
5089
+ assert.calledWith(webex.internal.llm.disconnectLLM);
5090
+ assert.notCalled(webex.internal.llm.registerAndConnect);
5091
+ assert.equal(result, undefined);
5092
+ });
5093
+ });
5094
+
4031
5095
  describe('#setLocus', () => {
4032
5096
  beforeEach(() => {
4033
5097
  meeting.locusInfo.initialSetup = sinon.stub().returns(true);
@@ -4039,7 +5103,7 @@ describe('plugin-meetings', () => {
4039
5103
  locusId: uuid1,
4040
5104
  selfId: uuid2,
4041
5105
  mediaId: uuid3,
4042
- host: {id: uuid4}
5106
+ host: {id: uuid4},
4043
5107
  });
4044
5108
  assert.calledOnce(meeting.locusInfo.initialSetup);
4045
5109
  assert.calledWith(meeting.locusInfo.initialSetup, {
@@ -4048,7 +5112,7 @@ describe('plugin-meetings', () => {
4048
5112
  locusId: uuid1,
4049
5113
  selfId: uuid2,
4050
5114
  mediaId: uuid3,
4051
- host: {id: uuid4}
5115
+ host: {id: uuid4},
4052
5116
  });
4053
5117
  assert.equal(meeting.mediaConnections, test1);
4054
5118
  assert.equal(meeting.locusUrl, url1);
@@ -4097,7 +5161,7 @@ describe('plugin-meetings', () => {
4097
5161
  });
4098
5162
  it('should send the whiteboard share', async () => {
4099
5163
  const whiteboardShare = meeting.startWhiteboardShare({
4100
- channelUrl: url2
5164
+ channelUrl: url2,
4101
5165
  });
4102
5166
 
4103
5167
  assert.exists(whiteboardShare.then);
@@ -4137,18 +5201,35 @@ describe('plugin-meetings', () => {
4137
5201
  const USER_IDS = {
4138
5202
  ME: '9528d952-e4de-46cf-8157-fd4823b98377',
4139
5203
  REMOTE_A: '5be7e7b0-b304-48da-8083-83bd72b5300d',
4140
- REMOTE_B: 'd4d102a1-17ce-4e17-9b08-bded3de467e4'
5204
+ REMOTE_B: 'd4d102a1-17ce-4e17-9b08-bded3de467e4',
4141
5205
  };
4142
5206
 
4143
5207
  const RESOURCE_URLS = {
4144
- WHITEBOARD_A: 'https://board-a.wbx2.com/board/api/v1/channels/49cfb550-5517-11eb-a2af-1b9e4bc3da13',
4145
- WHITEBOARD_B: 'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3'
5208
+ WHITEBOARD_A:
5209
+ 'https://board-a.wbx2.com/board/api/v1/channels/49cfb550-5517-11eb-a2af-1b9e4bc3da13',
5210
+ WHITEBOARD_B:
5211
+ 'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3',
4146
5212
  };
4147
5213
 
4148
- const generateContent = (beneficiaryId = null, disposition = null) => ({beneficiaryId, disposition});
4149
- const generateWhiteboard = (beneficiaryId = null, disposition = null, resourceUrl = null) => ({beneficiaryId, disposition, resourceUrl});
4150
-
4151
- const generateData = (payload, isGranting, isContent, beneficiaryId, resourceUrl, isAccepting, otherBeneficiaryId) => {
5214
+ const generateContent = (beneficiaryId = null, disposition = null) => ({
5215
+ beneficiaryId,
5216
+ disposition,
5217
+ });
5218
+ const generateWhiteboard = (
5219
+ beneficiaryId = null,
5220
+ disposition = null,
5221
+ resourceUrl = null
5222
+ ) => ({beneficiaryId, disposition, resourceUrl});
5223
+
5224
+ const generateData = (
5225
+ payload,
5226
+ isGranting,
5227
+ isContent,
5228
+ beneficiaryId,
5229
+ resourceUrl,
5230
+ isAccepting,
5231
+ otherBeneficiaryId
5232
+ ) => {
4152
5233
  const newPayload = cloneDeep(payload);
4153
5234
 
4154
5235
  newPayload.previous = cloneDeep(payload.current);
@@ -4159,15 +5240,15 @@ describe('plugin-meetings', () => {
4159
5240
  eventName: EVENT_TRIGGERS.MEMBERS_CONTENT_UPDATE,
4160
5241
  eventPayload: {
4161
5242
  activeSharingId: null,
4162
- endedSharingId: null
4163
- }
4164
- }
5243
+ endedSharingId: null,
5244
+ },
5245
+ },
4165
5246
  };
4166
5247
 
4167
5248
  let shareStatus = null;
4168
5249
  const activeSharingId = {
4169
5250
  whiteboard: null,
4170
- content: null
5251
+ content: null,
4171
5252
  };
4172
5253
 
4173
5254
  if (isGranting) {
@@ -4177,68 +5258,72 @@ describe('plugin-meetings', () => {
4177
5258
 
4178
5259
  if (isEqual(newPayload.current, newPayload.previous)) {
4179
5260
  eventTrigger.member = null;
4180
- }
4181
- else {
5261
+ } else {
4182
5262
  if (newPayload.current.whiteboard.beneficiaryId) {
4183
5263
  if (newPayload.current.whiteboard.disposition === FLOOR_ACTION.GRANTED) {
4184
5264
  newPayload.current.whiteboard.disposition = FLOOR_ACTION.RELEASED;
4185
5265
  eventTrigger.share.push({
4186
5266
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
4187
- functionName: 'stopWhiteboardShare'
5267
+ functionName: 'stopWhiteboardShare',
4188
5268
  });
4189
- eventTrigger.member.eventPayload.endedSharingId = newPayload.current.whiteboard.beneficiaryId;
5269
+ eventTrigger.member.eventPayload.endedSharingId =
5270
+ newPayload.current.whiteboard.beneficiaryId;
4190
5271
  }
4191
5272
  }
4192
5273
 
4193
5274
  if (newPayload.previous.content.beneficiaryId) {
4194
- if (newPayload.previous.content.beneficiaryId !== newPayload.current.content.beneficiaryId) {
5275
+ if (
5276
+ newPayload.previous.content.beneficiaryId !==
5277
+ newPayload.current.content.beneficiaryId
5278
+ ) {
4195
5279
  if (newPayload.previous.content.beneficiaryId === USER_IDS.ME) {
4196
5280
  eventTrigger.share.push({
4197
5281
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4198
- functionName: 'stopFloorRequest'
5282
+ functionName: 'localShare',
4199
5283
  });
4200
- }
4201
- else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
5284
+ } else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
4202
5285
  eventTrigger.share.push({
4203
5286
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_REMOTE,
4204
- functionName: 'remoteShare'
5287
+ functionName: 'remoteShare',
4205
5288
  });
4206
5289
  }
4207
- eventTrigger.member.eventPayload.endedSharingId = newPayload.previous.content.beneficiaryId;
5290
+ eventTrigger.member.eventPayload.endedSharingId =
5291
+ newPayload.previous.content.beneficiaryId;
4208
5292
  }
4209
5293
  }
4210
5294
 
4211
5295
  if (isAccepting) {
4212
5296
  eventTrigger.share.push({
4213
5297
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
4214
- functionName: 'stopWhiteboardShare'
5298
+ functionName: 'stopWhiteboardShare',
4215
5299
  });
4216
5300
  }
4217
5301
 
4218
5302
  if (beneficiaryId === USER_IDS.ME) {
4219
5303
  eventTrigger.share.push({
4220
5304
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_LOCAL,
4221
- functionName: 'share'
5305
+ functionName: 'share',
4222
5306
  });
4223
- }
4224
- else {
5307
+ } else {
4225
5308
  eventTrigger.share.push({
4226
5309
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
4227
5310
  functionName: 'remoteShare',
4228
- eventPayload: {memberId: beneficiaryId}
5311
+ eventPayload: {memberId: beneficiaryId},
4229
5312
  });
4230
5313
  }
4231
5314
  }
4232
5315
 
4233
5316
  if (beneficiaryId === USER_IDS.ME) {
4234
5317
  shareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
4235
- }
4236
- else {
5318
+ } else {
4237
5319
  shareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
4238
5320
  }
4239
- }
4240
- else {
4241
- newPayload.current.whiteboard = generateWhiteboard(beneficiaryId, FLOOR_ACTION.GRANTED, resourceUrl);
5321
+ } else {
5322
+ newPayload.current.whiteboard = generateWhiteboard(
5323
+ beneficiaryId,
5324
+ FLOOR_ACTION.GRANTED,
5325
+ resourceUrl
5326
+ );
4242
5327
 
4243
5328
  if (newPayload.current.content.beneficiaryId) {
4244
5329
  if (newPayload.current.content.disposition === FLOOR_ACTION.GRANTED) {
@@ -4246,41 +5331,48 @@ describe('plugin-meetings', () => {
4246
5331
  if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
4247
5332
  eventTrigger.share.push({
4248
5333
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4249
- functionName: 'stopFloorRequest'
5334
+ functionName: 'localShare',
4250
5335
  });
4251
- }
4252
- else {
5336
+ } else {
4253
5337
  eventTrigger.share.push({
4254
5338
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_REMOTE,
4255
- functionName: 'remoteShare'
5339
+ functionName: 'remoteShare',
4256
5340
  });
4257
5341
  }
4258
5342
 
4259
- eventTrigger.member.eventPayload.endedSharingId = newPayload.current.content.beneficiaryId;
5343
+ eventTrigger.member.eventPayload.endedSharingId =
5344
+ newPayload.current.content.beneficiaryId;
4260
5345
  }
4261
5346
  }
4262
5347
 
4263
5348
  if (newPayload.previous.content.beneficiaryId) {
4264
- if (newPayload.previous.content.beneficiaryId !== newPayload.current.content.beneficiaryId) {
5349
+ if (
5350
+ newPayload.previous.content.beneficiaryId !==
5351
+ newPayload.current.content.beneficiaryId
5352
+ ) {
4265
5353
  if (newPayload.previous.content.beneficiaryId === USER_IDS.ME) {
4266
5354
  eventTrigger.share.push({
4267
5355
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4268
- functionName: 'stopFloorRequest'
5356
+ functionName: 'localShare',
4269
5357
  });
4270
- }
4271
- else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
5358
+ } else if (newPayload.current.content.beneficiaryId === USER_IDS.ME) {
4272
5359
  eventTrigger.share.push({
4273
5360
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_REMOTE,
4274
- functionName: 'remoteShare'
5361
+ functionName: 'remoteShare',
4275
5362
  });
4276
5363
  }
4277
- eventTrigger.member.eventPayload.endedSharingId = newPayload.previous.content.beneficiaryId;
5364
+ eventTrigger.member.eventPayload.endedSharingId =
5365
+ newPayload.previous.content.beneficiaryId;
4278
5366
  }
4279
5367
  }
4280
5368
 
4281
5369
  if (newPayload.previous.whiteboard.beneficiaryId) {
4282
- if (newPayload.previous.whiteboard.beneficiaryId !== newPayload.current.whiteboard.beneficiaryId) {
4283
- eventTrigger.member.eventPayload.endedSharingId = newPayload.previous.whiteboard.beneficiaryId;
5370
+ if (
5371
+ newPayload.previous.whiteboard.beneficiaryId !==
5372
+ newPayload.current.whiteboard.beneficiaryId
5373
+ ) {
5374
+ eventTrigger.member.eventPayload.endedSharingId =
5375
+ newPayload.previous.whiteboard.beneficiaryId;
4284
5376
  }
4285
5377
  }
4286
5378
 
@@ -4289,7 +5381,7 @@ describe('plugin-meetings', () => {
4289
5381
  eventTrigger.share.push({
4290
5382
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
4291
5383
  functionName: 'startWhiteboardShare',
4292
- eventPayload: {resourceUrl, memberId: beneficiaryId}
5384
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
4293
5385
  });
4294
5386
 
4295
5387
  shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
@@ -4298,8 +5390,7 @@ describe('plugin-meetings', () => {
4298
5390
  if (eventTrigger.member) {
4299
5391
  eventTrigger.member.eventPayload.activeSharingId = beneficiaryId;
4300
5392
  }
4301
- }
4302
- else {
5393
+ } else {
4303
5394
  eventTrigger.member.eventPayload.endedSharingId = beneficiaryId;
4304
5395
 
4305
5396
  if (isContent) {
@@ -4308,19 +5399,17 @@ describe('plugin-meetings', () => {
4308
5399
  if (beneficiaryId === USER_IDS.ME) {
4309
5400
  eventTrigger.share.push({
4310
5401
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
4311
- functionName: 'stopFloorRequest'
5402
+ functionName: 'localShare',
4312
5403
  });
4313
- }
4314
- else {
5404
+ } else {
4315
5405
  eventTrigger.share.push({
4316
5406
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_REMOTE,
4317
- functionName: 'remoteShare'
5407
+ functionName: 'remoteShare',
4318
5408
  });
4319
5409
  }
4320
5410
 
4321
5411
  shareStatus = SHARE_STATUS.NO_SHARE;
4322
- }
4323
- else {
5412
+ } else {
4324
5413
  newPayload.current.whiteboard.disposition = FLOOR_ACTION.RELEASED;
4325
5414
 
4326
5415
  if (isAccepting) {
@@ -4330,15 +5419,14 @@ describe('plugin-meetings', () => {
4330
5419
  eventTrigger.share.push({
4331
5420
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
4332
5421
  functionName: 'startWhiteboardShare',
4333
- eventPayload: {resourceUrl, memberId: beneficiaryId}
5422
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
4334
5423
  });
4335
5424
 
4336
5425
  shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
4337
- }
4338
- else {
5426
+ } else {
4339
5427
  eventTrigger.share.push({
4340
5428
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
4341
- functionName: 'stopWhiteboardShare'
5429
+ functionName: 'stopWhiteboardShare',
4342
5430
  });
4343
5431
 
4344
5432
  shareStatus = SHARE_STATUS.NO_SHARE;
@@ -4347,22 +5435,24 @@ describe('plugin-meetings', () => {
4347
5435
  }
4348
5436
 
4349
5437
  return {
4350
- payload: newPayload, eventTrigger, shareStatus, activeSharingId
5438
+ payload: newPayload,
5439
+ eventTrigger,
5440
+ shareStatus,
5441
+ activeSharingId,
4351
5442
  };
4352
5443
  };
4353
5444
 
4354
5445
  const blankPayload = {
4355
5446
  previous: {
4356
5447
  content: generateContent(),
4357
- whiteboard: generateWhiteboard()
5448
+ whiteboard: generateWhiteboard(),
4358
5449
  },
4359
5450
  current: {
4360
5451
  content: generateContent(),
4361
- whiteboard: generateWhiteboard()
4362
- }
5452
+ whiteboard: generateWhiteboard(),
5453
+ },
4363
5454
  };
4364
5455
 
4365
-
4366
5456
  const payloadTestHelper = (data) => {
4367
5457
  assert.equal(meeting.shareStatus, SHARE_STATUS.NO_SHARE);
4368
5458
 
@@ -4370,23 +5460,33 @@ describe('plugin-meetings', () => {
4370
5460
  let callCounter = 1;
4371
5461
 
4372
5462
  data.forEach((d, index) => {
4373
- meeting.locusInfo.emit({function: 'test', file: 'test'}, EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, d.payload);
5463
+ meeting.locusInfo.emit(
5464
+ {function: 'test', file: 'test'},
5465
+ EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
5466
+ d.payload
5467
+ );
4374
5468
 
4375
5469
  assert.equal(meeting.shareStatus, data[index].shareStatus);
4376
5470
 
4377
- callCounter += data[index].eventTrigger.share.length + (data[index].eventTrigger.member ? 1 : 0);
5471
+ callCounter +=
5472
+ data[index].eventTrigger.share.length + (data[index].eventTrigger.member ? 1 : 0);
4378
5473
 
4379
5474
  assert.callCount(TriggerProxy.trigger, callCounter);
4380
5475
 
4381
- assert.equal(meeting.members.mediaShareWhiteboardId, data[index].activeSharingId.whiteboard);
4382
- assert.equal(meeting.members.mediaShareContentId, data[index].activeSharingId.content);
5476
+ assert.equal(
5477
+ meeting.members.mediaShareWhiteboardId,
5478
+ data[index].activeSharingId.whiteboard
5479
+ );
5480
+ assert.equal(
5481
+ meeting.members.mediaShareContentId,
5482
+ data[index].activeSharingId.content
5483
+ );
4383
5484
  });
4384
5485
 
4385
5486
  assert.callCount(TriggerProxy.trigger, callCounter);
4386
5487
 
4387
5488
  // Start with 1 to ignore members:update trigger
4388
5489
 
4389
-
4390
5490
  let i = 1;
4391
5491
  let offset = 2;
4392
5492
 
@@ -4398,21 +5498,24 @@ describe('plugin-meetings', () => {
4398
5498
  for (let idx = 0; idx < share.length; idx += 1) {
4399
5499
  const shareCallArgs = TriggerProxy.trigger.getCall(i + idx).args;
4400
5500
  const {functionName, eventName, eventPayload} = share[idx];
4401
- const fileName = functionName === 'remoteShare' ? 'meetings/index' : 'meeting/index';
5501
+ const fileName =
5502
+ functionName === 'remoteShare' ? 'meetings/index' : 'meeting/index';
4402
5503
 
4403
5504
  assert.deepEqual(shareCallArgs[1], {
4404
5505
  file: fileName,
4405
- function: functionName
5506
+ function: functionName,
4406
5507
  });
4407
5508
 
4408
-
4409
5509
  assert.equal(shareCallArgs[2], eventName);
4410
5510
 
4411
5511
  if (functionName === 'startWhiteboardShare') {
4412
5512
  assert.deepEqual(shareCallArgs[3], eventPayload);
4413
5513
  }
4414
5514
 
4415
- if (functionName === 'remoteShare' && eventName === EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE) {
5515
+ if (
5516
+ functionName === 'remoteShare' &&
5517
+ eventName === EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE
5518
+ ) {
4416
5519
  assert.deepEqual(shareCallArgs[3], eventPayload);
4417
5520
  }
4418
5521
  }
@@ -4423,7 +5526,7 @@ describe('plugin-meetings', () => {
4423
5526
 
4424
5527
  assert.deepEqual(memberCallArgs[1], {
4425
5528
  file: 'members',
4426
- function: 'locusMediaSharesUpdate'
5529
+ function: 'locusMediaSharesUpdate',
4427
5530
  });
4428
5531
  assert.equal(memberCallArgs[2], member.eventName);
4429
5532
 
@@ -4437,9 +5540,8 @@ describe('plugin-meetings', () => {
4437
5540
 
4438
5541
  if (share.length + 1 > offset) {
4439
5542
  offset = (offset + share.length + 1) / 2;
4440
- }
4441
- else if (share.length + 1 < offset) {
4442
- offset = (share.length + 1) + 0.5;
5543
+ } else if (share.length + 1 < offset) {
5544
+ offset = share.length + 1 + 0.5;
4443
5545
  }
4444
5546
  }
4445
5547
  };
@@ -4450,40 +5552,100 @@ describe('plugin-meetings', () => {
4450
5552
 
4451
5553
  describe('Whiteboard A --> Whiteboard B', () => {
4452
5554
  it('Scenario #1: you share both whiteboards', () => {
4453
- const data1 = generateData(blankPayload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A);
4454
- const data2 = generateData(data1.payload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_B);
5555
+ const data1 = generateData(
5556
+ blankPayload,
5557
+ true,
5558
+ false,
5559
+ USER_IDS.ME,
5560
+ RESOURCE_URLS.WHITEBOARD_A
5561
+ );
5562
+ const data2 = generateData(
5563
+ data1.payload,
5564
+ true,
5565
+ false,
5566
+ USER_IDS.ME,
5567
+ RESOURCE_URLS.WHITEBOARD_B
5568
+ );
4455
5569
  const data3 = generateData(data2.payload, false, false, USER_IDS.ME);
4456
5570
 
4457
5571
  payloadTestHelper([data1, data2, data3]);
4458
5572
  });
4459
5573
 
4460
5574
  it('Scenario #2: you share whiteboard A and remote person A shares whiteboard B', () => {
4461
- const data1 = generateData(blankPayload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A);
4462
- const data2 = generateData(data1.payload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_B);
5575
+ const data1 = generateData(
5576
+ blankPayload,
5577
+ true,
5578
+ false,
5579
+ USER_IDS.ME,
5580
+ RESOURCE_URLS.WHITEBOARD_A
5581
+ );
5582
+ const data2 = generateData(
5583
+ data1.payload,
5584
+ true,
5585
+ false,
5586
+ USER_IDS.REMOTE_A,
5587
+ RESOURCE_URLS.WHITEBOARD_B
5588
+ );
4463
5589
  const data3 = generateData(data2.payload, false, false, USER_IDS.REMOTE_A);
4464
5590
 
4465
5591
  payloadTestHelper([data1, data2, data3]);
4466
5592
  });
4467
5593
 
4468
5594
  it('Scenario #3: remote person A shares whiteboard A and you share whiteboard B', () => {
4469
- const data1 = generateData(blankPayload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
4470
- const data2 = generateData(data1.payload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_B);
5595
+ const data1 = generateData(
5596
+ blankPayload,
5597
+ true,
5598
+ false,
5599
+ USER_IDS.REMOTE_A,
5600
+ RESOURCE_URLS.WHITEBOARD_A
5601
+ );
5602
+ const data2 = generateData(
5603
+ data1.payload,
5604
+ true,
5605
+ false,
5606
+ USER_IDS.ME,
5607
+ RESOURCE_URLS.WHITEBOARD_B
5608
+ );
4471
5609
  const data3 = generateData(data2.payload, false, false, USER_IDS.ME);
4472
5610
 
4473
5611
  payloadTestHelper([data1, data2, data3]);
4474
5612
  });
4475
5613
 
4476
5614
  it('Scenario #4: remote person A shares both whiteboards', () => {
4477
- const data1 = generateData(blankPayload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
4478
- const data2 = generateData(data1.payload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_B);
5615
+ const data1 = generateData(
5616
+ blankPayload,
5617
+ true,
5618
+ false,
5619
+ USER_IDS.REMOTE_A,
5620
+ RESOURCE_URLS.WHITEBOARD_A
5621
+ );
5622
+ const data2 = generateData(
5623
+ data1.payload,
5624
+ true,
5625
+ false,
5626
+ USER_IDS.REMOTE_A,
5627
+ RESOURCE_URLS.WHITEBOARD_B
5628
+ );
4479
5629
  const data3 = generateData(data2.payload, false, false, USER_IDS.REMOTE_A);
4480
5630
 
4481
5631
  payloadTestHelper([data1, data2, data3]);
4482
5632
  });
4483
5633
 
4484
5634
  it('Scenario #5: remote person A shares whiteboard A and remote person B shares whiteboard B', () => {
4485
- const data1 = generateData(blankPayload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
4486
- const data2 = generateData(data1.payload, true, false, USER_IDS.REMOTE_B, RESOURCE_URLS.WHITEBOARD_B);
5635
+ const data1 = generateData(
5636
+ blankPayload,
5637
+ true,
5638
+ false,
5639
+ USER_IDS.REMOTE_A,
5640
+ RESOURCE_URLS.WHITEBOARD_A
5641
+ );
5642
+ const data2 = generateData(
5643
+ data1.payload,
5644
+ true,
5645
+ false,
5646
+ USER_IDS.REMOTE_B,
5647
+ RESOURCE_URLS.WHITEBOARD_B
5648
+ );
4487
5649
  const data3 = generateData(data2.payload, false, false, USER_IDS.REMOTE_B);
4488
5650
 
4489
5651
  payloadTestHelper([data1, data2, data3]);
@@ -4492,45 +5654,155 @@ describe('plugin-meetings', () => {
4492
5654
 
4493
5655
  describe('Whiteboard A --> Desktop', () => {
4494
5656
  it('Scenario #1: you share whiteboard and then share desktop', () => {
4495
- const data1 = generateData(blankPayload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A);
4496
- const data2 = generateData(data1.payload, false, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A, true, USER_IDS.ME);
4497
- const data3 = generateData(data2.payload, true, true, USER_IDS.ME, undefined, true, USER_IDS.ME);
5657
+ const data1 = generateData(
5658
+ blankPayload,
5659
+ true,
5660
+ false,
5661
+ USER_IDS.ME,
5662
+ RESOURCE_URLS.WHITEBOARD_A
5663
+ );
5664
+ const data2 = generateData(
5665
+ data1.payload,
5666
+ false,
5667
+ false,
5668
+ USER_IDS.ME,
5669
+ RESOURCE_URLS.WHITEBOARD_A,
5670
+ true,
5671
+ USER_IDS.ME
5672
+ );
5673
+ const data3 = generateData(
5674
+ data2.payload,
5675
+ true,
5676
+ true,
5677
+ USER_IDS.ME,
5678
+ undefined,
5679
+ true,
5680
+ USER_IDS.ME
5681
+ );
4498
5682
  const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
4499
5683
 
4500
5684
  payloadTestHelper([data1, data2, data3, data4]);
4501
5685
  });
4502
5686
 
4503
5687
  it('Scenario #2: you share whiteboard A and remote person A shares desktop', () => {
4504
- const data1 = generateData(blankPayload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A);
4505
- const data2 = generateData(data1.payload, false, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A, true, USER_IDS.REMOTE_A);
4506
- const data3 = generateData(data2.payload, true, true, USER_IDS.REMOTE_A, undefined, true, USER_IDS.ME);
5688
+ const data1 = generateData(
5689
+ blankPayload,
5690
+ true,
5691
+ false,
5692
+ USER_IDS.ME,
5693
+ RESOURCE_URLS.WHITEBOARD_A
5694
+ );
5695
+ const data2 = generateData(
5696
+ data1.payload,
5697
+ false,
5698
+ false,
5699
+ USER_IDS.ME,
5700
+ RESOURCE_URLS.WHITEBOARD_A,
5701
+ true,
5702
+ USER_IDS.REMOTE_A
5703
+ );
5704
+ const data3 = generateData(
5705
+ data2.payload,
5706
+ true,
5707
+ true,
5708
+ USER_IDS.REMOTE_A,
5709
+ undefined,
5710
+ true,
5711
+ USER_IDS.ME
5712
+ );
4507
5713
  const data4 = generateData(data3.payload, false, true, USER_IDS.REMOTE_A);
4508
5714
 
4509
5715
  payloadTestHelper([data1, data2, data3, data4]);
4510
5716
  });
4511
5717
 
4512
5718
  it('Scenario #3: remote person A shares whiteboard and you share desktop', () => {
4513
- const data1 = generateData(blankPayload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
4514
- const data2 = generateData(data1.payload, false, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A, true, USER_IDS.ME);
4515
- const data3 = generateData(data2.payload, true, true, USER_IDS.ME, undefined, true, USER_IDS.REMOTE_A);
5719
+ const data1 = generateData(
5720
+ blankPayload,
5721
+ true,
5722
+ false,
5723
+ USER_IDS.REMOTE_A,
5724
+ RESOURCE_URLS.WHITEBOARD_A
5725
+ );
5726
+ const data2 = generateData(
5727
+ data1.payload,
5728
+ false,
5729
+ false,
5730
+ USER_IDS.REMOTE_A,
5731
+ RESOURCE_URLS.WHITEBOARD_A,
5732
+ true,
5733
+ USER_IDS.ME
5734
+ );
5735
+ const data3 = generateData(
5736
+ data2.payload,
5737
+ true,
5738
+ true,
5739
+ USER_IDS.ME,
5740
+ undefined,
5741
+ true,
5742
+ USER_IDS.REMOTE_A
5743
+ );
4516
5744
  const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
4517
5745
 
4518
5746
  payloadTestHelper([data1, data2, data3, data4]);
4519
5747
  });
4520
5748
 
4521
5749
  it('Scenario #4: remote person A shares whiteboard and then shares desktop', () => {
4522
- const data1 = generateData(blankPayload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
4523
- const data2 = generateData(data1.payload, false, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A, true, USER_IDS.REMOTE_A);
4524
- const data3 = generateData(data2.payload, true, true, USER_IDS.REMOTE_A, undefined, true, USER_IDS.REMOTE_A);
5750
+ const data1 = generateData(
5751
+ blankPayload,
5752
+ true,
5753
+ false,
5754
+ USER_IDS.REMOTE_A,
5755
+ RESOURCE_URLS.WHITEBOARD_A
5756
+ );
5757
+ const data2 = generateData(
5758
+ data1.payload,
5759
+ false,
5760
+ false,
5761
+ USER_IDS.REMOTE_A,
5762
+ RESOURCE_URLS.WHITEBOARD_A,
5763
+ true,
5764
+ USER_IDS.REMOTE_A
5765
+ );
5766
+ const data3 = generateData(
5767
+ data2.payload,
5768
+ true,
5769
+ true,
5770
+ USER_IDS.REMOTE_A,
5771
+ undefined,
5772
+ true,
5773
+ USER_IDS.REMOTE_A
5774
+ );
4525
5775
  const data4 = generateData(data3.payload, false, true, USER_IDS.REMOTE_A);
4526
5776
 
4527
5777
  payloadTestHelper([data1, data2, data3, data4]);
4528
5778
  });
4529
5779
 
4530
5780
  it('Scenario #5: remote person A shares whiteboard and remote person B shares desktop', () => {
4531
- const data1 = generateData(blankPayload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
4532
- const data2 = generateData(data1.payload, false, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A, true, USER_IDS.REMOTE_B);
4533
- const data3 = generateData(data2.payload, true, true, USER_IDS.REMOTE_B, undefined, true, USER_IDS.REMOTE_A);
5781
+ const data1 = generateData(
5782
+ blankPayload,
5783
+ true,
5784
+ false,
5785
+ USER_IDS.REMOTE_A,
5786
+ RESOURCE_URLS.WHITEBOARD_A
5787
+ );
5788
+ const data2 = generateData(
5789
+ data1.payload,
5790
+ false,
5791
+ false,
5792
+ USER_IDS.REMOTE_A,
5793
+ RESOURCE_URLS.WHITEBOARD_A,
5794
+ true,
5795
+ USER_IDS.REMOTE_B
5796
+ );
5797
+ const data3 = generateData(
5798
+ data2.payload,
5799
+ true,
5800
+ true,
5801
+ USER_IDS.REMOTE_B,
5802
+ undefined,
5803
+ true,
5804
+ USER_IDS.REMOTE_A
5805
+ );
4534
5806
  const data4 = generateData(data3.payload, false, true, USER_IDS.REMOTE_B);
4535
5807
 
4536
5808
  payloadTestHelper([data1, data2, data3, data4]);
@@ -4540,7 +5812,13 @@ describe('plugin-meetings', () => {
4540
5812
  describe('Desktop --> Whiteboard A', () => {
4541
5813
  it('Scenario #1: you share desktop and then share whiteboard', () => {
4542
5814
  const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
4543
- const data2 = generateData(data1.payload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A);
5815
+ const data2 = generateData(
5816
+ data1.payload,
5817
+ true,
5818
+ false,
5819
+ USER_IDS.ME,
5820
+ RESOURCE_URLS.WHITEBOARD_A
5821
+ );
4544
5822
  const data3 = generateData(data2.payload, false, false, USER_IDS.ME);
4545
5823
 
4546
5824
  payloadTestHelper([data1, data2, data3]);
@@ -4548,7 +5826,13 @@ describe('plugin-meetings', () => {
4548
5826
 
4549
5827
  it('Scenario #2: you share desktop and remote person A shares whiteboard', () => {
4550
5828
  const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
4551
- const data2 = generateData(data1.payload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
5829
+ const data2 = generateData(
5830
+ data1.payload,
5831
+ true,
5832
+ false,
5833
+ USER_IDS.REMOTE_A,
5834
+ RESOURCE_URLS.WHITEBOARD_A
5835
+ );
4552
5836
  const data3 = generateData(data2.payload, false, false, USER_IDS.REMOTE_A);
4553
5837
 
4554
5838
  payloadTestHelper([data1, data2, data3]);
@@ -4556,7 +5840,13 @@ describe('plugin-meetings', () => {
4556
5840
 
4557
5841
  it('Scenario #3: remote person A shares desktop and you share whiteboard', () => {
4558
5842
  const data1 = generateData(blankPayload, true, true, USER_IDS.REMOTE_A);
4559
- const data2 = generateData(data1.payload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
5843
+ const data2 = generateData(
5844
+ data1.payload,
5845
+ true,
5846
+ false,
5847
+ USER_IDS.REMOTE_A,
5848
+ RESOURCE_URLS.WHITEBOARD_A
5849
+ );
4560
5850
  const data3 = generateData(data2.payload, false, false, USER_IDS.REMOTE_A);
4561
5851
 
4562
5852
  payloadTestHelper([data1, data2, data3]);
@@ -4564,7 +5854,13 @@ describe('plugin-meetings', () => {
4564
5854
 
4565
5855
  it('Scenario #4: remote person A shares desktop and then shares whiteboard', () => {
4566
5856
  const data1 = generateData(blankPayload, true, true, USER_IDS.REMOTE_A);
4567
- const data2 = generateData(data1.payload, true, false, USER_IDS.ME, RESOURCE_URLS.WHITEBOARD_A);
5857
+ const data2 = generateData(
5858
+ data1.payload,
5859
+ true,
5860
+ false,
5861
+ USER_IDS.ME,
5862
+ RESOURCE_URLS.WHITEBOARD_A
5863
+ );
4568
5864
  const data3 = generateData(data2.payload, false, false, USER_IDS.ME);
4569
5865
 
4570
5866
  payloadTestHelper([data1, data2, data3]);
@@ -4572,7 +5868,13 @@ describe('plugin-meetings', () => {
4572
5868
 
4573
5869
  it('Scenario #5: remote person A shares desktop and remote person B shares whiteboard', () => {
4574
5870
  const data1 = generateData(blankPayload, true, true, USER_IDS.REMOTE_A);
4575
- const data2 = generateData(data1.payload, true, false, USER_IDS.REMOTE_A, RESOURCE_URLS.WHITEBOARD_A);
5871
+ const data2 = generateData(
5872
+ data1.payload,
5873
+ true,
5874
+ false,
5875
+ USER_IDS.REMOTE_A,
5876
+ RESOURCE_URLS.WHITEBOARD_A
5877
+ );
4576
5878
  const data3 = generateData(data2.payload, false, false, USER_IDS.REMOTE_A);
4577
5879
 
4578
5880
  payloadTestHelper([data1, data2, data3]);
@@ -4648,17 +5950,21 @@ describe('plugin-meetings', () => {
4648
5950
  assert.isNull(meeting.keepAliveTimerId);
4649
5951
  meeting.joinedWith = {
4650
5952
  keepAliveUrl: defaultKeepAliveUrl,
4651
- keepAliveSecs: defaultKeepAliveSecs
5953
+ keepAliveSecs: defaultKeepAliveSecs,
4652
5954
  };
4653
5955
  meeting.startKeepAlive();
4654
5956
  assert.isNumber(meeting.keepAliveTimerId.id);
4655
5957
  await testUtils.flushPromises();
4656
5958
  assert.notCalled(meeting.meetingRequest.keepAlive);
4657
5959
  await progressTime(defaultExpectedInterval);
4658
- assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {keepAliveUrl: defaultKeepAliveUrl});
5960
+ assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
5961
+ keepAliveUrl: defaultKeepAliveUrl,
5962
+ });
4659
5963
  await progressTime(defaultExpectedInterval);
4660
5964
  assert.calledTwice(meeting.meetingRequest.keepAlive);
4661
- assert.alwaysCalledWithExactly(meeting.meetingRequest.keepAlive, {keepAliveUrl: defaultKeepAliveUrl});
5965
+ assert.alwaysCalledWithExactly(meeting.meetingRequest.keepAlive, {
5966
+ keepAliveUrl: defaultKeepAliveUrl,
5967
+ });
4662
5968
  });
4663
5969
  it('startKeepAlive handles existing keepAliveTimerId', async () => {
4664
5970
  meeting.meetingRequest.keepAlive = sinon.stub().returns(Promise.resolve());
@@ -4667,7 +5973,7 @@ describe('plugin-meetings', () => {
4667
5973
  meeting.keepAliveTimerId = 7;
4668
5974
  meeting.joinedWith = {
4669
5975
  keepAliveUrl: defaultKeepAliveUrl,
4670
- keepAliveSecs: defaultKeepAliveSecs
5976
+ keepAliveSecs: defaultKeepAliveSecs,
4671
5977
  };
4672
5978
  meeting.startKeepAlive();
4673
5979
  assert.equal(meeting.keepAliveTimerId, 7);
@@ -4680,7 +5986,7 @@ describe('plugin-meetings', () => {
4680
5986
 
4681
5987
  assert.isNull(meeting.keepAliveTimerId);
4682
5988
  meeting.joinedWith = {
4683
- keepAliveSecs: defaultKeepAliveSecs
5989
+ keepAliveSecs: defaultKeepAliveSecs,
4684
5990
  };
4685
5991
  meeting.startKeepAlive();
4686
5992
  assert.isNull(meeting.keepAliveTimerId);
@@ -4693,7 +5999,7 @@ describe('plugin-meetings', () => {
4693
5999
 
4694
6000
  assert.isNull(meeting.keepAliveTimerId);
4695
6001
  meeting.joinedWith = {
4696
- keepAliveUrl: defaultKeepAliveUrl
6002
+ keepAliveUrl: defaultKeepAliveUrl,
4697
6003
  };
4698
6004
  meeting.startKeepAlive();
4699
6005
  assert.isNull(meeting.keepAliveTimerId);
@@ -4707,7 +6013,7 @@ describe('plugin-meetings', () => {
4707
6013
  assert.isNull(meeting.keepAliveTimerId);
4708
6014
  meeting.joinedWith = {
4709
6015
  keepAliveUrl: defaultKeepAliveUrl,
4710
- keepAliveSecs: 1
6016
+ keepAliveSecs: 1,
4711
6017
  };
4712
6018
  meeting.startKeepAlive();
4713
6019
  assert.isNull(meeting.keepAliveTimerId);
@@ -4720,14 +6026,16 @@ describe('plugin-meetings', () => {
4720
6026
  assert.isNull(meeting.keepAliveTimerId);
4721
6027
  meeting.joinedWith = {
4722
6028
  keepAliveUrl: defaultKeepAliveUrl,
4723
- keepAliveSecs: defaultKeepAliveSecs
6029
+ keepAliveSecs: defaultKeepAliveSecs,
4724
6030
  };
4725
6031
  meeting.startKeepAlive();
4726
6032
  assert.isNumber(meeting.keepAliveTimerId.id);
4727
6033
  await testUtils.flushPromises();
4728
6034
  assert.notCalled(meeting.meetingRequest.keepAlive);
4729
6035
  await progressTime(defaultExpectedInterval);
4730
- assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {keepAliveUrl: defaultKeepAliveUrl});
6036
+ assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
6037
+ keepAliveUrl: defaultKeepAliveUrl,
6038
+ });
4731
6039
  assert.isNull(meeting.keepAliveTimerId);
4732
6040
  await progressTime(defaultExpectedInterval);
4733
6041
  assert.calledOnce(meeting.meetingRequest.keepAlive);
@@ -4757,12 +6065,14 @@ describe('plugin-meetings', () => {
4757
6065
  assert.isNull(meeting.keepAliveTimerId);
4758
6066
  meeting.joinedWith = {
4759
6067
  keepAliveUrl: defaultKeepAliveUrl,
4760
- keepAliveSecs: defaultKeepAliveSecs
6068
+ keepAliveSecs: defaultKeepAliveSecs,
4761
6069
  };
4762
6070
  meeting.startKeepAlive();
4763
6071
  assert.isNumber(meeting.keepAliveTimerId.id);
4764
6072
  await progressTime(defaultExpectedInterval);
4765
- assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {keepAliveUrl: defaultKeepAliveUrl});
6073
+ assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
6074
+ keepAliveUrl: defaultKeepAliveUrl,
6075
+ });
4766
6076
 
4767
6077
  meeting.stopKeepAlive();
4768
6078
  assert.isNull(meeting.keepAliveTimerId);
@@ -4774,6 +6084,164 @@ describe('plugin-meetings', () => {
4774
6084
  meeting.stopKeepAlive();
4775
6085
  });
4776
6086
  });
6087
+
6088
+ describe('#sendReaction', () => {
6089
+ it('should have #sendReaction', () => {
6090
+ assert.exists(meeting.sendReaction);
6091
+ });
6092
+
6093
+ beforeEach(() => {
6094
+ meeting.meetingRequest.sendReaction = sinon.stub().returns(Promise.resolve());
6095
+ });
6096
+
6097
+ it('should send reaction with the right data and return a promise', async () => {
6098
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
6099
+
6100
+ const reactionPromise = meeting.sendReaction('thumb_down', 'light');
6101
+
6102
+ assert.exists(reactionPromise.then);
6103
+ await reactionPromise;
6104
+ assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
6105
+ reactionChannelUrl: 'Fake URL',
6106
+ reaction: {
6107
+ type: 'thumb_down',
6108
+ codepoints: '1F44E',
6109
+ shortcodes: ':thumbsdown:',
6110
+ tone: {
6111
+ type: 'light_skin_tone',
6112
+ codepoints: '1F3FB',
6113
+ shortcodes: ':skin-tone-2:',
6114
+ },
6115
+ },
6116
+ participantId: meeting.members.selfId,
6117
+ });
6118
+ });
6119
+
6120
+ it('should fail sending a reaction if data channel is undefined', async () => {
6121
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: undefined}};
6122
+
6123
+ await assert.isRejected(
6124
+ meeting.sendReaction('thumb_down', 'light'),
6125
+ Error,
6126
+ 'Error sending reaction, service url not found.'
6127
+ );
6128
+
6129
+ assert.notCalled(meeting.meetingRequest.sendReaction);
6130
+ });
6131
+
6132
+ it('should fail sending a reaction if reactionType is invalid ', async () => {
6133
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
6134
+
6135
+ await assert.isRejected(
6136
+ meeting.sendReaction('invalid_reaction', 'light'),
6137
+ Error,
6138
+ 'invalid_reaction is not a valid reaction.'
6139
+ );
6140
+
6141
+ assert.notCalled(meeting.meetingRequest.sendReaction);
6142
+ });
6143
+
6144
+ it('should send a reaction with default skin tone if provided skinToneType is invalid ', async () => {
6145
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
6146
+
6147
+ const reactionPromise = meeting.sendReaction('thumb_down', 'invalid_skin_tone');
6148
+
6149
+ assert.exists(reactionPromise.then);
6150
+ await reactionPromise;
6151
+ assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
6152
+ reactionChannelUrl: 'Fake URL',
6153
+ reaction: {
6154
+ type: 'thumb_down',
6155
+ codepoints: '1F44E',
6156
+ shortcodes: ':thumbsdown:',
6157
+ tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''},
6158
+ },
6159
+ participantId: meeting.members.selfId,
6160
+ });
6161
+ });
6162
+
6163
+ it('should send a reaction with default skin tone if none provided', async () => {
6164
+ meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
6165
+
6166
+ const reactionPromise = meeting.sendReaction('thumb_down');
6167
+
6168
+ assert.exists(reactionPromise.then);
6169
+ await reactionPromise;
6170
+ assert.calledOnceWithExactly(meeting.meetingRequest.sendReaction, {
6171
+ reactionChannelUrl: 'Fake URL',
6172
+ reaction: {
6173
+ type: 'thumb_down',
6174
+ codepoints: '1F44E',
6175
+ shortcodes: ':thumbsdown:',
6176
+ tone: {type: 'normal_skin_tone', codepoints: '', shortcodes: ''},
6177
+ },
6178
+ participantId: meeting.members.selfId,
6179
+ });
6180
+ });
6181
+ });
6182
+ describe('#toggleReactions', () => {
6183
+ it('should have #toggleReactions', () => {
6184
+ assert.exists(meeting.toggleReactions);
6185
+ });
6186
+
6187
+ beforeEach(() => {
6188
+ meeting.meetingRequest.toggleReactions = sinon.stub().returns(Promise.resolve());
6189
+ });
6190
+
6191
+ it('should toggle the reactions with the right data and return a promise', async () => {
6192
+ meeting.locusUrl = 'locusUrl';
6193
+ meeting.locusInfo.controls = {reactions: {enabled: false}};
6194
+
6195
+ const togglePromise = meeting.toggleReactions(true);
6196
+
6197
+ assert.exists(togglePromise.then);
6198
+ await togglePromise;
6199
+ assert.calledOnceWithExactly(meeting.meetingRequest.toggleReactions, {
6200
+ locusUrl: 'locusUrl',
6201
+ enable: true,
6202
+ requestingParticipantId: meeting.members.selfId,
6203
+ });
6204
+ });
6205
+
6206
+ it('should resolve immediately if already enabled', async () => {
6207
+ meeting.locusUrl = 'locusUrl';
6208
+ meeting.locusInfo.controls = {reactions: {enabled: true}};
6209
+
6210
+ const togglePromise = meeting.toggleReactions(true);
6211
+
6212
+ const response = await togglePromise;
6213
+
6214
+ assert.equal(response, 'Reactions are already enabled.');
6215
+ assert.notCalled(meeting.meetingRequest.toggleReactions);
6216
+ });
6217
+
6218
+ it('should resolve immediately if already disabled', async () => {
6219
+ meeting.locusUrl = 'locusUrl';
6220
+ meeting.locusInfo.controls = {reactions: {enabled: false}};
6221
+
6222
+ const togglePromise = meeting.toggleReactions(false);
6223
+
6224
+ const response = await togglePromise;
6225
+
6226
+ assert.equal(response, 'Reactions are already disabled.');
6227
+ assert.notCalled(meeting.meetingRequest.toggleReactions);
6228
+ });
6229
+
6230
+ it('should toggle reactions on if controls is undefined and enable = true', async () => {
6231
+ meeting.locusUrl = 'locusUrl';
6232
+ meeting.locusInfo.controls = undefined;
6233
+
6234
+ const togglePromise = meeting.toggleReactions(true);
6235
+
6236
+ assert.exists(togglePromise.then);
6237
+ await togglePromise;
6238
+ assert.calledOnceWithExactly(meeting.meetingRequest.toggleReactions, {
6239
+ locusUrl: 'locusUrl',
6240
+ enable: true,
6241
+ requestingParticipantId: meeting.members.selfId,
6242
+ });
6243
+ });
6244
+ });
4777
6245
  });
4778
6246
  });
4779
6247
  });