@webex/plugin-meetings 3.8.0 → 3.8.1-next.1

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 (360) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +70 -6
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/common/errors/webex-errors.js +12 -2
  5. package/dist/common/errors/webex-errors.js.map +1 -1
  6. package/dist/config.js +5 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +23 -123
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +2 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/types.js.map +1 -1
  13. package/dist/controls-options-manager/util.js +52 -0
  14. package/dist/controls-options-manager/util.js.map +1 -1
  15. package/dist/interpretation/index.js +4 -4
  16. package/dist/interpretation/index.js.map +1 -1
  17. package/dist/interpretation/siLanguage.js +1 -1
  18. package/dist/locus-info/controlsUtils.js +31 -11
  19. package/dist/locus-info/controlsUtils.js.map +1 -1
  20. package/dist/locus-info/index.js +83 -12
  21. package/dist/locus-info/index.js.map +1 -1
  22. package/dist/locus-info/selfUtils.js +432 -418
  23. package/dist/locus-info/selfUtils.js.map +1 -1
  24. package/dist/media/index.js +17 -17
  25. package/dist/media/index.js.map +1 -1
  26. package/dist/media/properties.js +94 -6
  27. package/dist/media/properties.js.map +1 -1
  28. package/dist/meeting/brbState.js +9 -2
  29. package/dist/meeting/brbState.js.map +1 -1
  30. package/dist/meeting/in-meeting-actions.js +17 -1
  31. package/dist/meeting/in-meeting-actions.js.map +1 -1
  32. package/dist/meeting/index.js +661 -334
  33. package/dist/meeting/index.js.map +1 -1
  34. package/dist/meeting/locusMediaRequest.js +21 -22
  35. package/dist/meeting/locusMediaRequest.js.map +1 -1
  36. package/dist/meeting/muteState.js +4 -4
  37. package/dist/meeting/muteState.js.map +1 -1
  38. package/dist/meeting/request.js +30 -0
  39. package/dist/meeting/request.js.map +1 -1
  40. package/dist/meeting/request.type.js.map +1 -1
  41. package/dist/meeting/util.js +13 -2
  42. package/dist/meeting/util.js.map +1 -1
  43. package/dist/meeting-info/meeting-info-v2.js +373 -68
  44. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  45. package/dist/meeting-info/utilv2.js +5 -1
  46. package/dist/meeting-info/utilv2.js.map +1 -1
  47. package/dist/meetings/index.js +136 -1
  48. package/dist/meetings/index.js.map +1 -1
  49. package/dist/meetings/util.js +14 -0
  50. package/dist/meetings/util.js.map +1 -1
  51. package/dist/member/index.js +55 -9
  52. package/dist/member/index.js.map +1 -1
  53. package/dist/member/types.js +3 -0
  54. package/dist/member/types.js.map +1 -1
  55. package/dist/member/util.js +335 -353
  56. package/dist/member/util.js.map +1 -1
  57. package/dist/members/collection.js.map +1 -1
  58. package/dist/members/index.js +137 -29
  59. package/dist/members/index.js.map +1 -1
  60. package/dist/members/request.js +38 -0
  61. package/dist/members/request.js.map +1 -1
  62. package/dist/members/util.js +36 -1
  63. package/dist/members/util.js.map +1 -1
  64. package/dist/metrics/constants.js +10 -0
  65. package/dist/metrics/constants.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +40 -8
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/reachability/clusterReachability.js +63 -27
  69. package/dist/reachability/clusterReachability.js.map +1 -1
  70. package/dist/reachability/index.js +112 -47
  71. package/dist/reachability/index.js.map +1 -1
  72. package/dist/reachability/reachability.types.js +14 -0
  73. package/dist/reachability/reachability.types.js.map +1 -1
  74. package/dist/reachability/request.js +19 -3
  75. package/dist/reachability/request.js.map +1 -1
  76. package/dist/reconnection-manager/index.js +2 -2
  77. package/dist/reconnection-manager/index.js.map +1 -1
  78. package/dist/recording-controller/util.js +5 -5
  79. package/dist/recording-controller/util.js.map +1 -1
  80. package/dist/roap/index.js.map +1 -1
  81. package/dist/roap/turnDiscovery.js +45 -27
  82. package/dist/roap/turnDiscovery.js.map +1 -1
  83. package/dist/roap/types.js +17 -0
  84. package/dist/roap/types.js.map +1 -0
  85. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  86. package/dist/types/config.d.ts +3 -0
  87. package/dist/types/constants.d.ts +16 -85
  88. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  89. package/dist/types/controls-options-manager/types.d.ts +7 -1
  90. package/dist/types/locus-info/index.d.ts +3 -3
  91. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  92. package/dist/types/media/properties.d.ts +15 -0
  93. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  94. package/dist/types/meeting/index.d.ts +65 -1
  95. package/dist/types/meeting/muteState.d.ts +0 -1
  96. package/dist/types/meeting/request.d.ts +12 -1
  97. package/dist/types/meeting/request.type.d.ts +6 -0
  98. package/dist/types/meeting/util.d.ts +3 -1
  99. package/dist/types/meeting-info/meeting-info-v2.d.ts +82 -1
  100. package/dist/types/meetings/index.d.ts +57 -0
  101. package/dist/types/member/index.d.ts +21 -6
  102. package/dist/types/member/types.d.ts +73 -14
  103. package/dist/types/member/util.d.ts +156 -1
  104. package/dist/types/members/collection.d.ts +6 -5
  105. package/dist/types/members/index.d.ts +32 -43
  106. package/dist/types/members/request.d.ts +26 -0
  107. package/dist/types/members/util.d.ts +27 -0
  108. package/dist/types/metrics/constants.d.ts +10 -0
  109. package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
  110. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  111. package/dist/types/reachability/index.d.ts +10 -1
  112. package/dist/types/reachability/reachability.types.d.ts +5 -0
  113. package/dist/types/roap/index.d.ts +3 -2
  114. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  115. package/dist/types/roap/types.d.ts +16 -0
  116. package/dist/webinar/index.js +2 -2
  117. package/dist/webinar/index.js.map +1 -1
  118. package/package.json +24 -23
  119. package/src/breakouts/index.ts +69 -0
  120. package/src/common/errors/webex-errors.ts +8 -1
  121. package/src/config.ts +3 -0
  122. package/src/constants.ts +24 -90
  123. package/src/controls-options-manager/enums.ts +2 -0
  124. package/src/controls-options-manager/types.ts +11 -1
  125. package/src/controls-options-manager/util.ts +62 -0
  126. package/src/interpretation/index.ts +3 -3
  127. package/src/locus-info/controlsUtils.ts +50 -14
  128. package/src/locus-info/index.ts +88 -13
  129. package/src/locus-info/selfUtils.ts +496 -442
  130. package/src/media/index.ts +23 -21
  131. package/src/media/properties.ts +96 -0
  132. package/src/meeting/brbState.ts +11 -2
  133. package/src/meeting/in-meeting-actions.ts +32 -0
  134. package/src/meeting/index.ts +449 -95
  135. package/src/meeting/locusMediaRequest.ts +27 -22
  136. package/src/meeting/muteState.ts +4 -4
  137. package/src/meeting/request.ts +36 -1
  138. package/src/meeting/request.type.ts +7 -0
  139. package/src/meeting/util.ts +11 -2
  140. package/src/meeting-info/meeting-info-v2.ts +254 -8
  141. package/src/meeting-info/utilv2.ts +5 -0
  142. package/src/meetings/index.ts +148 -1
  143. package/src/meetings/util.ts +18 -0
  144. package/src/member/index.ts +68 -22
  145. package/src/member/types.ts +82 -16
  146. package/src/member/util.ts +357 -350
  147. package/src/members/collection.ts +4 -3
  148. package/src/members/index.ts +137 -18
  149. package/src/members/request.ts +44 -0
  150. package/src/members/util.ts +43 -1
  151. package/src/metrics/constants.ts +10 -0
  152. package/src/multistream/remoteMediaManager.ts +32 -10
  153. package/src/reachability/clusterReachability.ts +73 -26
  154. package/src/reachability/index.ts +70 -1
  155. package/src/reachability/reachability.types.ts +6 -0
  156. package/src/reachability/request.ts +7 -0
  157. package/src/reconnection-manager/index.ts +2 -2
  158. package/src/recording-controller/util.ts +17 -13
  159. package/src/roap/index.ts +3 -7
  160. package/src/roap/turnDiscovery.ts +34 -39
  161. package/src/roap/types.ts +23 -0
  162. package/src/webinar/index.ts +1 -1
  163. package/test/unit/spec/breakouts/index.ts +167 -95
  164. package/test/unit/spec/controls-options-manager/util.js +120 -0
  165. package/test/unit/spec/interpretation/index.ts +39 -1
  166. package/test/unit/spec/locus-info/controlsUtils.js +139 -9
  167. package/test/unit/spec/locus-info/index.js +195 -73
  168. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  169. package/test/unit/spec/media/index.ts +150 -18
  170. package/test/unit/spec/media/properties.ts +130 -0
  171. package/test/unit/spec/meeting/brbState.ts +40 -2
  172. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  173. package/test/unit/spec/meeting/index.js +754 -139
  174. package/test/unit/spec/meeting/locusMediaRequest.ts +95 -87
  175. package/test/unit/spec/meeting/muteState.js +73 -2
  176. package/test/unit/spec/meeting/request.js +32 -1
  177. package/test/unit/spec/meeting/utils.js +119 -18
  178. package/test/unit/spec/meeting-info/meetinginfov2.js +484 -114
  179. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  180. package/test/unit/spec/meetings/index.js +146 -2
  181. package/test/unit/spec/member/index.js +7 -0
  182. package/test/unit/spec/member/util.js +24 -0
  183. package/test/unit/spec/members/index.js +304 -78
  184. package/test/unit/spec/members/request.js +68 -22
  185. package/test/unit/spec/members/utils.js +75 -0
  186. package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
  187. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  188. package/test/unit/spec/reachability/index.ts +101 -0
  189. package/test/unit/spec/reachability/request.js +47 -2
  190. package/test/unit/spec/reconnection-manager/index.js +4 -4
  191. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
  192. package/test/unit/spec/webinar/index.ts +5 -0
  193. package/dist/annotation/annotation.types.d.ts +0 -42
  194. package/dist/annotation/constants.d.ts +0 -31
  195. package/dist/annotation/index.d.ts +0 -117
  196. package/dist/breakouts/breakout.d.ts +0 -8
  197. package/dist/breakouts/collection.d.ts +0 -5
  198. package/dist/breakouts/edit-lock-error.d.ts +0 -15
  199. package/dist/breakouts/events.d.ts +0 -8
  200. package/dist/breakouts/index.d.ts +0 -5
  201. package/dist/breakouts/request.d.ts +0 -22
  202. package/dist/breakouts/utils.d.ts +0 -15
  203. package/dist/common/browser-detection.d.ts +0 -9
  204. package/dist/common/collection.d.ts +0 -48
  205. package/dist/common/config.d.ts +0 -2
  206. package/dist/common/errors/captcha-error.d.ts +0 -15
  207. package/dist/common/errors/intent-to-join.d.ts +0 -16
  208. package/dist/common/errors/join-meeting.d.ts +0 -17
  209. package/dist/common/errors/media.d.ts +0 -15
  210. package/dist/common/errors/no-meeting-info.d.ts +0 -14
  211. package/dist/common/errors/parameter.d.ts +0 -15
  212. package/dist/common/errors/password-error.d.ts +0 -15
  213. package/dist/common/errors/permission.d.ts +0 -14
  214. package/dist/common/errors/reclaim-host-role-error.js +0 -149
  215. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  216. package/dist/common/errors/reclaim-host-role-errors.d.ts +0 -60
  217. package/dist/common/errors/reconnection-in-progress.d.ts +0 -9
  218. package/dist/common/errors/reconnection-in-progress.js +0 -33
  219. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  220. package/dist/common/errors/reconnection.d.ts +0 -15
  221. package/dist/common/errors/stats.d.ts +0 -15
  222. package/dist/common/errors/webex-errors.d.ts +0 -93
  223. package/dist/common/errors/webex-meetings-error.d.ts +0 -20
  224. package/dist/common/events/events-scope.d.ts +0 -17
  225. package/dist/common/events/events.d.ts +0 -12
  226. package/dist/common/events/trigger-proxy.d.ts +0 -2
  227. package/dist/common/events/util.d.ts +0 -2
  228. package/dist/common/logs/logger-config.d.ts +0 -2
  229. package/dist/common/logs/logger-proxy.d.ts +0 -2
  230. package/dist/common/logs/request.d.ts +0 -36
  231. package/dist/common/queue.d.ts +0 -34
  232. package/dist/config.d.ts +0 -72
  233. package/dist/constants.d.ts +0 -1088
  234. package/dist/controls-options-manager/constants.d.ts +0 -4
  235. package/dist/controls-options-manager/enums.d.ts +0 -15
  236. package/dist/controls-options-manager/index.d.ts +0 -136
  237. package/dist/controls-options-manager/types.d.ts +0 -43
  238. package/dist/controls-options-manager/util.d.ts +0 -1
  239. package/dist/index.d.ts +0 -7
  240. package/dist/interceptors/index.d.ts +0 -2
  241. package/dist/interceptors/locusRetry.d.ts +0 -27
  242. package/dist/interpretation/collection.d.ts +0 -5
  243. package/dist/interpretation/index.d.ts +0 -5
  244. package/dist/interpretation/siLanguage.d.ts +0 -5
  245. package/dist/locus-info/controlsUtils.d.ts +0 -2
  246. package/dist/locus-info/embeddedAppsUtils.d.ts +0 -2
  247. package/dist/locus-info/fullState.d.ts +0 -2
  248. package/dist/locus-info/hostUtils.d.ts +0 -2
  249. package/dist/locus-info/index.d.ts +0 -322
  250. package/dist/locus-info/infoUtils.d.ts +0 -2
  251. package/dist/locus-info/mediaSharesUtils.d.ts +0 -2
  252. package/dist/locus-info/parser.d.ts +0 -272
  253. package/dist/locus-info/selfUtils.d.ts +0 -2
  254. package/dist/media/index.d.ts +0 -34
  255. package/dist/media/properties.d.ts +0 -93
  256. package/dist/media/util.d.ts +0 -2
  257. package/dist/mediaQualityMetrics/config.d.ts +0 -241
  258. package/dist/mediaQualityMetrics/config.js +0 -502
  259. package/dist/mediaQualityMetrics/config.js.map +0 -1
  260. package/dist/meeting/effectsState.js +0 -260
  261. package/dist/meeting/effectsState.js.map +0 -1
  262. package/dist/meeting/in-meeting-actions.d.ts +0 -167
  263. package/dist/meeting/index.d.ts +0 -1825
  264. package/dist/meeting/locusMediaRequest.d.ts +0 -74
  265. package/dist/meeting/muteState.d.ts +0 -178
  266. package/dist/meeting/request.d.ts +0 -295
  267. package/dist/meeting/request.type.d.ts +0 -11
  268. package/dist/meeting/state.d.ts +0 -9
  269. package/dist/meeting/util.d.ts +0 -119
  270. package/dist/meeting/voicea-meeting.d.ts +0 -16
  271. package/dist/meeting-info/collection.d.ts +0 -20
  272. package/dist/meeting-info/index.d.ts +0 -69
  273. package/dist/meeting-info/meeting-info-v2.d.ts +0 -123
  274. package/dist/meeting-info/request.d.ts +0 -22
  275. package/dist/meeting-info/util.d.ts +0 -2
  276. package/dist/meeting-info/utilv2.d.ts +0 -2
  277. package/dist/meetings/collection.d.ts +0 -40
  278. package/dist/meetings/index.d.ts +0 -390
  279. package/dist/meetings/meetings.types.d.ts +0 -4
  280. package/dist/meetings/request.d.ts +0 -27
  281. package/dist/meetings/util.d.ts +0 -18
  282. package/dist/member/index.d.ts +0 -160
  283. package/dist/member/member.types.js +0 -17
  284. package/dist/member/member.types.js.map +0 -1
  285. package/dist/member/types.d.ts +0 -32
  286. package/dist/member/util.d.ts +0 -2
  287. package/dist/members/collection.d.ts +0 -29
  288. package/dist/members/index.d.ts +0 -353
  289. package/dist/members/request.d.ts +0 -114
  290. package/dist/members/types.d.ts +0 -25
  291. package/dist/members/util.d.ts +0 -215
  292. package/dist/metrics/config.js +0 -276
  293. package/dist/metrics/config.js.map +0 -1
  294. package/dist/metrics/constants.d.ts +0 -70
  295. package/dist/metrics/index.d.ts +0 -45
  296. package/dist/multistream/mediaRequestManager.d.ts +0 -119
  297. package/dist/multistream/receiveSlot.d.ts +0 -68
  298. package/dist/multistream/receiveSlotManager.d.ts +0 -56
  299. package/dist/multistream/remoteMedia.d.ts +0 -72
  300. package/dist/multistream/remoteMediaGroup.d.ts +0 -49
  301. package/dist/multistream/remoteMediaManager.d.ts +0 -300
  302. package/dist/multistream/sendSlotManager.d.ts +0 -69
  303. package/dist/networkQualityMonitor/index.d.ts +0 -70
  304. package/dist/networkQualityMonitor/index.js +0 -221
  305. package/dist/networkQualityMonitor/index.js.map +0 -1
  306. package/dist/peer-connection-manager/index.js +0 -671
  307. package/dist/peer-connection-manager/index.js.map +0 -1
  308. package/dist/peer-connection-manager/util.js +0 -109
  309. package/dist/peer-connection-manager/util.js.map +0 -1
  310. package/dist/personal-meeting-room/index.d.ts +0 -47
  311. package/dist/personal-meeting-room/request.d.ts +0 -14
  312. package/dist/personal-meeting-room/util.d.ts +0 -2
  313. package/dist/reachability/clusterReachability.d.ts +0 -109
  314. package/dist/reachability/index.d.ts +0 -105
  315. package/dist/reachability/request.d.ts +0 -39
  316. package/dist/reachability/util.d.ts +0 -8
  317. package/dist/reactions/constants.d.ts +0 -3
  318. package/dist/reactions/reactions.d.ts +0 -4
  319. package/dist/reactions/reactions.type.d.ts +0 -52
  320. package/dist/reconnection-manager/index.d.ts +0 -136
  321. package/dist/recording-controller/enums.d.ts +0 -7
  322. package/dist/recording-controller/index.d.ts +0 -207
  323. package/dist/recording-controller/util.d.ts +0 -14
  324. package/dist/roap/collection.js +0 -62
  325. package/dist/roap/collection.js.map +0 -1
  326. package/dist/roap/handler.js +0 -275
  327. package/dist/roap/handler.js.map +0 -1
  328. package/dist/roap/index.d.ts +0 -86
  329. package/dist/roap/request.d.ts +0 -39
  330. package/dist/roap/state.js +0 -126
  331. package/dist/roap/state.js.map +0 -1
  332. package/dist/roap/turnDiscovery.d.ts +0 -155
  333. package/dist/roap/util.js +0 -75
  334. package/dist/roap/util.js.map +0 -1
  335. package/dist/rtcMetrics/constants.d.ts +0 -4
  336. package/dist/rtcMetrics/constants.js +0 -11
  337. package/dist/rtcMetrics/constants.js.map +0 -1
  338. package/dist/rtcMetrics/index.d.ts +0 -61
  339. package/dist/rtcMetrics/index.js +0 -197
  340. package/dist/rtcMetrics/index.js.map +0 -1
  341. package/dist/statsAnalyzer/global.d.ts +0 -36
  342. package/dist/statsAnalyzer/global.js +0 -126
  343. package/dist/statsAnalyzer/global.js.map +0 -1
  344. package/dist/statsAnalyzer/index.d.ts +0 -217
  345. package/dist/statsAnalyzer/index.js +0 -1013
  346. package/dist/statsAnalyzer/index.js.map +0 -1
  347. package/dist/statsAnalyzer/mqaUtil.d.ts +0 -48
  348. package/dist/statsAnalyzer/mqaUtil.js +0 -179
  349. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  350. package/dist/transcription/index.d.ts +0 -64
  351. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  352. package/dist/types/mediaQualityMetrics/config.d.ts +0 -241
  353. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  354. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  355. package/dist/types/rtcMetrics/index.d.ts +0 -71
  356. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  357. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  358. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  359. package/dist/webinar/collection.d.ts +0 -16
  360. package/dist/webinar/index.d.ts +0 -5
@@ -1,6 +1,7 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
+ import {v4 as uuidv4} from 'uuid';
4
5
  import 'jsdom-global/register';
5
6
  import {cloneDeep, forEach, isEqual, isUndefined} from 'lodash';
6
7
  import sinon from 'sinon';
@@ -93,13 +94,14 @@ import CaptchaError from '../../../../src/common/errors/captcha-error';
93
94
  import PermissionError from '../../../../src/common/errors/permission';
94
95
  import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
95
96
  import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
96
- import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';;
97
+ import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';
97
98
  import testUtils from '../../../utils/testUtils';
98
99
  import {
99
100
  MeetingInfoV2CaptchaError,
100
101
  MeetingInfoV2PasswordError,
101
102
  MeetingInfoV2PolicyError,
102
- MeetingInfoV2JoinWebinarError, MeetingInfoV2JoinForbiddenError,
103
+ MeetingInfoV2JoinWebinarError,
104
+ MeetingInfoV2JoinForbiddenError,
103
105
  } from '../../../../src/meeting-info/meeting-info-v2';
104
106
  import {
105
107
  DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
@@ -114,8 +116,9 @@ import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagno
114
116
  import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
115
117
 
116
118
  import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
117
- import { createBrbState } from '@webex/plugin-meetings/src/meeting/brbState';
118
- import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
119
+ import {createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
120
+ import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
121
+ import {EventEmitter} from 'stream';
119
122
 
120
123
  describe('plugin-meetings', () => {
121
124
  const logger = {
@@ -208,6 +211,8 @@ describe('plugin-meetings', () => {
208
211
  let membersSpy;
209
212
  let meetingRequestSpy;
210
213
  let correlationId;
214
+ let isoLocalClientMeetingJoinTime;
215
+ let uploadEvent;
211
216
 
212
217
  beforeEach(() => {
213
218
  webex = new MockWebex({
@@ -238,6 +243,7 @@ describe('plugin-meetings', () => {
238
243
  },
239
244
  });
240
245
 
246
+ webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
241
247
  webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
242
248
  webex.internal.services = {get: sinon.stub().returns('locus-url')};
243
249
  webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
@@ -248,6 +254,7 @@ describe('plugin-meetings', () => {
248
254
  getReachabilityResults: sinon.stub().resolves(undefined),
249
255
  getReachabilityMetrics: sinon.stub().resolves({}),
250
256
  stopReachability: sinon.stub(),
257
+ isSubnetReachable: sinon.stub().returns(true),
251
258
  };
252
259
  webex.internal.llm.on = sinon.stub();
253
260
  webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
@@ -277,6 +284,8 @@ describe('plugin-meetings', () => {
277
284
  test4 = `test4-${uuid.v4()}`;
278
285
  testDestination = `testDestination-${uuid.v4()}`;
279
286
  correlationId = uuid.v4();
287
+ uploadEvent = new EventEmitter();
288
+ uploadEvent.addListener('progress', () => {});
280
289
 
281
290
  meeting = new Meeting(
282
291
  {
@@ -605,6 +614,22 @@ describe('plugin-meetings', () => {
605
614
  assert.calledWith(meeting.members.cancelPhoneInvite, uuid1);
606
615
  });
607
616
  });
617
+ describe('#cancelSIPInvite', () => {
618
+ it('should have #cancelSIPInvite', () => {
619
+ assert.exists(meeting.cancelSIPInvite);
620
+ });
621
+ beforeEach(() => {
622
+ meeting.members.cancelSIPInvite = sinon.stub().returns(Promise.resolve(test1));
623
+ });
624
+ it('should proxy members #cancelSIPInvite and return a promise', async () => {
625
+ const cancel = meeting.cancelSIPInvite({memberId: uuid1});
626
+
627
+ assert.exists(cancel.then);
628
+ await cancel;
629
+ assert.calledOnce(meeting.members.cancelSIPInvite);
630
+ assert.calledWith(meeting.members.cancelSIPInvite, {memberId: uuid1});
631
+ });
632
+ });
608
633
  describe('#admit', () => {
609
634
  it('should have #admit', () => {
610
635
  assert.exists(meeting.admit);
@@ -667,7 +692,7 @@ describe('plugin-meetings', () => {
667
692
  beforeEach(() => {
668
693
  meeting.join = sinon.stub().callsFake((joinOptions) => {
669
694
  meeting.isMultistream = joinOptions.enableMultistream;
670
- return Promise.resolve(fakeJoinResult)
695
+ return Promise.resolve(fakeJoinResult);
671
696
  });
672
697
  addMediaInternalStub = sinon
673
698
  .stub(meeting, 'addMediaInternal')
@@ -1070,7 +1095,11 @@ describe('plugin-meetings', () => {
1070
1095
  mediaOptions,
1071
1096
  });
1072
1097
 
1073
- assert.deepEqual(result, {join: fakeJoinResult, media: undefined, multistreamEnabled: false});
1098
+ assert.deepEqual(result, {
1099
+ join: fakeJoinResult,
1100
+ media: undefined,
1101
+ multistreamEnabled: false,
1102
+ });
1074
1103
 
1075
1104
  assert.calledOnce(meeting.join);
1076
1105
 
@@ -1174,7 +1203,10 @@ describe('plugin-meetings', () => {
1174
1203
  type: addMediaError.name,
1175
1204
  }
1176
1205
  );
1177
- assert.calledOnceWithExactly(meeting.leave, {resourceId: undefined, reason: 'joinWithMedia failure'})
1206
+ assert.calledOnceWithExactly(meeting.leave, {
1207
+ resourceId: undefined,
1208
+ reason: 'joinWithMedia failure',
1209
+ });
1178
1210
  });
1179
1211
  });
1180
1212
 
@@ -1191,6 +1223,46 @@ describe('plugin-meetings', () => {
1191
1223
  });
1192
1224
  });
1193
1225
 
1226
+ describe('#update spoken language', () => {
1227
+ beforeEach(() => {
1228
+ webex.internal.voicea.onSpokenLanguageUpdate = sinon.stub();
1229
+ meeting.transcription = {languageOptions: {currentSpokenLanguage: 'en'}};
1230
+ });
1231
+ afterEach(() => {
1232
+ // Restore the original methods after each test
1233
+ sinon.restore();
1234
+ });
1235
+ it('should call voicea.onSpokenLanguageUpdate when joined', async () => {
1236
+
1237
+ meeting.joinedWith = {state: 'JOINED'};
1238
+ await meeting.locusInfo.emitScoped(
1239
+ {function: 'test', file: 'test'},
1240
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
1241
+ {spokenLanguage: 'fr'},
1242
+ );
1243
+ assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'fr');
1244
+ assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, 'fr');
1245
+ assert.calledWith(
1246
+ TriggerProxy.trigger,
1247
+ meeting,
1248
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
1249
+ EVENT_TRIGGERS.MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED
1250
+ );
1251
+ });
1252
+
1253
+ it('should also call voicea.onSpokenLanguageUpdate when not joined', async () => {
1254
+
1255
+ meeting.joinedWith = {state: 'NOT_JOINED'};
1256
+ await meeting.locusInfo.emitScoped(
1257
+ {function: 'test', file: 'test'},
1258
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
1259
+ {spokenLanguage: 'de'},
1260
+ );
1261
+ assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'de');
1262
+ assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, 'de');
1263
+ });
1264
+ });
1265
+
1194
1266
  describe('#startTranscription', () => {
1195
1267
  beforeEach(() => {
1196
1268
  webex.internal.voicea.on = sinon.stub();
@@ -1680,10 +1752,6 @@ describe('plugin-meetings', () => {
1680
1752
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
1681
1753
  });
1682
1754
 
1683
- afterEach(() => {
1684
- assert.exists(meeting.isoLocalClientMeetingJoinTime);
1685
- });
1686
-
1687
1755
  it('should join the meeting and return promise', async () => {
1688
1756
  const join = meeting.join({pstnAudioType: 'dial-in'});
1689
1757
  meeting.config.enableAutomaticLLM = true;
@@ -2035,7 +2103,12 @@ describe('plugin-meetings', () => {
2035
2103
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2036
2104
  meeting.mediaProperties.getCurrentConnectionInfo = sinon
2037
2105
  .stub()
2038
- .resolves({connectionType: 'udp', selectedCandidatePairChanges: 2, numTransports: 1});
2106
+ .resolves({
2107
+ connectionType: 'udp',
2108
+ selectedCandidatePairChanges: 2,
2109
+ numTransports: 1,
2110
+ ipVersion: 'IPv6',
2111
+ });
2039
2112
  meeting.audio = muteStateStub;
2040
2113
  meeting.video = muteStateStub;
2041
2114
  sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
@@ -2098,6 +2171,7 @@ describe('plugin-meetings', () => {
2098
2171
  someReachabilityMetric2: 'some value2',
2099
2172
  }),
2100
2173
  stopReachability: sinon.stub(),
2174
+ isSubnetReachable: sinon.stub().returns(false),
2101
2175
  };
2102
2176
 
2103
2177
  const forceRtcMetricsSend = sinon.stub().resolves();
@@ -2153,6 +2227,8 @@ describe('plugin-meetings', () => {
2153
2227
  someReachabilityMetric1: 'some value1',
2154
2228
  someReachabilityMetric2: 'some value2',
2155
2229
  selectedCandidatePairChanges: 2,
2230
+ isSubnetReachable: null,
2231
+ selectedCluster: null,
2156
2232
  numTransports: 1,
2157
2233
  iceCandidatesCount: 0,
2158
2234
  }
@@ -2199,6 +2275,8 @@ describe('plugin-meetings', () => {
2199
2275
  signalingState: 'unknown',
2200
2276
  connectionState: 'unknown',
2201
2277
  iceConnectionState: 'unknown',
2278
+ isSubnetReachable: null,
2279
+ selectedCluster: null,
2202
2280
  })
2203
2281
  );
2204
2282
 
@@ -2213,6 +2291,7 @@ describe('plugin-meetings', () => {
2213
2291
  someReachabilityMetric1: 'some value1',
2214
2292
  someReachabilityMetric2: 'some value2',
2215
2293
  }),
2294
+ isSubnetReachable: sinon.stub().returns(true),
2216
2295
  };
2217
2296
 
2218
2297
  meeting.waitForRemoteSDPAnswer = sinon.stub().rejects();
@@ -2263,6 +2342,8 @@ describe('plugin-meetings', () => {
2263
2342
  selectedCandidatePairChanges: 2,
2264
2343
  numTransports: 1,
2265
2344
  iceCandidatesCount: 0,
2345
+ isSubnetReachable: null,
2346
+ selectedCluster: null,
2266
2347
  }
2267
2348
  );
2268
2349
  });
@@ -2320,6 +2401,8 @@ describe('plugin-meetings', () => {
2320
2401
  signalingState: 'have-local-offer',
2321
2402
  connectionState: 'connecting',
2322
2403
  iceConnectionState: 'checking',
2404
+ isSubnetReachable: null,
2405
+ selectedCluster: null,
2323
2406
  })
2324
2407
  );
2325
2408
 
@@ -2377,6 +2460,8 @@ describe('plugin-meetings', () => {
2377
2460
  signalingState: 'have-local-offer',
2378
2461
  connectionState: 'connecting',
2379
2462
  iceConnectionState: 'checking',
2463
+ isSubnetReachable: null,
2464
+ selectedCluster: null,
2380
2465
  })
2381
2466
  );
2382
2467
 
@@ -2655,7 +2740,7 @@ describe('plugin-meetings', () => {
2655
2740
 
2656
2741
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
2657
2742
  turnServerInfo: {
2658
- url: FAKE_TURN_URL,
2743
+ urls: [FAKE_TURN_URL],
2659
2744
  username: FAKE_TURN_USER,
2660
2745
  password: FAKE_TURN_PASSWORD,
2661
2746
  },
@@ -2677,7 +2762,7 @@ describe('plugin-meetings', () => {
2677
2762
  meeting.id,
2678
2763
  sinon.match({
2679
2764
  turnServerInfo: {
2680
- url: FAKE_TURN_URL,
2765
+ urls: [FAKE_TURN_URL],
2681
2766
  username: FAKE_TURN_USER,
2682
2767
  password: FAKE_TURN_PASSWORD,
2683
2768
  },
@@ -2712,8 +2797,9 @@ describe('plugin-meetings', () => {
2712
2797
  sinon.stub().returns(FAKE_ERROR));
2713
2798
  webex.meetings.reachability = {
2714
2799
  isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
2715
- getReachabilityMetrics: sinon.stub().resolves(),
2800
+ getReachabilityMetrics: sinon.stub().resolves({}),
2716
2801
  stopReachability: sinon.stub(),
2802
+ isSubnetReachable: sinon.stub().returns(true),
2717
2803
  };
2718
2804
  const MOCK_CLIENT_ERROR_CODE = 2004;
2719
2805
  const generateClientErrorCodeForIceFailureStub = sinon
@@ -2735,14 +2821,15 @@ describe('plugin-meetings', () => {
2735
2821
  .onSecondCall()
2736
2822
  .returns({
2737
2823
  turnServerInfo: {
2738
- url: FAKE_TURN_URL,
2824
+ urls: [FAKE_TURN_URL],
2739
2825
  username: FAKE_TURN_USER,
2740
2826
  password: FAKE_TURN_PASSWORD,
2741
2827
  },
2742
2828
  turnDiscoverySkippedReason: undefined,
2743
2829
  });
2744
2830
  meeting.meetingState = 'ACTIVE';
2745
- meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
2831
+ const error = {iceConnected: false};
2832
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects(error);
2746
2833
 
2747
2834
  const forceRtcMetricsSend = sinon.stub().resolves();
2748
2835
  const closeMediaConnectionStub = sinon.stub();
@@ -2760,6 +2847,7 @@ describe('plugin-meetings', () => {
2760
2847
  })
2761
2848
  .catch((err) => {
2762
2849
  errorThrown = err;
2850
+ assert.instanceOf(err.cause, Error);
2763
2851
  assert.instanceOf(err, AddMediaFailed);
2764
2852
  });
2765
2853
 
@@ -2816,6 +2904,7 @@ describe('plugin-meetings', () => {
2816
2904
  },
2817
2905
  options: {
2818
2906
  meetingId: meeting.id,
2907
+ rawError: error,
2819
2908
  },
2820
2909
  });
2821
2910
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
@@ -2827,6 +2916,7 @@ describe('plugin-meetings', () => {
2827
2916
  },
2828
2917
  options: {
2829
2918
  meetingId: meeting.id,
2919
+ rawError: error,
2830
2920
  },
2831
2921
  });
2832
2922
 
@@ -2893,6 +2983,8 @@ describe('plugin-meetings', () => {
2893
2983
  selectedCandidatePairChanges: 2,
2894
2984
  numTransports: 1,
2895
2985
  iceCandidatesCount: 0,
2986
+ isSubnetReachable: null,
2987
+ selectedCluster: null,
2896
2988
  },
2897
2989
  ]);
2898
2990
 
@@ -2923,6 +3015,7 @@ describe('plugin-meetings', () => {
2923
3015
  .resolves(false),
2924
3016
  getReachabilityMetrics: sinon.stub().resolves({}),
2925
3017
  stopReachability: sinon.stub(),
3018
+ isSubnetReachable: sinon.stub().returns(true),
2926
3019
  };
2927
3020
  const getErrorPayloadForClientErrorCodeStub =
2928
3021
  (webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
@@ -2947,16 +3040,19 @@ describe('plugin-meetings', () => {
2947
3040
  .onSecondCall()
2948
3041
  .returns({
2949
3042
  turnServerInfo: {
2950
- url: FAKE_TURN_URL,
3043
+ urls: [FAKE_TURN_URL],
2951
3044
  username: FAKE_TURN_USER,
2952
3045
  password: FAKE_TURN_PASSWORD,
2953
3046
  },
2954
3047
  turnDiscoverySkippedReason: undefined,
2955
3048
  });
3049
+
3050
+ const mediaConnectionError = new Error('fake error');
3051
+
2956
3052
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon
2957
3053
  .stub()
2958
3054
  .onFirstCall()
2959
- .rejects()
3055
+ .rejects(mediaConnectionError)
2960
3056
  .onSecondCall()
2961
3057
  .resolves();
2962
3058
 
@@ -3025,10 +3121,14 @@ describe('plugin-meetings', () => {
3025
3121
  },
3026
3122
  options: {
3027
3123
  meetingId: meeting.id,
3124
+ rawError: mediaConnectionError,
3028
3125
  },
3029
3126
  });
3030
3127
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
3031
3128
  name: 'client.media-engine.ready',
3129
+ payload: {
3130
+ ipVersion: 'IPv6',
3131
+ },
3032
3132
  options: {
3033
3133
  meetingId: meeting.id,
3034
3134
  },
@@ -3085,11 +3185,14 @@ describe('plugin-meetings', () => {
3085
3185
  locus_id: meeting.locusUrl.split('/').pop(),
3086
3186
  connectionType: 'udp',
3087
3187
  selectedCandidatePairChanges: 2,
3188
+ ipVersion: 'IPv6',
3088
3189
  numTransports: 1,
3089
3190
  isMultistream: false,
3090
3191
  retriedWithTurnServer: true,
3091
3192
  isJoinWithMediaRetry: false,
3092
3193
  iceCandidatesCount: 0,
3194
+ isSubnetReachable: null,
3195
+ selectedCluster: null,
3093
3196
  },
3094
3197
  ]);
3095
3198
  meeting.roap.doTurnDiscovery;
@@ -3124,7 +3227,7 @@ describe('plugin-meetings', () => {
3124
3227
  .onSecondCall()
3125
3228
  .returns({
3126
3229
  turnServerInfo: {
3127
- url: FAKE_TURN_URL,
3230
+ urls: [FAKE_TURN_URL],
3128
3231
  username: FAKE_TURN_USER,
3129
3232
  password: FAKE_TURN_PASSWORD,
3130
3233
  },
@@ -3176,7 +3279,7 @@ describe('plugin-meetings', () => {
3176
3279
  .onSecondCall()
3177
3280
  .returns({
3178
3281
  turnServerInfo: {
3179
- url: FAKE_TURN_URL,
3282
+ urls: [FAKE_TURN_URL],
3180
3283
  username: FAKE_TURN_USER,
3181
3284
  password: FAKE_TURN_PASSWORD,
3182
3285
  },
@@ -3218,7 +3321,13 @@ describe('plugin-meetings', () => {
3218
3321
  someReachabilityMetric2: 'some value2',
3219
3322
  }),
3220
3323
  stopReachability: sinon.stub(),
3324
+ isSubnetReachable: sinon.stub().returns(true),
3221
3325
  };
3326
+ meeting.mediaConnections = [
3327
+ {
3328
+ mediaAgentCluster: 'some.cluster',
3329
+ }
3330
+ ]
3222
3331
  meeting.iceCandidatesCount = 3;
3223
3332
  meeting.iceCandidateErrors.set('701_error', 3);
3224
3333
  meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
@@ -3236,6 +3345,7 @@ describe('plugin-meetings', () => {
3236
3345
  locus_id: meeting.locusUrl.split('/').pop(),
3237
3346
  connectionType: 'udp',
3238
3347
  selectedCandidatePairChanges: 2,
3348
+ ipVersion: 'IPv6',
3239
3349
  numTransports: 1,
3240
3350
  isMultistream: false,
3241
3351
  retriedWithTurnServer: false,
@@ -3245,6 +3355,8 @@ describe('plugin-meetings', () => {
3245
3355
  iceCandidatesCount: 3,
3246
3356
  '701_error': 3,
3247
3357
  '701_turn_host_lookup_received_error': 1,
3358
+ isSubnetReachable: null,
3359
+ selectedCluster: 'some.cluster',
3248
3360
  }
3249
3361
  );
3250
3362
 
@@ -3307,6 +3419,8 @@ describe('plugin-meetings', () => {
3307
3419
  iceConnectionState: 'unknown',
3308
3420
  selectedCandidatePairChanges: 2,
3309
3421
  numTransports: 1,
3422
+ isSubnetReachable: null,
3423
+ selectedCluster: null,
3310
3424
  iceCandidatesCount: 0,
3311
3425
  }
3312
3426
  );
@@ -3368,6 +3482,120 @@ describe('plugin-meetings', () => {
3368
3482
  numTransports: 1,
3369
3483
  '701_error': 2,
3370
3484
  '701_turn_host_lookup_received_error': 1,
3485
+ isSubnetReachable: null,
3486
+ selectedCluster: null,
3487
+ iceCandidatesCount: 0,
3488
+ }
3489
+ );
3490
+
3491
+ assert.isOk(errorThrown);
3492
+ });
3493
+
3494
+ it('should send valid isSubnetReachability if media connection success', async () => {
3495
+ meeting.roap.doTurnDiscovery = sinon.stub().returns({
3496
+ turnServerInfo: undefined,
3497
+ turnDiscoverySkippedReason: undefined,
3498
+ });
3499
+ meeting.meetingState = 'ACTIVE';
3500
+ meeting.mediaProperties.waitForMediaConnectionConnected.resolves();
3501
+ meeting.webex.meetings.reachability = {
3502
+ getReachabilityMetrics: sinon.stub().resolves({
3503
+ reachability_public_udp_success: 5,
3504
+ }),
3505
+ stopReachability: sinon.stub(),
3506
+ isSubnetReachable: sinon.stub().returns(false),
3507
+ };
3508
+
3509
+ const forceRtcMetricsSend = sinon.stub().resolves();
3510
+ const closeMediaConnectionStub = sinon.stub();
3511
+ Media.createMediaConnection = sinon.stub().returns({
3512
+ close: closeMediaConnectionStub,
3513
+ forceRtcMetricsSend,
3514
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
3515
+ initiateOffer: sinon.stub().resolves({}),
3516
+ on: sinon.stub(),
3517
+ });
3518
+
3519
+ await meeting.addMedia({
3520
+ mediaSettings: {},
3521
+ });
3522
+
3523
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
3524
+ correlation_id: meeting.correlationId,
3525
+ locus_id: meeting.locusUrl.split('/').pop(),
3526
+ connectionType: 'udp',
3527
+ ipVersion: 'IPv6',
3528
+ selectedCandidatePairChanges: 2,
3529
+ numTransports: 1,
3530
+ isMultistream: false,
3531
+ retriedWithTurnServer: false,
3532
+ isJoinWithMediaRetry: false,
3533
+ iceCandidatesCount: 0,
3534
+ reachability_public_udp_success: 5,
3535
+ isSubnetReachable: false,
3536
+ selectedCluster: null,
3537
+ });
3538
+ });
3539
+
3540
+ it('should send valid isSubnetReachability if media connection fails', async () => {
3541
+ let errorThrown = undefined;
3542
+
3543
+ meeting.roap.doTurnDiscovery = sinon.stub().returns({
3544
+ turnServerInfo: undefined,
3545
+ turnDiscoverySkippedReason: undefined,
3546
+ });
3547
+ meeting.meetingState = 'ACTIVE';
3548
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
3549
+ meeting.webex.meetings.reachability = {
3550
+ getReachabilityMetrics: sinon.stub().resolves({
3551
+ reachability_public_udp_success: 5,
3552
+ }),
3553
+ stopReachability: sinon.stub(),
3554
+ isSubnetReachable: sinon.stub().returns(true),
3555
+ };
3556
+
3557
+ const forceRtcMetricsSend = sinon.stub().resolves();
3558
+ const closeMediaConnectionStub = sinon.stub();
3559
+ Media.createMediaConnection = sinon.stub().returns({
3560
+ close: closeMediaConnectionStub,
3561
+ forceRtcMetricsSend,
3562
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
3563
+ initiateOffer: sinon.stub().resolves({}),
3564
+ on: sinon.stub(),
3565
+ });
3566
+
3567
+ await meeting
3568
+ .addMedia({
3569
+ mediaSettings: {},
3570
+ })
3571
+ .catch((err) => {
3572
+ errorThrown = err;
3573
+ assert.instanceOf(err, AddMediaFailed);
3574
+ });
3575
+
3576
+ // Check that the only metric sent is ADD_MEDIA_FAILURE
3577
+ assert.calledOnceWithExactly(
3578
+ Metrics.sendBehavioralMetric,
3579
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
3580
+ {
3581
+ correlation_id: meeting.correlationId,
3582
+ locus_id: meeting.locusUrl.split('/').pop(),
3583
+ reason: errorThrown.message,
3584
+ stack: errorThrown.stack,
3585
+ code: errorThrown.code,
3586
+ turnDiscoverySkippedReason: undefined,
3587
+ turnServerUsed: true,
3588
+ retriedWithTurnServer: false,
3589
+ isMultistream: false,
3590
+ isJoinWithMediaRetry: false,
3591
+ signalingState: 'unknown',
3592
+ connectionState: 'unknown',
3593
+ iceConnectionState: 'unknown',
3594
+ selectedCandidatePairChanges: 2,
3595
+ numTransports: 1,
3596
+ reachability_public_udp_success: 5,
3597
+ isSubnetReachable: true,
3598
+ selectedCluster: null,
3371
3599
  iceCandidatesCount: 0,
3372
3600
  }
3373
3601
  );
@@ -3387,6 +3615,8 @@ describe('plugin-meetings', () => {
3387
3615
  meeting.config.stats.enableStatsAnalyzer = true;
3388
3616
 
3389
3617
  statsAnalyzerStub = new EventsScope();
3618
+ statsAnalyzerStub.getNetworkType = sinon.stub().returns('wifi');
3619
+
3390
3620
  // mock the StatsAnalyzer constructor
3391
3621
  sinon.stub(InternalMediaCoreModule, 'StatsAnalyzer').returns(statsAnalyzerStub);
3392
3622
 
@@ -3427,6 +3657,40 @@ describe('plugin-meetings', () => {
3427
3657
  });
3428
3658
  });
3429
3659
 
3660
+ it('LOCAL_MEDIA_STARTED triggers "meeting:media:local:start" event and does not send metric because we already have', async () => {
3661
+ meeting.shareCAEventSentStatus = {
3662
+ transmitStart: true,
3663
+ transmitStop: false,
3664
+ receiveStart: false,
3665
+ receiveStop: false,
3666
+ };
3667
+ statsAnalyzerStub.emit(
3668
+ {file: 'test', function: 'test'},
3669
+ StatsAnalyzerEventNames.LOCAL_MEDIA_STARTED,
3670
+ {mediaType: 'share'}
3671
+ );
3672
+
3673
+ assert.calledWith(
3674
+ TriggerProxy.trigger,
3675
+ sinon.match.instanceOf(Meeting),
3676
+ {
3677
+ file: 'meeting/index',
3678
+ function: 'addMedia',
3679
+ },
3680
+ EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
3681
+ {
3682
+ mediaType: 'share',
3683
+ }
3684
+ );
3685
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3686
+ name: 'client.media.tx.start',
3687
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3688
+ options: {
3689
+ meetingId: meeting.id,
3690
+ },
3691
+ });
3692
+ });
3693
+
3430
3694
  it('LOCAL_MEDIA_STOPPED triggers the right metrics', async () => {
3431
3695
  statsAnalyzerStub.emit(
3432
3696
  {file: 'test', function: 'test'},
@@ -3443,6 +3707,28 @@ describe('plugin-meetings', () => {
3443
3707
  });
3444
3708
  });
3445
3709
 
3710
+ it('LOCAL_MEDIA_STOPPED does not send metric because we already have', async () => {
3711
+ meeting.shareCAEventSentStatus = {
3712
+ transmitStart: false,
3713
+ transmitStop: true,
3714
+ receiveStart: false,
3715
+ receiveStop: false,
3716
+ };
3717
+ statsAnalyzerStub.emit(
3718
+ {file: 'test', function: 'test'},
3719
+ StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED,
3720
+ {mediaType: 'share'}
3721
+ );
3722
+
3723
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3724
+ name: 'client.media.tx.stop',
3725
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3726
+ options: {
3727
+ meetingId: meeting.id,
3728
+ },
3729
+ });
3730
+ });
3731
+
3446
3732
  it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics', async () => {
3447
3733
  statsAnalyzerStub.emit(
3448
3734
  {file: 'test', function: 'test'},
@@ -3523,6 +3809,47 @@ describe('plugin-meetings', () => {
3523
3809
  });
3524
3810
  });
3525
3811
 
3812
+ it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and does not send metric because we already have', async () => {
3813
+ meeting.shareCAEventSentStatus = {
3814
+ transmitStart: false,
3815
+ transmitStop: false,
3816
+ receiveStart: true,
3817
+ receiveStop: false,
3818
+ };
3819
+ statsAnalyzerStub.emit(
3820
+ {file: 'test', function: 'test'},
3821
+ StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
3822
+ {mediaType: 'share'}
3823
+ );
3824
+
3825
+ assert.calledWith(
3826
+ TriggerProxy.trigger,
3827
+ sinon.match.instanceOf(Meeting),
3828
+ {
3829
+ file: 'meeting/index',
3830
+ function: 'addMedia',
3831
+ },
3832
+ EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
3833
+ {
3834
+ mediaType: 'share',
3835
+ }
3836
+ );
3837
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3838
+ name: 'client.media.render.start',
3839
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3840
+ options: {
3841
+ meetingId: meeting.id,
3842
+ },
3843
+ });
3844
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3845
+ name: 'client.media.rx.start',
3846
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3847
+ options: {
3848
+ meetingId: meeting.id,
3849
+ },
3850
+ });
3851
+ });
3852
+
3526
3853
  it('REMOTE_MEDIA_STOPPED triggers the right metrics for share', async () => {
3527
3854
  statsAnalyzerStub.emit(
3528
3855
  {file: 'test', function: 'test'},
@@ -3547,21 +3874,49 @@ describe('plugin-meetings', () => {
3547
3874
  });
3548
3875
  });
3549
3876
 
3877
+ it('REMOTE_MEDIA_STOPPED does not send metric because we already have', async () => {
3878
+ meeting.shareCAEventSentStatus = {
3879
+ transmitStart: false,
3880
+ transmitStop: false,
3881
+ receiveStart: true,
3882
+ receiveStop: true,
3883
+ };
3884
+ statsAnalyzerStub.emit(
3885
+ {file: 'test', function: 'test'},
3886
+ StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
3887
+ {mediaType: 'share'}
3888
+ );
3889
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3890
+ name: 'client.media.render.stop',
3891
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3892
+ options: {
3893
+ meetingId: meeting.id,
3894
+ },
3895
+ });
3896
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3897
+ name: 'client.media.rx.stop',
3898
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3899
+ options: {
3900
+ meetingId: meeting.id,
3901
+ },
3902
+ });
3903
+ });
3904
+
3550
3905
  it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
3551
3906
  let fakeMembersCollection = {
3552
3907
  members: {
3553
- member1: { isInMeeting: true },
3554
- member2: { isInMeeting: true },
3555
- member3: { isInMeeting: false },
3908
+ member1: {isInMeeting: true},
3909
+ member2: {isInMeeting: true},
3910
+ member3: {isInMeeting: false},
3556
3911
  },
3557
3912
  };
3558
- sinon.stub(meeting, 'getMembers').returns({ membersCollection: fakeMembersCollection });
3559
- const fakeData = { intervalMetadata: {}, networkType: 'wifi' };
3913
+ sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
3914
+ const fakeData = {intervalMetadata: {}};
3560
3915
 
3561
3916
  statsAnalyzerStub.emit(
3562
- { file: 'test', function: 'test' },
3917
+ {file: 'test', function: 'test'},
3563
3918
  StatsAnalyzerEventNames.MEDIA_QUALITY,
3564
- { data: fakeData }
3919
+ {data: fakeData}
3565
3920
  );
3566
3921
 
3567
3922
  assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
@@ -3570,15 +3925,17 @@ describe('plugin-meetings', () => {
3570
3925
  meetingId: meeting.id,
3571
3926
  },
3572
3927
  payload: {
3573
- intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2))],
3928
+ intervals: [
3929
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
3930
+ ],
3574
3931
  },
3575
3932
  });
3576
3933
  fakeMembersCollection.members.member2.isInMeeting = false;
3577
3934
 
3578
3935
  statsAnalyzerStub.emit(
3579
- { file: 'test', function: 'test' },
3936
+ {file: 'test', function: 'test'},
3580
3937
  StatsAnalyzerEventNames.MEDIA_QUALITY,
3581
- { data: fakeData }
3938
+ {data: fakeData}
3582
3939
  );
3583
3940
 
3584
3941
  assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
@@ -3587,13 +3944,15 @@ describe('plugin-meetings', () => {
3587
3944
  meetingId: meeting.id,
3588
3945
  },
3589
3946
  payload: {
3590
- intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1))],
3947
+ intervals: [
3948
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1)),
3949
+ ],
3591
3950
  },
3592
3951
  });
3593
3952
  });
3594
3953
 
3595
3954
  it('calls submitMQE correctly', async () => {
3596
- const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
3955
+ const fakeData = {intervalMetadata: {bla: 'bla'}};
3597
3956
 
3598
3957
  statsAnalyzerStub.emit(
3599
3958
  {file: 'test', function: 'test'},
@@ -3624,7 +3983,7 @@ describe('plugin-meetings', () => {
3624
3983
 
3625
3984
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3626
3985
  turnServerInfo: {
3627
- url: FAKE_TURN_URL,
3986
+ urls: [FAKE_TURN_URL],
3628
3987
  username: FAKE_TURN_USER,
3629
3988
  password: FAKE_TURN_PASSWORD,
3630
3989
  },
@@ -3650,7 +4009,7 @@ describe('plugin-meetings', () => {
3650
4009
  meeting.id,
3651
4010
  sinon.match({
3652
4011
  turnServerInfo: {
3653
- url: FAKE_TURN_URL,
4012
+ urls: [FAKE_TURN_URL],
3654
4013
  username: FAKE_TURN_USER,
3655
4014
  password: FAKE_TURN_PASSWORD,
3656
4015
  },
@@ -3801,6 +4160,9 @@ describe('plugin-meetings', () => {
3801
4160
  },
3802
4161
  options: {
3803
4162
  meetingId: meeting.id,
4163
+ rawError: {
4164
+ iceConnected: false,
4165
+ },
3804
4166
  },
3805
4167
  },
3806
4168
  ]);
@@ -3830,7 +4192,7 @@ describe('plugin-meetings', () => {
3830
4192
  meeting.deviceUrl = 'device url';
3831
4193
  meeting.selfId = 'self id';
3832
4194
  meeting.brbState = createBrbState(meeting, false);
3833
- meeting.brbState.enable = sinon.stub().resolves();
4195
+ sinon.stub(meeting.brbState, 'enable').resolves();
3834
4196
  });
3835
4197
 
3836
4198
  afterEach(() => {
@@ -3842,7 +4204,6 @@ describe('plugin-meetings', () => {
3842
4204
  });
3843
4205
 
3844
4206
  describe('when in a multistream meeting', () => {
3845
-
3846
4207
  beforeEach(() => {
3847
4208
  meeting.isMultistream = true;
3848
4209
  });
@@ -3853,7 +4214,7 @@ describe('plugin-meetings', () => {
3853
4214
  await brbResult;
3854
4215
  assert.exists(brbResult.then);
3855
4216
  assert.calledOnce(meeting.brbState.enable);
3856
- })
4217
+ });
3857
4218
 
3858
4219
  it('should disable #beRightBack and return a promise', async () => {
3859
4220
  const brbResult = meeting.beRightBack(false);
@@ -3861,7 +4222,7 @@ describe('plugin-meetings', () => {
3861
4222
  await brbResult;
3862
4223
  assert.exists(brbResult.then);
3863
4224
  assert.calledOnce(meeting.brbState.enable);
3864
- })
4225
+ });
3865
4226
 
3866
4227
  it('should throw an error and reject the promise if setBrb fails', async () => {
3867
4228
  const error = new Error('setBrb failed');
@@ -3872,9 +4233,42 @@ describe('plugin-meetings', () => {
3872
4233
  } catch (err) {
3873
4234
  assert.instanceOf(err, Error);
3874
4235
  assert.equal(err.message, 'setBrb failed');
3875
- assert.isRejected((Promise.reject()));
4236
+ assert.isRejected(Promise.reject());
3876
4237
  }
3877
- })
4238
+ });
4239
+
4240
+ it('updates remote mute state when brb is enabled', async () => {
4241
+ meeting.audio = {handleServerRemoteMuteUpdate: sinon.stub()};
4242
+
4243
+ await meeting.beRightBack(true);
4244
+
4245
+ sinon.assert.calledOnceWithExactly(
4246
+ meeting.audio.handleServerRemoteMuteUpdate,
4247
+ meeting,
4248
+ true
4249
+ );
4250
+ });
4251
+
4252
+ it('does not update remote mute state when brb is disabled', async () => {
4253
+ meeting.audio = {handleServerRemoteMuteUpdate: sinon.stub()};
4254
+
4255
+ await meeting.beRightBack(false);
4256
+
4257
+ assert.notCalled(meeting.audio.handleServerRemoteMuteUpdate);
4258
+ });
4259
+
4260
+ it('should reject when brb enable fails', async () => {
4261
+ meeting.brbState.enable.restore();
4262
+
4263
+ const error = new Error();
4264
+ meeting.meetingRequest.setBrb = sinon.stub().rejects(error);
4265
+
4266
+ await expect(
4267
+ meeting.beRightBack(true)
4268
+ ).to.be.rejectedWith(error);
4269
+
4270
+ assert.isFalse(meeting.brbState.state.syncToServerInProgress);
4271
+ });
3878
4272
  });
3879
4273
  });
3880
4274
 
@@ -3928,7 +4322,10 @@ describe('plugin-meetings', () => {
3928
4322
  .resolves({id: 'fake clientMediaPreferences'});
3929
4323
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3930
4324
  turnServerInfo: {
3931
- url: 'turns:turn-server-url:443?transport=tcp',
4325
+ urls: [
4326
+ 'turns:turn-server-url1:443?transport=tcp',
4327
+ 'turns:turn-server-url2:443?transport=tcp',
4328
+ ],
3932
4329
  username: 'turn user',
3933
4330
  password: 'turn password',
3934
4331
  },
@@ -3946,12 +4343,10 @@ describe('plugin-meetings', () => {
3946
4343
  expectedMediaConnectionConfig = {
3947
4344
  iceServers: [
3948
4345
  {
3949
- urls: 'turn:turn-server-url:5004?transport=tcp',
3950
- username: 'turn user',
3951
- credential: 'turn password',
3952
- },
3953
- {
3954
- urls: 'turns:turn-server-url:443?transport=tcp',
4346
+ urls: [
4347
+ 'turns:turn-server-url1:443?transport=tcp',
4348
+ 'turns:turn-server-url2:443?transport=tcp',
4349
+ ],
3955
4350
  username: 'turn user',
3956
4351
  credential: 'turn password',
3957
4352
  },
@@ -4006,7 +4401,7 @@ describe('plugin-meetings', () => {
4006
4401
  initiateOffer: sinon.stub().resolves({}),
4007
4402
  update: sinon.stub().resolves({}),
4008
4403
  on: sinon.stub(),
4009
- roapMessageReceived: sinon.stub()
4404
+ roapMessageReceived: sinon.stub(),
4010
4405
  };
4011
4406
 
4012
4407
  fakeMultistreamRoapMediaConnection = {
@@ -4033,9 +4428,11 @@ describe('plugin-meetings', () => {
4033
4428
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
4034
4429
  .returns(fakeMultistreamRoapMediaConnection);
4035
4430
 
4036
- locusMediaRequestStub = sinon
4037
- .stub(WebexPlugin.prototype, 'request')
4038
- .resolves({body: {locus: {fullState: {}}}});
4431
+ locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({
4432
+ body: {locus: {fullState: {}}},
4433
+ upload: sinon.match.instanceOf(EventEmitter),
4434
+ download: sinon.match.instanceOf(EventEmitter),
4435
+ });
4039
4436
 
4040
4437
  // setup some things and mocks so that the call to join() works
4041
4438
  // (we need to call join() because it creates the LocusMediaRequest instance
@@ -4144,6 +4541,8 @@ describe('plugin-meetings', () => {
4144
4541
  id: 'fake clientMediaPreferences',
4145
4542
  },
4146
4543
  },
4544
+ upload: sinon.match.instanceOf(EventEmitter),
4545
+ download: sinon.match.instanceOf(EventEmitter),
4147
4546
  });
4148
4547
  };
4149
4548
 
@@ -4171,6 +4570,8 @@ describe('plugin-meetings', () => {
4171
4570
  },
4172
4571
  ],
4173
4572
  },
4573
+ upload: sinon.match.instanceOf(EventEmitter),
4574
+ download: sinon.match.instanceOf(EventEmitter),
4174
4575
  });
4175
4576
  };
4176
4577
 
@@ -4195,6 +4596,8 @@ describe('plugin-meetings', () => {
4195
4596
  respOnlySdp: true,
4196
4597
  usingResource: null,
4197
4598
  },
4599
+ upload: sinon.match.instanceOf(EventEmitter),
4600
+ download: sinon.match.instanceOf(EventEmitter),
4198
4601
  });
4199
4602
  };
4200
4603
 
@@ -5213,7 +5616,10 @@ describe('plugin-meetings', () => {
5213
5616
  // and check that when we fallback to transcoded we still do another TURN discovery
5214
5617
  await runCheck(
5215
5618
  {
5216
- url: 'turns:turn-server-url:443?transport=tcp',
5619
+ urls: [
5620
+ 'turns:turn-server-url1:443?transport=tcp',
5621
+ 'turns:turn-server-url2:443?transport=tcp',
5622
+ ],
5217
5623
  username: 'turn user',
5218
5624
  password: 'turn password',
5219
5625
  },
@@ -5227,7 +5633,10 @@ describe('plugin-meetings', () => {
5227
5633
  // but doing it just for completeness
5228
5634
  await runCheck(
5229
5635
  {
5230
- url: 'turns:turn-server-url:443?transport=tcp',
5636
+ urls: [
5637
+ 'turns:turn-server-url1:443?transport=tcp',
5638
+ 'turns:turn-server-url2:443?transport=tcp',
5639
+ ],
5231
5640
  username: 'turn user',
5232
5641
  password: 'turn password',
5233
5642
  },
@@ -6337,7 +6746,10 @@ describe('plugin-meetings', () => {
6337
6746
  .throws(new MeetingInfoV2JoinForbiddenError(403003, FAKE_MEETING_INFO)),
6338
6747
  };
6339
6748
 
6340
- await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinForbiddenError);
6749
+ await assert.isRejected(
6750
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6751
+ JoinForbiddenError
6752
+ );
6341
6753
 
6342
6754
  assert.calledWith(
6343
6755
  meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
@@ -6353,10 +6765,7 @@ describe('plugin-meetings', () => {
6353
6765
 
6354
6766
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6355
6767
  assert.equal(meeting.meetingInfoFailureCode, 403003);
6356
- assert.equal(
6357
- meeting.meetingInfoFailureReason,
6358
- MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH
6359
- );
6768
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH);
6360
6769
  assert.equal(meeting.requiredCaptcha, null);
6361
6770
  });
6362
6771
 
@@ -6733,15 +7142,10 @@ describe('plugin-meetings', () => {
6733
7142
  meeting.attrs.meetingInfoProvider = {
6734
7143
  fetchMeetingInfo: sinon
6735
7144
  .stub()
6736
- .throws(
6737
- new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
6738
- ),
7145
+ .throws(new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')),
6739
7146
  };
6740
7147
 
6741
- await assert.isRejected(
6742
- meeting.fetchMeetingInfo({sendCAevents: true}),
6743
- JoinWebinarError
6744
- );
7148
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6745
7149
 
6746
7150
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6747
7151
  assert.equal(
@@ -6756,15 +7160,10 @@ describe('plugin-meetings', () => {
6756
7160
  meeting.attrs.meetingInfoProvider = {
6757
7161
  fetchMeetingInfo: sinon
6758
7162
  .stub()
6759
- .throws(
6760
- new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
6761
- ),
7163
+ .throws(new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')),
6762
7164
  };
6763
7165
 
6764
- await assert.isRejected(
6765
- meeting.fetchMeetingInfo({sendCAevents: true}),
6766
- JoinWebinarError
6767
- );
7166
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6768
7167
 
6769
7168
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6770
7169
  assert.equal(
@@ -6779,15 +7178,10 @@ describe('plugin-meetings', () => {
6779
7178
  meeting.attrs.meetingInfoProvider = {
6780
7179
  fetchMeetingInfo: sinon
6781
7180
  .stub()
6782
- .throws(
6783
- new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
6784
- ),
7181
+ .throws(new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')),
6785
7182
  };
6786
7183
 
6787
- await assert.isRejected(
6788
- meeting.fetchMeetingInfo({sendCAevents: true}),
6789
- JoinWebinarError
6790
- );
7184
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6791
7185
 
6792
7186
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6793
7187
  assert.equal(
@@ -7524,6 +7918,27 @@ describe('plugin-meetings', () => {
7524
7918
  });
7525
7919
  });
7526
7920
 
7921
+ describe('#setIsoLocalClientMeetingJoinTime', () => {
7922
+ it('should fallback to system clock ISO string when given an undefined value', () => {
7923
+ const currentSystemTime = new Date().toISOString();
7924
+ meeting.isoLocalClientMeetingJoinTime = undefined;
7925
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
7926
+ });
7927
+
7928
+ it('should fallback to system clock ISO string when given an invalid value', () => {
7929
+ const currentSystemTime = new Date().toISOString();
7930
+ meeting.isoLocalClientMeetingJoinTime = 'invalid-date';
7931
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
7932
+ });
7933
+
7934
+ it('should set the isoLocalClientMeetingJoinTime correctly for a valid date string', () => {
7935
+ const validDateString = 'Tue, 01 Apr 2025 13:00:36 GMT';
7936
+ const expectedISOString = new Date(validDateString).toISOString();
7937
+ meeting.isoLocalClientMeetingJoinTime = validDateString;
7938
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, expectedISOString);
7939
+ });
7940
+ });
7941
+
7527
7942
  describe('#updateCallStateForMetrics', () => {
7528
7943
  it('should update the callState, overriding existing values', () => {
7529
7944
  assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
@@ -7605,6 +8020,12 @@ describe('plugin-meetings', () => {
7605
8020
  meeting.audio = {handleLocalStreamChange: sinon.stub()};
7606
8021
  meeting.video = {handleLocalStreamChange: sinon.stub()};
7607
8022
  meeting.statsAnalyzer = {updateMediaStatus: sinon.stub()};
8023
+ meeting.shareCAEventSentStatus = {
8024
+ transmitStart: false,
8025
+ transmitStop: false,
8026
+ receiveStart: false,
8027
+ receiveStop: false,
8028
+ };
7608
8029
  fakeMultistreamRoapMediaConnection = {
7609
8030
  createSendSlot: () => {
7610
8031
  return {
@@ -7672,6 +8093,9 @@ describe('plugin-meetings', () => {
7672
8093
  });
7673
8094
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
7674
8095
 
8096
+ assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
8097
+ assert.equal(meeting.shareCAEventSentStatus.transmitStop, false);
8098
+
7675
8099
  assert.calledWith(meeting.statsAnalyzer.updateMediaStatus, {
7676
8100
  expected: {sendShare: true},
7677
8101
  });
@@ -7692,18 +8116,23 @@ describe('plugin-meetings', () => {
7692
8116
  assert.equal(meeting.mediaProperties.shareAudioStream, stream);
7693
8117
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
7694
8118
 
8119
+ assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
8120
+ assert.equal(meeting.shareCAEventSentStatus.transmitStop, false);
8121
+
7695
8122
  assert.calledWith(meeting.statsAnalyzer.updateMediaStatus, {
7696
8123
  expected: {sendShare: true},
7697
8124
  });
7698
8125
  };
7699
8126
 
7700
8127
  it('requests screen share floor and publishes the screen share video stream', async () => {
8128
+ meeting.shareCAEventSentStatus.transmitStart = true;
7701
8129
  await meeting.publishStreams({screenShare: {video: videoShareStream}});
7702
8130
 
7703
8131
  checkScreenShareVideoPublished(videoShareStream);
7704
8132
  });
7705
8133
 
7706
8134
  it('requests screen share floor and publishes the screen share audio stream', async () => {
8135
+ meeting.shareCAEventSentStatus.transmitStart = true;
7707
8136
  await meeting.publishStreams({screenShare: {audio: audioShareStream}});
7708
8137
 
7709
8138
  checkScreenShareAudioPublished(audioShareStream);
@@ -8590,13 +9019,19 @@ describe('plugin-meetings', () => {
8590
9019
  const fakeErrorMessage = 'test error';
8591
9020
  const fakeRootCauseName = 'root cause name';
8592
9021
  const fakeErrorName = 'test error name';
9022
+ let clock;
8593
9023
 
8594
9024
  beforeEach(() => {
9025
+ clock = sinon.useFakeTimers();
8595
9026
  meeting.setupMediaConnectionListeners();
8596
9027
  webex.internal.newMetrics.submitClientEvent.resetHistory();
8597
9028
  Metrics.sendBehavioralMetric.resetHistory();
8598
9029
  });
8599
9030
 
9031
+ afterEach(() => {
9032
+ clock.restore();
9033
+ });
9034
+
8600
9035
  const checkMetricSent = (event, error) => {
8601
9036
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
8602
9037
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
@@ -8665,6 +9100,13 @@ describe('plugin-meetings', () => {
8665
9100
  });
8666
9101
 
8667
9102
  it('should send metrics for SdpAnswerHandlingError error', () => {
9103
+ meeting.sdpResponseTimer = '1234';
9104
+ meeting.deferSDPAnswer = {
9105
+ reject: sinon.stub(),
9106
+ };
9107
+
9108
+ const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
9109
+
8668
9110
  const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
8669
9111
  name: fakeErrorName,
8670
9112
  cause: {name: fakeRootCauseName},
@@ -8679,6 +9121,8 @@ describe('plugin-meetings', () => {
8679
9121
  fakeErrorMessage,
8680
9122
  fakeRootCauseName
8681
9123
  );
9124
+ assert.calledOnce(meeting.deferSDPAnswer.reject);
9125
+ assert.calledOnce(clearTimeoutSpy);
8682
9126
  });
8683
9127
 
8684
9128
  it('should send metrics for SdpError error', () => {
@@ -9223,22 +9667,22 @@ describe('plugin-meetings', () => {
9223
9667
  const assertBrb = (enabled) => {
9224
9668
  meeting.brbState = createBrbState(meeting, false);
9225
9669
  meeting.locusInfo.emit(
9226
- { function: 'test', file: 'test' },
9670
+ {function: 'test', file: 'test'},
9227
9671
  LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
9228
- { brb: { enabled } },
9229
- )
9672
+ {brb: {enabled}}
9673
+ );
9230
9674
  assert.calledWithExactly(
9231
9675
  TriggerProxy.trigger,
9232
9676
  meeting,
9233
9677
  {file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
9234
9678
  EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
9235
- { payload: { brb: { enabled } } },
9679
+ {payload: {brb: {enabled}}}
9236
9680
  );
9237
- }
9681
+ };
9238
9682
 
9239
9683
  assertBrb(true);
9240
9684
  assertBrb(false);
9241
- })
9685
+ });
9242
9686
 
9243
9687
  it('listens to the interpretation changed event', () => {
9244
9688
  meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
@@ -9584,6 +10028,42 @@ describe('plugin-meetings', () => {
9584
10028
  );
9585
10029
  });
9586
10030
 
10031
+ it('listens to CONTROLS_ANNOTATION_CHANGED', async () => {
10032
+ const state = {example: 'value'};
10033
+
10034
+ await meeting.locusInfo.emitScoped(
10035
+ {function: 'test', file: 'test'},
10036
+ LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED,
10037
+ {state}
10038
+ );
10039
+
10040
+ assert.calledWith(
10041
+ TriggerProxy.trigger,
10042
+ meeting,
10043
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10044
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
10045
+ {state}
10046
+ );
10047
+ });
10048
+
10049
+ it('listens to CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED', async () => {
10050
+ const state = {example: 'value'};
10051
+
10052
+ await meeting.locusInfo.emitScoped(
10053
+ {function: 'test', file: 'test'},
10054
+ LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED,
10055
+ {state}
10056
+ );
10057
+
10058
+ assert.calledWith(
10059
+ TriggerProxy.trigger,
10060
+ meeting,
10061
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10062
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
10063
+ {state}
10064
+ );
10065
+ });
10066
+
9587
10067
  it('listens to the locus interpretation update event', () => {
9588
10068
  const interpretation = {
9589
10069
  siLanguages: [{languageCode: 20, languageName: 'en'}],
@@ -9922,6 +10402,22 @@ describe('plugin-meetings', () => {
9922
10402
  });
9923
10403
  });
9924
10404
 
10405
+ describe('#emailInput', () => {
10406
+ it('should set the email input', () => {
10407
+ assert.notOk(meeting.emailInput);
10408
+ meeting.emailInput = 'current';
10409
+ assert.equal(meeting.emailInput, 'current');
10410
+ });
10411
+ });
10412
+
10413
+ describe('#userNameInput', () => {
10414
+ it('should set the user name input', () => {
10415
+ assert.notOk(meeting.userNameInput);
10416
+ meeting.userNameInput = 'current';
10417
+ assert.equal(meeting.userNameInput, 'current');
10418
+ });
10419
+ });
10420
+
9925
10421
  describe('#setPermissionTokenPayload', () => {
9926
10422
  let now;
9927
10423
  let clock;
@@ -10463,9 +10959,11 @@ describe('plugin-meetings', () => {
10463
10959
  let canUserLowerSomeoneElsesHandSpy;
10464
10960
  let waitingForOthersToJoinSpy;
10465
10961
  let canSendReactionsSpy;
10962
+ let requiresPostMeetingDataConsentPromptSpy;
10466
10963
  let canUserRenameSelfAndObservedSpy;
10467
10964
  let canUserRenameOthersSpy;
10468
10965
  let canShareWhiteBoardSpy;
10966
+ let canMoveToLobbySpy;
10469
10967
  // Due to import tree issues, hasHints must be stubed within the scope of the `it`.
10470
10968
 
10471
10969
  beforeEach(() => {
@@ -10490,8 +10988,13 @@ describe('plugin-meetings', () => {
10490
10988
  waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
10491
10989
  canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
10492
10990
  canUserRenameSelfAndObservedSpy = sinon.spy(MeetingUtil, 'canUserRenameSelfAndObserved');
10991
+ requiresPostMeetingDataConsentPromptSpy = sinon.spy(
10992
+ MeetingUtil,
10993
+ 'requiresPostMeetingDataConsentPrompt'
10994
+ );
10493
10995
  canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
10494
10996
  canShareWhiteBoardSpy = sinon.spy(MeetingUtil, 'canShareWhiteBoard');
10997
+ canMoveToLobbySpy = sinon.spy(MeetingUtil, 'canMoveToLobby');
10495
10998
  });
10496
10999
 
10497
11000
  afterEach(() => {
@@ -10589,6 +11092,16 @@ describe('plugin-meetings', () => {
10589
11092
  requiredDisplayHints: [],
10590
11093
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
10591
11094
  },
11095
+ {
11096
+ actionName: 'canRealtimeCloseCaption',
11097
+ requiredDisplayHints: [],
11098
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
11099
+ },
11100
+ {
11101
+ actionName: 'canRealtimeCloseCaptionManual',
11102
+ requiredDisplayHints: [],
11103
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
11104
+ },
10592
11105
  {
10593
11106
  actionName: 'canChat',
10594
11107
  requiredDisplayHints: [],
@@ -10618,6 +11131,11 @@ describe('plugin-meetings', () => {
10618
11131
  requiredDisplayHints: [],
10619
11132
  requiredPolicies: [SELF_POLICY.SUPPORT_POLLING_AND_QA],
10620
11133
  },
11134
+ {
11135
+ actionName: 'canShareWhiteBoard',
11136
+ requiredDisplayHints: [DISPLAY_HINTS.SHARE_WHITEBOARD],
11137
+ requiredPolicies: [SELF_POLICY.SUPPORT_WHITEBOARD],
11138
+ },
10621
11139
  ],
10622
11140
  ({
10623
11141
  actionName,
@@ -11025,8 +11543,10 @@ describe('plugin-meetings', () => {
11025
11543
  assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
11026
11544
  assert.calledWith(canSendReactionsSpy, null, userDisplayHints);
11027
11545
  assert.calledWith(canUserRenameSelfAndObservedSpy, userDisplayHints);
11546
+ assert.calledWith(requiresPostMeetingDataConsentPromptSpy, userDisplayHints);
11028
11547
  assert.calledWith(canUserRenameOthersSpy, userDisplayHints);
11029
- assert.calledWith(canShareWhiteBoardSpy, userDisplayHints);
11548
+ assert.calledWith(canShareWhiteBoardSpy, userDisplayHints, selfUserPolicies);
11549
+ assert.calledWith(canMoveToLobbySpy, userDisplayHints);
11030
11550
 
11031
11551
  assert.calledWith(ControlsOptionsUtil.hasHints, {
11032
11552
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
@@ -11120,6 +11640,22 @@ describe('plugin-meetings', () => {
11120
11640
  requiredPolicies: [SELF_POLICY.SUPPORT_VOIP],
11121
11641
  policies: selfUserPolicies,
11122
11642
  });
11643
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11644
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
11645
+ displayHints: userDisplayHints,
11646
+ });
11647
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11648
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
11649
+ displayHints: userDisplayHints,
11650
+ });
11651
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11652
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
11653
+ displayHints: userDisplayHints,
11654
+ });
11655
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11656
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
11657
+ displayHints: userDisplayHints,
11658
+ });
11123
11659
 
11124
11660
  assert.calledWith(
11125
11661
  TriggerProxy.trigger,
@@ -11251,7 +11787,10 @@ describe('plugin-meetings', () => {
11251
11787
 
11252
11788
  const result = await meeting.updateLLMConnection();
11253
11789
 
11254
- assert.calledWith(webex.internal.llm.disconnectLLM);
11790
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
11791
+ code: 3050,
11792
+ reason: 'done (permanent)',
11793
+ });
11255
11794
  assert.calledWith(
11256
11795
  webex.internal.llm.registerAndConnect,
11257
11796
  'a different url',
@@ -11281,7 +11820,10 @@ describe('plugin-meetings', () => {
11281
11820
 
11282
11821
  const result = await meeting.updateLLMConnection();
11283
11822
 
11284
- assert.calledWith(webex.internal.llm.disconnectLLM);
11823
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
11824
+ code: 3050,
11825
+ reason: 'done (permanent)',
11826
+ });
11285
11827
  assert.calledWith(
11286
11828
  webex.internal.llm.registerAndConnect,
11287
11829
  'a url',
@@ -11310,7 +11852,7 @@ describe('plugin-meetings', () => {
11310
11852
 
11311
11853
  const result = await meeting.updateLLMConnection();
11312
11854
 
11313
- assert.calledWith(webex.internal.llm.disconnectLLM);
11855
+ assert.calledWith(webex.internal.llm.disconnectLLM, undefined);
11314
11856
  assert.notCalled(webex.internal.llm.registerAndConnect);
11315
11857
  assert.equal(result, undefined);
11316
11858
  assert.calledOnceWithExactly(
@@ -11320,18 +11862,21 @@ describe('plugin-meetings', () => {
11320
11862
  );
11321
11863
  });
11322
11864
 
11323
-
11324
11865
  it('connect ps data channel if ps started in webinar', async () => {
11325
11866
  meeting.joinedWith = {state: 'JOINED'};
11326
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
11867
+ meeting.locusInfo = {
11868
+ url: 'a url',
11869
+ info: {
11870
+ datachannelUrl: 'a datachannel url',
11871
+ practiceSessionDatachannelUrl: 'a ps datachannel url',
11872
+ },
11873
+ };
11327
11874
  meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
11328
11875
  await meeting.updateLLMConnection();
11329
11876
 
11330
11877
  assert.notCalled(webex.internal.llm.disconnectLLM);
11331
11878
  assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
11332
-
11333
11879
  });
11334
-
11335
11880
  });
11336
11881
 
11337
11882
  describe('#setLocus', () => {
@@ -11749,24 +12294,29 @@ describe('plugin-meetings', () => {
11749
12294
 
11750
12295
  activeSharingId.whiteboard = beneficiaryId;
11751
12296
 
11752
- eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11753
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11754
- functionName: 'remoteShare',
11755
- eventPayload: {
11756
- memberId: null,
11757
- url,
11758
- shareInstanceId,
11759
- annotationInfo: undefined,
11760
- resourceType: undefined,
11761
- },
11762
- } : {
11763
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11764
- functionName: 'startWhiteboardShare',
11765
- eventPayload: {resourceUrl, memberId: beneficiaryId},
11766
- });
11767
-
11768
- shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
12297
+ eventTrigger.share.push(
12298
+ meeting.webinar.selfIsAttendee
12299
+ ? {
12300
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12301
+ functionName: 'remoteShare',
12302
+ eventPayload: {
12303
+ memberId: null,
12304
+ url,
12305
+ shareInstanceId,
12306
+ annotationInfo: undefined,
12307
+ resourceType: undefined,
12308
+ },
12309
+ }
12310
+ : {
12311
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
12312
+ functionName: 'startWhiteboardShare',
12313
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
12314
+ }
12315
+ );
11769
12316
 
12317
+ shareStatus = meeting.webinar.selfIsAttendee
12318
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12319
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11770
12320
  }
11771
12321
 
11772
12322
  if (eventTrigger.member) {
@@ -11798,24 +12348,29 @@ describe('plugin-meetings', () => {
11798
12348
  newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
11799
12349
  newPayload.current.content.beneficiaryId = otherBeneficiaryId;
11800
12350
 
11801
- eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11802
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11803
- functionName: 'remoteShare',
11804
- eventPayload: {
11805
- memberId: null,
11806
- url,
11807
- shareInstanceId,
11808
- annotationInfo: undefined,
11809
- resourceType: undefined,
11810
- },
11811
- } : {
11812
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11813
- functionName: 'startWhiteboardShare',
11814
- eventPayload: {resourceUrl, memberId: beneficiaryId},
11815
- });
11816
-
11817
- shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
12351
+ eventTrigger.share.push(
12352
+ meeting.webinar.selfIsAttendee
12353
+ ? {
12354
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12355
+ functionName: 'remoteShare',
12356
+ eventPayload: {
12357
+ memberId: null,
12358
+ url,
12359
+ shareInstanceId,
12360
+ annotationInfo: undefined,
12361
+ resourceType: undefined,
12362
+ },
12363
+ }
12364
+ : {
12365
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
12366
+ functionName: 'startWhiteboardShare',
12367
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
12368
+ }
12369
+ );
11818
12370
 
12371
+ shareStatus = meeting.webinar.selfIsAttendee
12372
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12373
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11819
12374
  } else {
11820
12375
  eventTrigger.share.push({
11821
12376
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
@@ -11945,24 +12500,26 @@ describe('plugin-meetings', () => {
11945
12500
  describe('Whiteboard Share - Webinar Attendee', () => {
11946
12501
  it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
11947
12502
  // Set the webinar attendee flag
11948
- meeting.webinar = { selfIsAttendee: true };
12503
+ meeting.webinar = {selfIsAttendee: true};
11949
12504
  meeting.locusInfo.info.isWebinar = true;
12505
+ meeting.shareCAEventSentStatus.receiveStart = true;
12506
+ meeting.shareCAEventSentStatus.receiveStop = true;
11950
12507
 
11951
12508
  // Step 1: Start sharing whiteboard A
11952
12509
  const data1 = generateData(
11953
- blankPayload, // Initial payload
11954
- true, // isGranting: Granting share
11955
- false, // isContent: Whiteboard (not content)
11956
- USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
12510
+ blankPayload, // Initial payload
12511
+ true, // isGranting: Granting share
12512
+ false, // isContent: Whiteboard (not content)
12513
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
11957
12514
  RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
11958
12515
  );
11959
12516
 
11960
12517
  // Step 2: Stop sharing whiteboard A
11961
12518
  const data2 = generateData(
11962
- data1.payload, // Updated payload from Step 1
11963
- false, // isGranting: Stopping share
11964
- false, // isContent: Whiteboard
11965
- USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
12519
+ data1.payload, // Updated payload from Step 1
12520
+ false, // isGranting: Stopping share
12521
+ false, // isContent: Whiteboard
12522
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
11966
12523
  );
11967
12524
 
11968
12525
  // Validate the payload changes and status updates
@@ -11970,10 +12527,11 @@ describe('plugin-meetings', () => {
11970
12527
 
11971
12528
  // Specific assertions for webinar attendee status
11972
12529
  assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
12530
+ assert.equal(meeting.shareCAEventSentStatus.receiveStart, false);
12531
+ assert.equal(meeting.shareCAEventSentStatus.receiveStop, false);
11973
12532
  });
11974
12533
  });
11975
12534
 
11976
-
11977
12535
  describe('Whiteboard A --> Whiteboard B', () => {
11978
12536
  it('Scenario #1: you share both whiteboards', () => {
11979
12537
  const data1 = generateData(
@@ -12626,6 +13184,31 @@ describe('plugin-meetings', () => {
12626
13184
  });
12627
13185
  });
12628
13186
  });
13187
+
13188
+ describe('handleShareVideoStreamMuteStateChange', () => {
13189
+ it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
13190
+ meeting.isMultistream = true;
13191
+ meeting.statsAnalyzer = {shareVideoEncoderImplementation: 'OpenH264'};
13192
+ meeting.mediaProperties.shareVideoStream = {
13193
+ getSettings: sinon.stub().returns({displaySurface: 'monitor', frameRate: 30}),
13194
+ };
13195
+
13196
+ meeting.handleShareVideoStreamMuteStateChange(true);
13197
+
13198
+ assert.calledOnceWithExactly(
13199
+ Metrics.sendBehavioralMetric,
13200
+ BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE,
13201
+ {
13202
+ correlationId: meeting.correlationId,
13203
+ muted: true,
13204
+ encoderImplementation: 'OpenH264',
13205
+ displaySurface: 'monitor',
13206
+ isMultistream: true,
13207
+ frameRate: 30,
13208
+ }
13209
+ );
13210
+ });
13211
+ });
12629
13212
  });
12630
13213
 
12631
13214
  describe('#startKeepAlive', () => {
@@ -12793,6 +13376,38 @@ describe('plugin-meetings', () => {
12793
13376
  });
12794
13377
  });
12795
13378
 
13379
+ describe('#setPostMeetingDataConsent', () => {
13380
+ it('should have #setPostMeetingDataConsent', () => {
13381
+ assert.exists(meeting.setPostMeetingDataConsent);
13382
+ });
13383
+
13384
+ beforeEach(() => {
13385
+ meeting.meetingRequest.setPostMeetingDataConsent = sinon
13386
+ .stub()
13387
+ .returns(Promise.resolve());
13388
+ });
13389
+
13390
+ [true, false].forEach((accept) => {
13391
+ it(`should send consent with ${accept}`, async () => {
13392
+ const id = uuidv4();
13393
+ meeting.locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${accept}`;
13394
+ meeting.deviceUrl = `https://wdm-test.wbx2.com/wdm/api/v1/devices/${accept}`;
13395
+ meeting.members.selfId = id;
13396
+
13397
+ const consentPromise = meeting.setPostMeetingDataConsent(accept);
13398
+
13399
+ assert.exists(consentPromise.then);
13400
+ await consentPromise;
13401
+ assert.calledOnceWithExactly(meeting.meetingRequest.setPostMeetingDataConsent, {
13402
+ locusUrl: `https://locus-test.wbx2.com/locus/api/v1/loci/${accept}`,
13403
+ postMeetingDataConsent: accept,
13404
+ selfId: id,
13405
+ deviceUrl: `https://wdm-test.wbx2.com/wdm/api/v1/devices/${accept}`,
13406
+ });
13407
+ });
13408
+ });
13409
+ });
13410
+
12796
13411
  describe('#sendReaction', () => {
12797
13412
  it('should have #sendReaction', () => {
12798
13413
  assert.exists(meeting.sendReaction);
@@ -13284,7 +13899,7 @@ describe('plugin-meetings', () => {
13284
13899
  await meeting.roapMessageReceived(fakeMessage);
13285
13900
 
13286
13901
  assert.fail('Expected MultistreamNotSupportedError to be thrown');
13287
- } catch(e) {
13902
+ } catch (e) {
13288
13903
  assert.isTrue(e instanceof MultistreamNotSupportedError);
13289
13904
  }
13290
13905