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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +70 -6
  4. package/dist/breakouts/index.js.map +1 -1
  5. package/dist/common/errors/webex-errors.js +12 -2
  6. package/dist/common/errors/webex-errors.js.map +1 -1
  7. package/dist/config.js +5 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/constants.js +28 -123
  10. package/dist/constants.js.map +1 -1
  11. package/dist/controls-options-manager/enums.js +3 -0
  12. package/dist/controls-options-manager/enums.js.map +1 -1
  13. package/dist/controls-options-manager/types.js.map +1 -1
  14. package/dist/controls-options-manager/util.js +78 -0
  15. package/dist/controls-options-manager/util.js.map +1 -1
  16. package/dist/interpretation/index.js +4 -4
  17. package/dist/interpretation/index.js.map +1 -1
  18. package/dist/interpretation/siLanguage.js +1 -1
  19. package/dist/locus-info/controlsUtils.js +37 -11
  20. package/dist/locus-info/controlsUtils.js.map +1 -1
  21. package/dist/locus-info/index.js +92 -12
  22. package/dist/locus-info/index.js.map +1 -1
  23. package/dist/locus-info/selfUtils.js +432 -418
  24. package/dist/locus-info/selfUtils.js.map +1 -1
  25. package/dist/media/index.js +17 -17
  26. package/dist/media/index.js.map +1 -1
  27. package/dist/media/properties.js +94 -6
  28. package/dist/media/properties.js.map +1 -1
  29. package/dist/meeting/brbState.js +9 -2
  30. package/dist/meeting/brbState.js.map +1 -1
  31. package/dist/meeting/in-meeting-actions.js +21 -1
  32. package/dist/meeting/in-meeting-actions.js.map +1 -1
  33. package/dist/meeting/index.js +678 -344
  34. package/dist/meeting/index.js.map +1 -1
  35. package/dist/meeting/locusMediaRequest.js +21 -22
  36. package/dist/meeting/locusMediaRequest.js.map +1 -1
  37. package/dist/meeting/muteState.js +4 -4
  38. package/dist/meeting/muteState.js.map +1 -1
  39. package/dist/meeting/request.js +30 -0
  40. package/dist/meeting/request.js.map +1 -1
  41. package/dist/meeting/request.type.js.map +1 -1
  42. package/dist/meeting/util.js +13 -2
  43. package/dist/meeting/util.js.map +1 -1
  44. package/dist/meeting-info/meeting-info-v2.js +373 -68
  45. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  46. package/dist/meeting-info/utilv2.js +5 -1
  47. package/dist/meeting-info/utilv2.js.map +1 -1
  48. package/dist/meetings/index.js +136 -1
  49. package/dist/meetings/index.js.map +1 -1
  50. package/dist/meetings/util.js +14 -0
  51. package/dist/meetings/util.js.map +1 -1
  52. package/dist/member/index.js +55 -9
  53. package/dist/member/index.js.map +1 -1
  54. package/dist/member/types.js +3 -0
  55. package/dist/member/types.js.map +1 -1
  56. package/dist/member/util.js +335 -353
  57. package/dist/member/util.js.map +1 -1
  58. package/dist/members/collection.js.map +1 -1
  59. package/dist/members/index.js +137 -29
  60. package/dist/members/index.js.map +1 -1
  61. package/dist/members/request.js +38 -0
  62. package/dist/members/request.js.map +1 -1
  63. package/dist/members/util.js +36 -1
  64. package/dist/members/util.js.map +1 -1
  65. package/dist/metrics/constants.js +10 -0
  66. package/dist/metrics/constants.js.map +1 -1
  67. package/dist/multistream/remoteMediaManager.js +40 -8
  68. package/dist/multistream/remoteMediaManager.js.map +1 -1
  69. package/dist/reachability/clusterReachability.js +63 -27
  70. package/dist/reachability/clusterReachability.js.map +1 -1
  71. package/dist/reachability/index.js +107 -47
  72. package/dist/reachability/index.js.map +1 -1
  73. package/dist/reachability/reachability.types.js +14 -0
  74. package/dist/reachability/reachability.types.js.map +1 -1
  75. package/dist/reachability/request.js +19 -3
  76. package/dist/reachability/request.js.map +1 -1
  77. package/dist/reconnection-manager/index.js +2 -2
  78. package/dist/reconnection-manager/index.js.map +1 -1
  79. package/dist/recording-controller/util.js +5 -5
  80. package/dist/recording-controller/util.js.map +1 -1
  81. package/dist/roap/index.js.map +1 -1
  82. package/dist/roap/turnDiscovery.js +45 -27
  83. package/dist/roap/turnDiscovery.js.map +1 -1
  84. package/dist/roap/types.js +17 -0
  85. package/dist/roap/types.js.map +1 -0
  86. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  87. package/dist/types/config.d.ts +3 -0
  88. package/dist/types/constants.d.ts +20 -85
  89. package/dist/types/controls-options-manager/enums.d.ts +4 -1
  90. package/dist/types/controls-options-manager/types.d.ts +10 -1
  91. package/dist/types/locus-info/index.d.ts +3 -3
  92. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  93. package/dist/types/media/properties.d.ts +15 -0
  94. package/dist/types/meeting/in-meeting-actions.d.ts +20 -0
  95. package/dist/types/meeting/index.d.ts +65 -1
  96. package/dist/types/meeting/muteState.d.ts +0 -1
  97. package/dist/types/meeting/request.d.ts +12 -1
  98. package/dist/types/meeting/request.type.d.ts +6 -0
  99. package/dist/types/meeting/util.d.ts +3 -1
  100. package/dist/types/meeting-info/meeting-info-v2.d.ts +82 -1
  101. package/dist/types/meetings/index.d.ts +57 -0
  102. package/dist/types/member/index.d.ts +21 -6
  103. package/dist/types/member/types.d.ts +73 -14
  104. package/dist/types/member/util.d.ts +156 -1
  105. package/dist/types/members/collection.d.ts +6 -5
  106. package/dist/types/members/index.d.ts +32 -43
  107. package/dist/types/members/request.d.ts +26 -0
  108. package/dist/types/members/util.d.ts +27 -0
  109. package/dist/types/metrics/constants.d.ts +10 -0
  110. package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
  111. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  112. package/dist/types/reachability/index.d.ts +10 -1
  113. package/dist/types/reachability/reachability.types.d.ts +5 -0
  114. package/dist/types/roap/index.d.ts +3 -2
  115. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  116. package/dist/types/roap/types.d.ts +16 -0
  117. package/dist/webinar/index.js +2 -2
  118. package/dist/webinar/index.js.map +1 -1
  119. package/package.json +24 -23
  120. package/src/breakouts/index.ts +69 -0
  121. package/src/common/errors/webex-errors.ts +8 -1
  122. package/src/config.ts +3 -0
  123. package/src/constants.ts +30 -90
  124. package/src/controls-options-manager/enums.ts +3 -0
  125. package/src/controls-options-manager/types.ts +16 -1
  126. package/src/controls-options-manager/util.ts +93 -0
  127. package/src/interpretation/index.ts +3 -3
  128. package/src/locus-info/controlsUtils.ts +59 -14
  129. package/src/locus-info/index.ts +97 -13
  130. package/src/locus-info/selfUtils.ts +496 -442
  131. package/src/media/index.ts +23 -21
  132. package/src/media/properties.ts +96 -0
  133. package/src/meeting/brbState.ts +11 -2
  134. package/src/meeting/in-meeting-actions.ts +40 -0
  135. package/src/meeting/index.ts +470 -105
  136. package/src/meeting/locusMediaRequest.ts +27 -22
  137. package/src/meeting/muteState.ts +4 -4
  138. package/src/meeting/request.ts +36 -1
  139. package/src/meeting/request.type.ts +7 -0
  140. package/src/meeting/util.ts +11 -2
  141. package/src/meeting-info/meeting-info-v2.ts +254 -8
  142. package/src/meeting-info/utilv2.ts +5 -0
  143. package/src/meetings/index.ts +148 -1
  144. package/src/meetings/util.ts +18 -0
  145. package/src/member/index.ts +68 -22
  146. package/src/member/types.ts +82 -16
  147. package/src/member/util.ts +357 -350
  148. package/src/members/collection.ts +4 -3
  149. package/src/members/index.ts +137 -18
  150. package/src/members/request.ts +44 -0
  151. package/src/members/util.ts +43 -1
  152. package/src/metrics/constants.ts +10 -0
  153. package/src/multistream/remoteMediaManager.ts +32 -10
  154. package/src/reachability/clusterReachability.ts +73 -26
  155. package/src/reachability/index.ts +62 -1
  156. package/src/reachability/reachability.types.ts +6 -0
  157. package/src/reachability/request.ts +7 -0
  158. package/src/reconnection-manager/index.ts +2 -2
  159. package/src/recording-controller/util.ts +17 -13
  160. package/src/roap/index.ts +3 -7
  161. package/src/roap/turnDiscovery.ts +34 -39
  162. package/src/roap/types.ts +23 -0
  163. package/src/webinar/index.ts +1 -1
  164. package/test/unit/spec/breakouts/index.ts +167 -95
  165. package/test/unit/spec/controls-options-manager/util.js +178 -0
  166. package/test/unit/spec/interpretation/index.ts +39 -1
  167. package/test/unit/spec/locus-info/controlsUtils.js +155 -9
  168. package/test/unit/spec/locus-info/index.js +209 -73
  169. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  170. package/test/unit/spec/media/index.ts +150 -18
  171. package/test/unit/spec/media/properties.ts +130 -0
  172. package/test/unit/spec/meeting/brbState.ts +40 -2
  173. package/test/unit/spec/meeting/in-meeting-actions.ts +23 -4
  174. package/test/unit/spec/meeting/index.js +804 -139
  175. package/test/unit/spec/meeting/locusMediaRequest.ts +95 -87
  176. package/test/unit/spec/meeting/muteState.js +73 -2
  177. package/test/unit/spec/meeting/request.js +32 -1
  178. package/test/unit/spec/meeting/utils.js +119 -18
  179. package/test/unit/spec/meeting-info/meetinginfov2.js +484 -114
  180. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  181. package/test/unit/spec/meetings/index.js +146 -2
  182. package/test/unit/spec/member/index.js +7 -0
  183. package/test/unit/spec/member/util.js +24 -0
  184. package/test/unit/spec/members/index.js +304 -78
  185. package/test/unit/spec/members/request.js +68 -22
  186. package/test/unit/spec/members/utils.js +75 -0
  187. package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
  188. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  189. package/test/unit/spec/reachability/index.ts +97 -0
  190. package/test/unit/spec/reachability/request.js +47 -2
  191. package/test/unit/spec/reconnection-manager/index.js +4 -4
  192. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
  193. package/test/unit/spec/webinar/index.ts +5 -0
  194. package/dist/annotation/annotation.types.d.ts +0 -42
  195. package/dist/annotation/constants.d.ts +0 -31
  196. package/dist/annotation/index.d.ts +0 -117
  197. package/dist/breakouts/breakout.d.ts +0 -8
  198. package/dist/breakouts/collection.d.ts +0 -5
  199. package/dist/breakouts/edit-lock-error.d.ts +0 -15
  200. package/dist/breakouts/events.d.ts +0 -8
  201. package/dist/breakouts/index.d.ts +0 -5
  202. package/dist/breakouts/request.d.ts +0 -22
  203. package/dist/breakouts/utils.d.ts +0 -15
  204. package/dist/common/browser-detection.d.ts +0 -9
  205. package/dist/common/collection.d.ts +0 -48
  206. package/dist/common/config.d.ts +0 -2
  207. package/dist/common/errors/captcha-error.d.ts +0 -15
  208. package/dist/common/errors/intent-to-join.d.ts +0 -16
  209. package/dist/common/errors/join-meeting.d.ts +0 -17
  210. package/dist/common/errors/media.d.ts +0 -15
  211. package/dist/common/errors/no-meeting-info.d.ts +0 -14
  212. package/dist/common/errors/parameter.d.ts +0 -15
  213. package/dist/common/errors/password-error.d.ts +0 -15
  214. package/dist/common/errors/permission.d.ts +0 -14
  215. package/dist/common/errors/reclaim-host-role-error.js +0 -149
  216. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  217. package/dist/common/errors/reclaim-host-role-errors.d.ts +0 -60
  218. package/dist/common/errors/reconnection-in-progress.d.ts +0 -9
  219. package/dist/common/errors/reconnection-in-progress.js +0 -33
  220. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  221. package/dist/common/errors/reconnection.d.ts +0 -15
  222. package/dist/common/errors/stats.d.ts +0 -15
  223. package/dist/common/errors/webex-errors.d.ts +0 -93
  224. package/dist/common/errors/webex-meetings-error.d.ts +0 -20
  225. package/dist/common/events/events-scope.d.ts +0 -17
  226. package/dist/common/events/events.d.ts +0 -12
  227. package/dist/common/events/trigger-proxy.d.ts +0 -2
  228. package/dist/common/events/util.d.ts +0 -2
  229. package/dist/common/logs/logger-config.d.ts +0 -2
  230. package/dist/common/logs/logger-proxy.d.ts +0 -2
  231. package/dist/common/logs/request.d.ts +0 -36
  232. package/dist/common/queue.d.ts +0 -34
  233. package/dist/config.d.ts +0 -72
  234. package/dist/constants.d.ts +0 -1088
  235. package/dist/controls-options-manager/constants.d.ts +0 -4
  236. package/dist/controls-options-manager/enums.d.ts +0 -15
  237. package/dist/controls-options-manager/index.d.ts +0 -136
  238. package/dist/controls-options-manager/types.d.ts +0 -43
  239. package/dist/controls-options-manager/util.d.ts +0 -1
  240. package/dist/index.d.ts +0 -7
  241. package/dist/interceptors/index.d.ts +0 -2
  242. package/dist/interceptors/locusRetry.d.ts +0 -27
  243. package/dist/interpretation/collection.d.ts +0 -5
  244. package/dist/interpretation/index.d.ts +0 -5
  245. package/dist/interpretation/siLanguage.d.ts +0 -5
  246. package/dist/locus-info/controlsUtils.d.ts +0 -2
  247. package/dist/locus-info/embeddedAppsUtils.d.ts +0 -2
  248. package/dist/locus-info/fullState.d.ts +0 -2
  249. package/dist/locus-info/hostUtils.d.ts +0 -2
  250. package/dist/locus-info/index.d.ts +0 -322
  251. package/dist/locus-info/infoUtils.d.ts +0 -2
  252. package/dist/locus-info/mediaSharesUtils.d.ts +0 -2
  253. package/dist/locus-info/parser.d.ts +0 -272
  254. package/dist/locus-info/selfUtils.d.ts +0 -2
  255. package/dist/media/index.d.ts +0 -34
  256. package/dist/media/properties.d.ts +0 -93
  257. package/dist/media/util.d.ts +0 -2
  258. package/dist/mediaQualityMetrics/config.d.ts +0 -241
  259. package/dist/mediaQualityMetrics/config.js +0 -502
  260. package/dist/mediaQualityMetrics/config.js.map +0 -1
  261. package/dist/meeting/effectsState.js +0 -260
  262. package/dist/meeting/effectsState.js.map +0 -1
  263. package/dist/meeting/in-meeting-actions.d.ts +0 -167
  264. package/dist/meeting/index.d.ts +0 -1825
  265. package/dist/meeting/locusMediaRequest.d.ts +0 -74
  266. package/dist/meeting/muteState.d.ts +0 -178
  267. package/dist/meeting/request.d.ts +0 -295
  268. package/dist/meeting/request.type.d.ts +0 -11
  269. package/dist/meeting/state.d.ts +0 -9
  270. package/dist/meeting/util.d.ts +0 -119
  271. package/dist/meeting/voicea-meeting.d.ts +0 -16
  272. package/dist/meeting-info/collection.d.ts +0 -20
  273. package/dist/meeting-info/index.d.ts +0 -69
  274. package/dist/meeting-info/meeting-info-v2.d.ts +0 -123
  275. package/dist/meeting-info/request.d.ts +0 -22
  276. package/dist/meeting-info/util.d.ts +0 -2
  277. package/dist/meeting-info/utilv2.d.ts +0 -2
  278. package/dist/meetings/collection.d.ts +0 -40
  279. package/dist/meetings/index.d.ts +0 -390
  280. package/dist/meetings/meetings.types.d.ts +0 -4
  281. package/dist/meetings/request.d.ts +0 -27
  282. package/dist/meetings/util.d.ts +0 -18
  283. package/dist/member/index.d.ts +0 -160
  284. package/dist/member/member.types.js +0 -17
  285. package/dist/member/member.types.js.map +0 -1
  286. package/dist/member/types.d.ts +0 -32
  287. package/dist/member/util.d.ts +0 -2
  288. package/dist/members/collection.d.ts +0 -29
  289. package/dist/members/index.d.ts +0 -353
  290. package/dist/members/request.d.ts +0 -114
  291. package/dist/members/types.d.ts +0 -25
  292. package/dist/members/util.d.ts +0 -215
  293. package/dist/metrics/config.js +0 -276
  294. package/dist/metrics/config.js.map +0 -1
  295. package/dist/metrics/constants.d.ts +0 -70
  296. package/dist/metrics/index.d.ts +0 -45
  297. package/dist/multistream/mediaRequestManager.d.ts +0 -119
  298. package/dist/multistream/receiveSlot.d.ts +0 -68
  299. package/dist/multistream/receiveSlotManager.d.ts +0 -56
  300. package/dist/multistream/remoteMedia.d.ts +0 -72
  301. package/dist/multistream/remoteMediaGroup.d.ts +0 -49
  302. package/dist/multistream/remoteMediaManager.d.ts +0 -300
  303. package/dist/multistream/sendSlotManager.d.ts +0 -69
  304. package/dist/networkQualityMonitor/index.d.ts +0 -70
  305. package/dist/networkQualityMonitor/index.js +0 -221
  306. package/dist/networkQualityMonitor/index.js.map +0 -1
  307. package/dist/peer-connection-manager/index.js +0 -671
  308. package/dist/peer-connection-manager/index.js.map +0 -1
  309. package/dist/peer-connection-manager/util.js +0 -109
  310. package/dist/peer-connection-manager/util.js.map +0 -1
  311. package/dist/personal-meeting-room/index.d.ts +0 -47
  312. package/dist/personal-meeting-room/request.d.ts +0 -14
  313. package/dist/personal-meeting-room/util.d.ts +0 -2
  314. package/dist/reachability/clusterReachability.d.ts +0 -109
  315. package/dist/reachability/index.d.ts +0 -105
  316. package/dist/reachability/request.d.ts +0 -39
  317. package/dist/reachability/util.d.ts +0 -8
  318. package/dist/reactions/constants.d.ts +0 -3
  319. package/dist/reactions/reactions.d.ts +0 -4
  320. package/dist/reactions/reactions.type.d.ts +0 -52
  321. package/dist/reconnection-manager/index.d.ts +0 -136
  322. package/dist/recording-controller/enums.d.ts +0 -7
  323. package/dist/recording-controller/index.d.ts +0 -207
  324. package/dist/recording-controller/util.d.ts +0 -14
  325. package/dist/roap/collection.js +0 -62
  326. package/dist/roap/collection.js.map +0 -1
  327. package/dist/roap/handler.js +0 -275
  328. package/dist/roap/handler.js.map +0 -1
  329. package/dist/roap/index.d.ts +0 -86
  330. package/dist/roap/request.d.ts +0 -39
  331. package/dist/roap/state.js +0 -126
  332. package/dist/roap/state.js.map +0 -1
  333. package/dist/roap/turnDiscovery.d.ts +0 -155
  334. package/dist/roap/util.js +0 -75
  335. package/dist/roap/util.js.map +0 -1
  336. package/dist/rtcMetrics/constants.d.ts +0 -4
  337. package/dist/rtcMetrics/constants.js +0 -11
  338. package/dist/rtcMetrics/constants.js.map +0 -1
  339. package/dist/rtcMetrics/index.d.ts +0 -61
  340. package/dist/rtcMetrics/index.js +0 -197
  341. package/dist/rtcMetrics/index.js.map +0 -1
  342. package/dist/statsAnalyzer/global.d.ts +0 -36
  343. package/dist/statsAnalyzer/global.js +0 -126
  344. package/dist/statsAnalyzer/global.js.map +0 -1
  345. package/dist/statsAnalyzer/index.d.ts +0 -217
  346. package/dist/statsAnalyzer/index.js +0 -1013
  347. package/dist/statsAnalyzer/index.js.map +0 -1
  348. package/dist/statsAnalyzer/mqaUtil.d.ts +0 -48
  349. package/dist/statsAnalyzer/mqaUtil.js +0 -179
  350. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  351. package/dist/transcription/index.d.ts +0 -64
  352. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  353. package/dist/types/mediaQualityMetrics/config.d.ts +0 -241
  354. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  355. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  356. package/dist/types/rtcMetrics/index.d.ts +0 -71
  357. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  358. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  359. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  360. package/dist/webinar/collection.d.ts +0 -16
  361. 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,9 @@ describe('plugin-meetings', () => {
2153
2227
  someReachabilityMetric1: 'some value1',
2154
2228
  someReachabilityMetric2: 'some value2',
2155
2229
  selectedCandidatePairChanges: 2,
2230
+ subnet_reachable: null,
2231
+ selected_cluster: null,
2232
+ selected_subnet: null,
2156
2233
  numTransports: 1,
2157
2234
  iceCandidatesCount: 0,
2158
2235
  }
@@ -2199,6 +2276,9 @@ describe('plugin-meetings', () => {
2199
2276
  signalingState: 'unknown',
2200
2277
  connectionState: 'unknown',
2201
2278
  iceConnectionState: 'unknown',
2279
+ subnet_reachable: null,
2280
+ selected_cluster: null,
2281
+ selected_subnet: null,
2202
2282
  })
2203
2283
  );
2204
2284
 
@@ -2213,6 +2293,7 @@ describe('plugin-meetings', () => {
2213
2293
  someReachabilityMetric1: 'some value1',
2214
2294
  someReachabilityMetric2: 'some value2',
2215
2295
  }),
2296
+ isSubnetReachable: sinon.stub().returns(true),
2216
2297
  };
2217
2298
 
2218
2299
  meeting.waitForRemoteSDPAnswer = sinon.stub().rejects();
@@ -2263,6 +2344,9 @@ describe('plugin-meetings', () => {
2263
2344
  selectedCandidatePairChanges: 2,
2264
2345
  numTransports: 1,
2265
2346
  iceCandidatesCount: 0,
2347
+ subnet_reachable: null,
2348
+ selected_cluster: null,
2349
+ selected_subnet: null,
2266
2350
  }
2267
2351
  );
2268
2352
  });
@@ -2320,6 +2404,9 @@ describe('plugin-meetings', () => {
2320
2404
  signalingState: 'have-local-offer',
2321
2405
  connectionState: 'connecting',
2322
2406
  iceConnectionState: 'checking',
2407
+ subnet_reachable: null,
2408
+ selected_cluster: null,
2409
+ selected_subnet: null,
2323
2410
  })
2324
2411
  );
2325
2412
 
@@ -2377,6 +2464,9 @@ describe('plugin-meetings', () => {
2377
2464
  signalingState: 'have-local-offer',
2378
2465
  connectionState: 'connecting',
2379
2466
  iceConnectionState: 'checking',
2467
+ subnet_reachable: null,
2468
+ selected_cluster: null,
2469
+ selected_subnet: null,
2380
2470
  })
2381
2471
  );
2382
2472
 
@@ -2655,7 +2745,7 @@ describe('plugin-meetings', () => {
2655
2745
 
2656
2746
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
2657
2747
  turnServerInfo: {
2658
- url: FAKE_TURN_URL,
2748
+ urls: [FAKE_TURN_URL],
2659
2749
  username: FAKE_TURN_USER,
2660
2750
  password: FAKE_TURN_PASSWORD,
2661
2751
  },
@@ -2677,7 +2767,7 @@ describe('plugin-meetings', () => {
2677
2767
  meeting.id,
2678
2768
  sinon.match({
2679
2769
  turnServerInfo: {
2680
- url: FAKE_TURN_URL,
2770
+ urls: [FAKE_TURN_URL],
2681
2771
  username: FAKE_TURN_USER,
2682
2772
  password: FAKE_TURN_PASSWORD,
2683
2773
  },
@@ -2712,8 +2802,9 @@ describe('plugin-meetings', () => {
2712
2802
  sinon.stub().returns(FAKE_ERROR));
2713
2803
  webex.meetings.reachability = {
2714
2804
  isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
2715
- getReachabilityMetrics: sinon.stub().resolves(),
2805
+ getReachabilityMetrics: sinon.stub().resolves({}),
2716
2806
  stopReachability: sinon.stub(),
2807
+ isSubnetReachable: sinon.stub().returns(true),
2717
2808
  };
2718
2809
  const MOCK_CLIENT_ERROR_CODE = 2004;
2719
2810
  const generateClientErrorCodeForIceFailureStub = sinon
@@ -2735,14 +2826,15 @@ describe('plugin-meetings', () => {
2735
2826
  .onSecondCall()
2736
2827
  .returns({
2737
2828
  turnServerInfo: {
2738
- url: FAKE_TURN_URL,
2829
+ urls: [FAKE_TURN_URL],
2739
2830
  username: FAKE_TURN_USER,
2740
2831
  password: FAKE_TURN_PASSWORD,
2741
2832
  },
2742
2833
  turnDiscoverySkippedReason: undefined,
2743
2834
  });
2744
2835
  meeting.meetingState = 'ACTIVE';
2745
- meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
2836
+ const error = {iceConnected: false};
2837
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects(error);
2746
2838
 
2747
2839
  const forceRtcMetricsSend = sinon.stub().resolves();
2748
2840
  const closeMediaConnectionStub = sinon.stub();
@@ -2760,6 +2852,7 @@ describe('plugin-meetings', () => {
2760
2852
  })
2761
2853
  .catch((err) => {
2762
2854
  errorThrown = err;
2855
+ assert.instanceOf(err.cause, Error);
2763
2856
  assert.instanceOf(err, AddMediaFailed);
2764
2857
  });
2765
2858
 
@@ -2816,6 +2909,7 @@ describe('plugin-meetings', () => {
2816
2909
  },
2817
2910
  options: {
2818
2911
  meetingId: meeting.id,
2912
+ rawError: error,
2819
2913
  },
2820
2914
  });
2821
2915
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
@@ -2827,6 +2921,7 @@ describe('plugin-meetings', () => {
2827
2921
  },
2828
2922
  options: {
2829
2923
  meetingId: meeting.id,
2924
+ rawError: error,
2830
2925
  },
2831
2926
  });
2832
2927
 
@@ -2893,6 +2988,9 @@ describe('plugin-meetings', () => {
2893
2988
  selectedCandidatePairChanges: 2,
2894
2989
  numTransports: 1,
2895
2990
  iceCandidatesCount: 0,
2991
+ subnet_reachable: null,
2992
+ selected_cluster: null,
2993
+ selected_subnet: null,
2896
2994
  },
2897
2995
  ]);
2898
2996
 
@@ -2923,6 +3021,7 @@ describe('plugin-meetings', () => {
2923
3021
  .resolves(false),
2924
3022
  getReachabilityMetrics: sinon.stub().resolves({}),
2925
3023
  stopReachability: sinon.stub(),
3024
+ isSubnetReachable: sinon.stub().returns(true),
2926
3025
  };
2927
3026
  const getErrorPayloadForClientErrorCodeStub =
2928
3027
  (webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
@@ -2947,16 +3046,19 @@ describe('plugin-meetings', () => {
2947
3046
  .onSecondCall()
2948
3047
  .returns({
2949
3048
  turnServerInfo: {
2950
- url: FAKE_TURN_URL,
3049
+ urls: [FAKE_TURN_URL],
2951
3050
  username: FAKE_TURN_USER,
2952
3051
  password: FAKE_TURN_PASSWORD,
2953
3052
  },
2954
3053
  turnDiscoverySkippedReason: undefined,
2955
3054
  });
3055
+
3056
+ const mediaConnectionError = new Error('fake error');
3057
+
2956
3058
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon
2957
3059
  .stub()
2958
3060
  .onFirstCall()
2959
- .rejects()
3061
+ .rejects(mediaConnectionError)
2960
3062
  .onSecondCall()
2961
3063
  .resolves();
2962
3064
 
@@ -3025,10 +3127,14 @@ describe('plugin-meetings', () => {
3025
3127
  },
3026
3128
  options: {
3027
3129
  meetingId: meeting.id,
3130
+ rawError: mediaConnectionError,
3028
3131
  },
3029
3132
  });
3030
3133
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
3031
3134
  name: 'client.media-engine.ready',
3135
+ payload: {
3136
+ ipVersion: 'IPv6',
3137
+ },
3032
3138
  options: {
3033
3139
  meetingId: meeting.id,
3034
3140
  },
@@ -3085,11 +3191,15 @@ describe('plugin-meetings', () => {
3085
3191
  locus_id: meeting.locusUrl.split('/').pop(),
3086
3192
  connectionType: 'udp',
3087
3193
  selectedCandidatePairChanges: 2,
3194
+ ipVersion: 'IPv6',
3088
3195
  numTransports: 1,
3089
3196
  isMultistream: false,
3090
3197
  retriedWithTurnServer: true,
3091
3198
  isJoinWithMediaRetry: false,
3092
3199
  iceCandidatesCount: 0,
3200
+ subnet_reachable: null,
3201
+ selected_cluster: null,
3202
+ selected_subnet: null,
3093
3203
  },
3094
3204
  ]);
3095
3205
  meeting.roap.doTurnDiscovery;
@@ -3124,7 +3234,7 @@ describe('plugin-meetings', () => {
3124
3234
  .onSecondCall()
3125
3235
  .returns({
3126
3236
  turnServerInfo: {
3127
- url: FAKE_TURN_URL,
3237
+ urls: [FAKE_TURN_URL],
3128
3238
  username: FAKE_TURN_USER,
3129
3239
  password: FAKE_TURN_PASSWORD,
3130
3240
  },
@@ -3176,7 +3286,7 @@ describe('plugin-meetings', () => {
3176
3286
  .onSecondCall()
3177
3287
  .returns({
3178
3288
  turnServerInfo: {
3179
- url: FAKE_TURN_URL,
3289
+ urls: [FAKE_TURN_URL],
3180
3290
  username: FAKE_TURN_USER,
3181
3291
  password: FAKE_TURN_PASSWORD,
3182
3292
  },
@@ -3218,7 +3328,13 @@ describe('plugin-meetings', () => {
3218
3328
  someReachabilityMetric2: 'some value2',
3219
3329
  }),
3220
3330
  stopReachability: sinon.stub(),
3331
+ isSubnetReachable: sinon.stub().returns(true),
3221
3332
  };
3333
+ meeting.mediaConnections = [
3334
+ {
3335
+ mediaAgentCluster: 'some.cluster',
3336
+ }
3337
+ ]
3222
3338
  meeting.iceCandidatesCount = 3;
3223
3339
  meeting.iceCandidateErrors.set('701_error', 3);
3224
3340
  meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
@@ -3236,6 +3352,7 @@ describe('plugin-meetings', () => {
3236
3352
  locus_id: meeting.locusUrl.split('/').pop(),
3237
3353
  connectionType: 'udp',
3238
3354
  selectedCandidatePairChanges: 2,
3355
+ ipVersion: 'IPv6',
3239
3356
  numTransports: 1,
3240
3357
  isMultistream: false,
3241
3358
  retriedWithTurnServer: false,
@@ -3245,6 +3362,9 @@ describe('plugin-meetings', () => {
3245
3362
  iceCandidatesCount: 3,
3246
3363
  '701_error': 3,
3247
3364
  '701_turn_host_lookup_received_error': 1,
3365
+ subnet_reachable: null,
3366
+ selected_cluster: 'some.cluster',
3367
+ selected_subnet: null,
3248
3368
  }
3249
3369
  );
3250
3370
 
@@ -3307,6 +3427,9 @@ describe('plugin-meetings', () => {
3307
3427
  iceConnectionState: 'unknown',
3308
3428
  selectedCandidatePairChanges: 2,
3309
3429
  numTransports: 1,
3430
+ subnet_reachable: null,
3431
+ selected_cluster: null,
3432
+ selected_subnet: null,
3310
3433
  iceCandidatesCount: 0,
3311
3434
  }
3312
3435
  );
@@ -3368,6 +3491,135 @@ describe('plugin-meetings', () => {
3368
3491
  numTransports: 1,
3369
3492
  '701_error': 2,
3370
3493
  '701_turn_host_lookup_received_error': 1,
3494
+ subnet_reachable: null,
3495
+ selected_cluster: null,
3496
+ selected_subnet: null,
3497
+ iceCandidatesCount: 0,
3498
+ }
3499
+ );
3500
+
3501
+ assert.isOk(errorThrown);
3502
+ });
3503
+
3504
+ it('should send subnet reachablity metrics if media connection success', async () => {
3505
+ meeting.roap.doTurnDiscovery = sinon.stub().returns({
3506
+ turnServerInfo: undefined,
3507
+ turnDiscoverySkippedReason: undefined,
3508
+ });
3509
+ meeting.meetingState = 'ACTIVE';
3510
+ meeting.mediaProperties.waitForMediaConnectionConnected.resolves();
3511
+ meeting.webex.meetings.reachability = {
3512
+ getReachabilityMetrics: sinon.stub().resolves({
3513
+ reachability_public_udp_success: 5,
3514
+ }),
3515
+ stopReachability: sinon.stub(),
3516
+ isSubnetReachable: sinon.stub().returns(false),
3517
+ };
3518
+ meeting.mediaServerIp = '1.2.3.4';
3519
+ meeting.mediaConnections = [
3520
+ {
3521
+ mediaAgentCluster: 'some.cluster',
3522
+ }
3523
+ ]
3524
+
3525
+ const forceRtcMetricsSend = sinon.stub().resolves();
3526
+ const closeMediaConnectionStub = sinon.stub();
3527
+ Media.createMediaConnection = sinon.stub().returns({
3528
+ close: closeMediaConnectionStub,
3529
+ forceRtcMetricsSend,
3530
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
3531
+ initiateOffer: sinon.stub().resolves({}),
3532
+ on: sinon.stub(),
3533
+ });
3534
+
3535
+ await meeting.addMedia({
3536
+ mediaSettings: {},
3537
+ });
3538
+
3539
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
3540
+ correlation_id: meeting.correlationId,
3541
+ locus_id: meeting.locusUrl.split('/').pop(),
3542
+ connectionType: 'udp',
3543
+ ipVersion: 'IPv6',
3544
+ selectedCandidatePairChanges: 2,
3545
+ numTransports: 1,
3546
+ isMultistream: false,
3547
+ retriedWithTurnServer: false,
3548
+ isJoinWithMediaRetry: false,
3549
+ iceCandidatesCount: 0,
3550
+ reachability_public_udp_success: 5,
3551
+ subnet_reachable: false,
3552
+ selected_cluster: 'some.cluster',
3553
+ selected_subnet: '1.X.X.X',
3554
+ });
3555
+ });
3556
+
3557
+ it('should send subnet reachablity metrics if media connection fails', async () => {
3558
+ let errorThrown = undefined;
3559
+
3560
+ meeting.roap.doTurnDiscovery = sinon.stub().returns({
3561
+ turnServerInfo: undefined,
3562
+ turnDiscoverySkippedReason: undefined,
3563
+ });
3564
+ meeting.meetingState = 'ACTIVE';
3565
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
3566
+ meeting.webex.meetings.reachability = {
3567
+ getReachabilityMetrics: sinon.stub().resolves({
3568
+ reachability_public_udp_success: 5,
3569
+ }),
3570
+ stopReachability: sinon.stub(),
3571
+ isSubnetReachable: sinon.stub().returns(true),
3572
+ };
3573
+ meeting.mediaServerIp = '1.2.3.4';
3574
+ meeting.mediaConnections = [
3575
+ {
3576
+ mediaAgentCluster: 'some.cluster',
3577
+ }
3578
+ ]
3579
+
3580
+ const forceRtcMetricsSend = sinon.stub().resolves();
3581
+ const closeMediaConnectionStub = sinon.stub();
3582
+ Media.createMediaConnection = sinon.stub().returns({
3583
+ close: closeMediaConnectionStub,
3584
+ forceRtcMetricsSend,
3585
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
3586
+ initiateOffer: sinon.stub().resolves({}),
3587
+ on: sinon.stub(),
3588
+ });
3589
+
3590
+ await meeting
3591
+ .addMedia({
3592
+ mediaSettings: {},
3593
+ })
3594
+ .catch((err) => {
3595
+ errorThrown = err;
3596
+ assert.instanceOf(err, AddMediaFailed);
3597
+ });
3598
+
3599
+ // Check that the only metric sent is ADD_MEDIA_FAILURE
3600
+ assert.calledOnceWithExactly(
3601
+ Metrics.sendBehavioralMetric,
3602
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
3603
+ {
3604
+ correlation_id: meeting.correlationId,
3605
+ locus_id: meeting.locusUrl.split('/').pop(),
3606
+ reason: errorThrown.message,
3607
+ stack: errorThrown.stack,
3608
+ code: errorThrown.code,
3609
+ turnDiscoverySkippedReason: undefined,
3610
+ turnServerUsed: true,
3611
+ retriedWithTurnServer: false,
3612
+ isMultistream: false,
3613
+ isJoinWithMediaRetry: false,
3614
+ signalingState: 'unknown',
3615
+ connectionState: 'unknown',
3616
+ iceConnectionState: 'unknown',
3617
+ selectedCandidatePairChanges: 2,
3618
+ numTransports: 1,
3619
+ reachability_public_udp_success: 5,
3620
+ subnet_reachable: true,
3621
+ selected_cluster: 'some.cluster',
3622
+ selected_subnet: '1.X.X.X',
3371
3623
  iceCandidatesCount: 0,
3372
3624
  }
3373
3625
  );
@@ -3387,6 +3639,8 @@ describe('plugin-meetings', () => {
3387
3639
  meeting.config.stats.enableStatsAnalyzer = true;
3388
3640
 
3389
3641
  statsAnalyzerStub = new EventsScope();
3642
+ statsAnalyzerStub.getNetworkType = sinon.stub().returns('wifi');
3643
+
3390
3644
  // mock the StatsAnalyzer constructor
3391
3645
  sinon.stub(InternalMediaCoreModule, 'StatsAnalyzer').returns(statsAnalyzerStub);
3392
3646
 
@@ -3427,6 +3681,40 @@ describe('plugin-meetings', () => {
3427
3681
  });
3428
3682
  });
3429
3683
 
3684
+ it('LOCAL_MEDIA_STARTED triggers "meeting:media:local:start" event and does not send metric because we already have', async () => {
3685
+ meeting.shareCAEventSentStatus = {
3686
+ transmitStart: true,
3687
+ transmitStop: false,
3688
+ receiveStart: false,
3689
+ receiveStop: false,
3690
+ };
3691
+ statsAnalyzerStub.emit(
3692
+ {file: 'test', function: 'test'},
3693
+ StatsAnalyzerEventNames.LOCAL_MEDIA_STARTED,
3694
+ {mediaType: 'share'}
3695
+ );
3696
+
3697
+ assert.calledWith(
3698
+ TriggerProxy.trigger,
3699
+ sinon.match.instanceOf(Meeting),
3700
+ {
3701
+ file: 'meeting/index',
3702
+ function: 'addMedia',
3703
+ },
3704
+ EVENT_TRIGGERS.MEETING_MEDIA_LOCAL_STARTED,
3705
+ {
3706
+ mediaType: 'share',
3707
+ }
3708
+ );
3709
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3710
+ name: 'client.media.tx.start',
3711
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3712
+ options: {
3713
+ meetingId: meeting.id,
3714
+ },
3715
+ });
3716
+ });
3717
+
3430
3718
  it('LOCAL_MEDIA_STOPPED triggers the right metrics', async () => {
3431
3719
  statsAnalyzerStub.emit(
3432
3720
  {file: 'test', function: 'test'},
@@ -3443,6 +3731,28 @@ describe('plugin-meetings', () => {
3443
3731
  });
3444
3732
  });
3445
3733
 
3734
+ it('LOCAL_MEDIA_STOPPED does not send metric because we already have', async () => {
3735
+ meeting.shareCAEventSentStatus = {
3736
+ transmitStart: false,
3737
+ transmitStop: true,
3738
+ receiveStart: false,
3739
+ receiveStop: false,
3740
+ };
3741
+ statsAnalyzerStub.emit(
3742
+ {file: 'test', function: 'test'},
3743
+ StatsAnalyzerEventNames.LOCAL_MEDIA_STOPPED,
3744
+ {mediaType: 'share'}
3745
+ );
3746
+
3747
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3748
+ name: 'client.media.tx.stop',
3749
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3750
+ options: {
3751
+ meetingId: meeting.id,
3752
+ },
3753
+ });
3754
+ });
3755
+
3446
3756
  it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics', async () => {
3447
3757
  statsAnalyzerStub.emit(
3448
3758
  {file: 'test', function: 'test'},
@@ -3523,6 +3833,47 @@ describe('plugin-meetings', () => {
3523
3833
  });
3524
3834
  });
3525
3835
 
3836
+ it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and does not send metric because we already have', async () => {
3837
+ meeting.shareCAEventSentStatus = {
3838
+ transmitStart: false,
3839
+ transmitStop: false,
3840
+ receiveStart: true,
3841
+ receiveStop: false,
3842
+ };
3843
+ statsAnalyzerStub.emit(
3844
+ {file: 'test', function: 'test'},
3845
+ StatsAnalyzerEventNames.REMOTE_MEDIA_STARTED,
3846
+ {mediaType: 'share'}
3847
+ );
3848
+
3849
+ assert.calledWith(
3850
+ TriggerProxy.trigger,
3851
+ sinon.match.instanceOf(Meeting),
3852
+ {
3853
+ file: 'meeting/index',
3854
+ function: 'addMedia',
3855
+ },
3856
+ EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
3857
+ {
3858
+ mediaType: 'share',
3859
+ }
3860
+ );
3861
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3862
+ name: 'client.media.render.start',
3863
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3864
+ options: {
3865
+ meetingId: meeting.id,
3866
+ },
3867
+ });
3868
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3869
+ name: 'client.media.rx.start',
3870
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3871
+ options: {
3872
+ meetingId: meeting.id,
3873
+ },
3874
+ });
3875
+ });
3876
+
3526
3877
  it('REMOTE_MEDIA_STOPPED triggers the right metrics for share', async () => {
3527
3878
  statsAnalyzerStub.emit(
3528
3879
  {file: 'test', function: 'test'},
@@ -3547,21 +3898,49 @@ describe('plugin-meetings', () => {
3547
3898
  });
3548
3899
  });
3549
3900
 
3901
+ it('REMOTE_MEDIA_STOPPED does not send metric because we already have', async () => {
3902
+ meeting.shareCAEventSentStatus = {
3903
+ transmitStart: false,
3904
+ transmitStop: false,
3905
+ receiveStart: true,
3906
+ receiveStop: true,
3907
+ };
3908
+ statsAnalyzerStub.emit(
3909
+ {file: 'test', function: 'test'},
3910
+ StatsAnalyzerEventNames.REMOTE_MEDIA_STOPPED,
3911
+ {mediaType: 'share'}
3912
+ );
3913
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3914
+ name: 'client.media.render.stop',
3915
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3916
+ options: {
3917
+ meetingId: meeting.id,
3918
+ },
3919
+ });
3920
+ assert.neverCalledWith(webex.internal.newMetrics.submitClientEvent, {
3921
+ name: 'client.media.rx.stop',
3922
+ payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
3923
+ options: {
3924
+ meetingId: meeting.id,
3925
+ },
3926
+ });
3927
+ });
3928
+
3550
3929
  it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
3551
3930
  let fakeMembersCollection = {
3552
3931
  members: {
3553
- member1: { isInMeeting: true },
3554
- member2: { isInMeeting: true },
3555
- member3: { isInMeeting: false },
3932
+ member1: {isInMeeting: true},
3933
+ member2: {isInMeeting: true},
3934
+ member3: {isInMeeting: false},
3556
3935
  },
3557
3936
  };
3558
- sinon.stub(meeting, 'getMembers').returns({ membersCollection: fakeMembersCollection });
3559
- const fakeData = { intervalMetadata: {}, networkType: 'wifi' };
3937
+ sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
3938
+ const fakeData = {intervalMetadata: {}};
3560
3939
 
3561
3940
  statsAnalyzerStub.emit(
3562
- { file: 'test', function: 'test' },
3941
+ {file: 'test', function: 'test'},
3563
3942
  StatsAnalyzerEventNames.MEDIA_QUALITY,
3564
- { data: fakeData }
3943
+ {data: fakeData}
3565
3944
  );
3566
3945
 
3567
3946
  assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
@@ -3570,15 +3949,17 @@ describe('plugin-meetings', () => {
3570
3949
  meetingId: meeting.id,
3571
3950
  },
3572
3951
  payload: {
3573
- intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2))],
3952
+ intervals: [
3953
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
3954
+ ],
3574
3955
  },
3575
3956
  });
3576
3957
  fakeMembersCollection.members.member2.isInMeeting = false;
3577
3958
 
3578
3959
  statsAnalyzerStub.emit(
3579
- { file: 'test', function: 'test' },
3960
+ {file: 'test', function: 'test'},
3580
3961
  StatsAnalyzerEventNames.MEDIA_QUALITY,
3581
- { data: fakeData }
3962
+ {data: fakeData}
3582
3963
  );
3583
3964
 
3584
3965
  assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
@@ -3587,13 +3968,15 @@ describe('plugin-meetings', () => {
3587
3968
  meetingId: meeting.id,
3588
3969
  },
3589
3970
  payload: {
3590
- intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1))],
3971
+ intervals: [
3972
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1)),
3973
+ ],
3591
3974
  },
3592
3975
  });
3593
3976
  });
3594
3977
 
3595
3978
  it('calls submitMQE correctly', async () => {
3596
- const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
3979
+ const fakeData = {intervalMetadata: {bla: 'bla'}};
3597
3980
 
3598
3981
  statsAnalyzerStub.emit(
3599
3982
  {file: 'test', function: 'test'},
@@ -3624,7 +4007,7 @@ describe('plugin-meetings', () => {
3624
4007
 
3625
4008
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3626
4009
  turnServerInfo: {
3627
- url: FAKE_TURN_URL,
4010
+ urls: [FAKE_TURN_URL],
3628
4011
  username: FAKE_TURN_USER,
3629
4012
  password: FAKE_TURN_PASSWORD,
3630
4013
  },
@@ -3650,7 +4033,7 @@ describe('plugin-meetings', () => {
3650
4033
  meeting.id,
3651
4034
  sinon.match({
3652
4035
  turnServerInfo: {
3653
- url: FAKE_TURN_URL,
4036
+ urls: [FAKE_TURN_URL],
3654
4037
  username: FAKE_TURN_USER,
3655
4038
  password: FAKE_TURN_PASSWORD,
3656
4039
  },
@@ -3801,6 +4184,9 @@ describe('plugin-meetings', () => {
3801
4184
  },
3802
4185
  options: {
3803
4186
  meetingId: meeting.id,
4187
+ rawError: {
4188
+ iceConnected: false,
4189
+ },
3804
4190
  },
3805
4191
  },
3806
4192
  ]);
@@ -3830,7 +4216,7 @@ describe('plugin-meetings', () => {
3830
4216
  meeting.deviceUrl = 'device url';
3831
4217
  meeting.selfId = 'self id';
3832
4218
  meeting.brbState = createBrbState(meeting, false);
3833
- meeting.brbState.enable = sinon.stub().resolves();
4219
+ sinon.stub(meeting.brbState, 'enable').resolves();
3834
4220
  });
3835
4221
 
3836
4222
  afterEach(() => {
@@ -3842,7 +4228,6 @@ describe('plugin-meetings', () => {
3842
4228
  });
3843
4229
 
3844
4230
  describe('when in a multistream meeting', () => {
3845
-
3846
4231
  beforeEach(() => {
3847
4232
  meeting.isMultistream = true;
3848
4233
  });
@@ -3853,7 +4238,7 @@ describe('plugin-meetings', () => {
3853
4238
  await brbResult;
3854
4239
  assert.exists(brbResult.then);
3855
4240
  assert.calledOnce(meeting.brbState.enable);
3856
- })
4241
+ });
3857
4242
 
3858
4243
  it('should disable #beRightBack and return a promise', async () => {
3859
4244
  const brbResult = meeting.beRightBack(false);
@@ -3861,7 +4246,7 @@ describe('plugin-meetings', () => {
3861
4246
  await brbResult;
3862
4247
  assert.exists(brbResult.then);
3863
4248
  assert.calledOnce(meeting.brbState.enable);
3864
- })
4249
+ });
3865
4250
 
3866
4251
  it('should throw an error and reject the promise if setBrb fails', async () => {
3867
4252
  const error = new Error('setBrb failed');
@@ -3872,9 +4257,42 @@ describe('plugin-meetings', () => {
3872
4257
  } catch (err) {
3873
4258
  assert.instanceOf(err, Error);
3874
4259
  assert.equal(err.message, 'setBrb failed');
3875
- assert.isRejected((Promise.reject()));
4260
+ assert.isRejected(Promise.reject());
3876
4261
  }
3877
- })
4262
+ });
4263
+
4264
+ it('updates remote mute state when brb is enabled', async () => {
4265
+ meeting.audio = {handleServerRemoteMuteUpdate: sinon.stub()};
4266
+
4267
+ await meeting.beRightBack(true);
4268
+
4269
+ sinon.assert.calledOnceWithExactly(
4270
+ meeting.audio.handleServerRemoteMuteUpdate,
4271
+ meeting,
4272
+ true
4273
+ );
4274
+ });
4275
+
4276
+ it('does not update remote mute state when brb is disabled', async () => {
4277
+ meeting.audio = {handleServerRemoteMuteUpdate: sinon.stub()};
4278
+
4279
+ await meeting.beRightBack(false);
4280
+
4281
+ assert.notCalled(meeting.audio.handleServerRemoteMuteUpdate);
4282
+ });
4283
+
4284
+ it('should reject when brb enable fails', async () => {
4285
+ meeting.brbState.enable.restore();
4286
+
4287
+ const error = new Error();
4288
+ meeting.meetingRequest.setBrb = sinon.stub().rejects(error);
4289
+
4290
+ await expect(
4291
+ meeting.beRightBack(true)
4292
+ ).to.be.rejectedWith(error);
4293
+
4294
+ assert.isFalse(meeting.brbState.state.syncToServerInProgress);
4295
+ });
3878
4296
  });
3879
4297
  });
3880
4298
 
@@ -3928,7 +4346,10 @@ describe('plugin-meetings', () => {
3928
4346
  .resolves({id: 'fake clientMediaPreferences'});
3929
4347
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3930
4348
  turnServerInfo: {
3931
- url: 'turns:turn-server-url:443?transport=tcp',
4349
+ urls: [
4350
+ 'turns:turn-server-url1:443?transport=tcp',
4351
+ 'turns:turn-server-url2:443?transport=tcp',
4352
+ ],
3932
4353
  username: 'turn user',
3933
4354
  password: 'turn password',
3934
4355
  },
@@ -3946,12 +4367,10 @@ describe('plugin-meetings', () => {
3946
4367
  expectedMediaConnectionConfig = {
3947
4368
  iceServers: [
3948
4369
  {
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',
4370
+ urls: [
4371
+ 'turns:turn-server-url1:443?transport=tcp',
4372
+ 'turns:turn-server-url2:443?transport=tcp',
4373
+ ],
3955
4374
  username: 'turn user',
3956
4375
  credential: 'turn password',
3957
4376
  },
@@ -4006,7 +4425,7 @@ describe('plugin-meetings', () => {
4006
4425
  initiateOffer: sinon.stub().resolves({}),
4007
4426
  update: sinon.stub().resolves({}),
4008
4427
  on: sinon.stub(),
4009
- roapMessageReceived: sinon.stub()
4428
+ roapMessageReceived: sinon.stub(),
4010
4429
  };
4011
4430
 
4012
4431
  fakeMultistreamRoapMediaConnection = {
@@ -4033,9 +4452,11 @@ describe('plugin-meetings', () => {
4033
4452
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
4034
4453
  .returns(fakeMultistreamRoapMediaConnection);
4035
4454
 
4036
- locusMediaRequestStub = sinon
4037
- .stub(WebexPlugin.prototype, 'request')
4038
- .resolves({body: {locus: {fullState: {}}}});
4455
+ locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({
4456
+ body: {locus: {fullState: {}}},
4457
+ upload: sinon.match.instanceOf(EventEmitter),
4458
+ download: sinon.match.instanceOf(EventEmitter),
4459
+ });
4039
4460
 
4040
4461
  // setup some things and mocks so that the call to join() works
4041
4462
  // (we need to call join() because it creates the LocusMediaRequest instance
@@ -4144,6 +4565,8 @@ describe('plugin-meetings', () => {
4144
4565
  id: 'fake clientMediaPreferences',
4145
4566
  },
4146
4567
  },
4568
+ upload: sinon.match.instanceOf(EventEmitter),
4569
+ download: sinon.match.instanceOf(EventEmitter),
4147
4570
  });
4148
4571
  };
4149
4572
 
@@ -4171,6 +4594,8 @@ describe('plugin-meetings', () => {
4171
4594
  },
4172
4595
  ],
4173
4596
  },
4597
+ upload: sinon.match.instanceOf(EventEmitter),
4598
+ download: sinon.match.instanceOf(EventEmitter),
4174
4599
  });
4175
4600
  };
4176
4601
 
@@ -4195,6 +4620,8 @@ describe('plugin-meetings', () => {
4195
4620
  respOnlySdp: true,
4196
4621
  usingResource: null,
4197
4622
  },
4623
+ upload: sinon.match.instanceOf(EventEmitter),
4624
+ download: sinon.match.instanceOf(EventEmitter),
4198
4625
  });
4199
4626
  };
4200
4627
 
@@ -5213,7 +5640,10 @@ describe('plugin-meetings', () => {
5213
5640
  // and check that when we fallback to transcoded we still do another TURN discovery
5214
5641
  await runCheck(
5215
5642
  {
5216
- url: 'turns:turn-server-url:443?transport=tcp',
5643
+ urls: [
5644
+ 'turns:turn-server-url1:443?transport=tcp',
5645
+ 'turns:turn-server-url2:443?transport=tcp',
5646
+ ],
5217
5647
  username: 'turn user',
5218
5648
  password: 'turn password',
5219
5649
  },
@@ -5227,7 +5657,10 @@ describe('plugin-meetings', () => {
5227
5657
  // but doing it just for completeness
5228
5658
  await runCheck(
5229
5659
  {
5230
- url: 'turns:turn-server-url:443?transport=tcp',
5660
+ urls: [
5661
+ 'turns:turn-server-url1:443?transport=tcp',
5662
+ 'turns:turn-server-url2:443?transport=tcp',
5663
+ ],
5231
5664
  username: 'turn user',
5232
5665
  password: 'turn password',
5233
5666
  },
@@ -6337,7 +6770,10 @@ describe('plugin-meetings', () => {
6337
6770
  .throws(new MeetingInfoV2JoinForbiddenError(403003, FAKE_MEETING_INFO)),
6338
6771
  };
6339
6772
 
6340
- await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinForbiddenError);
6773
+ await assert.isRejected(
6774
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6775
+ JoinForbiddenError
6776
+ );
6341
6777
 
6342
6778
  assert.calledWith(
6343
6779
  meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
@@ -6353,10 +6789,7 @@ describe('plugin-meetings', () => {
6353
6789
 
6354
6790
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6355
6791
  assert.equal(meeting.meetingInfoFailureCode, 403003);
6356
- assert.equal(
6357
- meeting.meetingInfoFailureReason,
6358
- MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH
6359
- );
6792
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH);
6360
6793
  assert.equal(meeting.requiredCaptcha, null);
6361
6794
  });
6362
6795
 
@@ -6733,15 +7166,10 @@ describe('plugin-meetings', () => {
6733
7166
  meeting.attrs.meetingInfoProvider = {
6734
7167
  fetchMeetingInfo: sinon
6735
7168
  .stub()
6736
- .throws(
6737
- new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
6738
- ),
7169
+ .throws(new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')),
6739
7170
  };
6740
7171
 
6741
- await assert.isRejected(
6742
- meeting.fetchMeetingInfo({sendCAevents: true}),
6743
- JoinWebinarError
6744
- );
7172
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6745
7173
 
6746
7174
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6747
7175
  assert.equal(
@@ -6756,15 +7184,10 @@ describe('plugin-meetings', () => {
6756
7184
  meeting.attrs.meetingInfoProvider = {
6757
7185
  fetchMeetingInfo: sinon
6758
7186
  .stub()
6759
- .throws(
6760
- new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
6761
- ),
7187
+ .throws(new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')),
6762
7188
  };
6763
7189
 
6764
- await assert.isRejected(
6765
- meeting.fetchMeetingInfo({sendCAevents: true}),
6766
- JoinWebinarError
6767
- );
7190
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6768
7191
 
6769
7192
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6770
7193
  assert.equal(
@@ -6779,15 +7202,10 @@ describe('plugin-meetings', () => {
6779
7202
  meeting.attrs.meetingInfoProvider = {
6780
7203
  fetchMeetingInfo: sinon
6781
7204
  .stub()
6782
- .throws(
6783
- new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
6784
- ),
7205
+ .throws(new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')),
6785
7206
  };
6786
7207
 
6787
- await assert.isRejected(
6788
- meeting.fetchMeetingInfo({sendCAevents: true}),
6789
- JoinWebinarError
6790
- );
7208
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6791
7209
 
6792
7210
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6793
7211
  assert.equal(
@@ -7524,6 +7942,27 @@ describe('plugin-meetings', () => {
7524
7942
  });
7525
7943
  });
7526
7944
 
7945
+ describe('#setIsoLocalClientMeetingJoinTime', () => {
7946
+ it('should fallback to system clock ISO string when given an undefined value', () => {
7947
+ const currentSystemTime = new Date().toISOString();
7948
+ meeting.isoLocalClientMeetingJoinTime = undefined;
7949
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
7950
+ });
7951
+
7952
+ it('should fallback to system clock ISO string when given an invalid value', () => {
7953
+ const currentSystemTime = new Date().toISOString();
7954
+ meeting.isoLocalClientMeetingJoinTime = 'invalid-date';
7955
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
7956
+ });
7957
+
7958
+ it('should set the isoLocalClientMeetingJoinTime correctly for a valid date string', () => {
7959
+ const validDateString = 'Tue, 01 Apr 2025 13:00:36 GMT';
7960
+ const expectedISOString = new Date(validDateString).toISOString();
7961
+ meeting.isoLocalClientMeetingJoinTime = validDateString;
7962
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, expectedISOString);
7963
+ });
7964
+ });
7965
+
7527
7966
  describe('#updateCallStateForMetrics', () => {
7528
7967
  it('should update the callState, overriding existing values', () => {
7529
7968
  assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
@@ -7605,6 +8044,12 @@ describe('plugin-meetings', () => {
7605
8044
  meeting.audio = {handleLocalStreamChange: sinon.stub()};
7606
8045
  meeting.video = {handleLocalStreamChange: sinon.stub()};
7607
8046
  meeting.statsAnalyzer = {updateMediaStatus: sinon.stub()};
8047
+ meeting.shareCAEventSentStatus = {
8048
+ transmitStart: false,
8049
+ transmitStop: false,
8050
+ receiveStart: false,
8051
+ receiveStop: false,
8052
+ };
7608
8053
  fakeMultistreamRoapMediaConnection = {
7609
8054
  createSendSlot: () => {
7610
8055
  return {
@@ -7672,6 +8117,9 @@ describe('plugin-meetings', () => {
7672
8117
  });
7673
8118
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
7674
8119
 
8120
+ assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
8121
+ assert.equal(meeting.shareCAEventSentStatus.transmitStop, false);
8122
+
7675
8123
  assert.calledWith(meeting.statsAnalyzer.updateMediaStatus, {
7676
8124
  expected: {sendShare: true},
7677
8125
  });
@@ -7692,18 +8140,23 @@ describe('plugin-meetings', () => {
7692
8140
  assert.equal(meeting.mediaProperties.shareAudioStream, stream);
7693
8141
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
7694
8142
 
8143
+ assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
8144
+ assert.equal(meeting.shareCAEventSentStatus.transmitStop, false);
8145
+
7695
8146
  assert.calledWith(meeting.statsAnalyzer.updateMediaStatus, {
7696
8147
  expected: {sendShare: true},
7697
8148
  });
7698
8149
  };
7699
8150
 
7700
8151
  it('requests screen share floor and publishes the screen share video stream', async () => {
8152
+ meeting.shareCAEventSentStatus.transmitStart = true;
7701
8153
  await meeting.publishStreams({screenShare: {video: videoShareStream}});
7702
8154
 
7703
8155
  checkScreenShareVideoPublished(videoShareStream);
7704
8156
  });
7705
8157
 
7706
8158
  it('requests screen share floor and publishes the screen share audio stream', async () => {
8159
+ meeting.shareCAEventSentStatus.transmitStart = true;
7707
8160
  await meeting.publishStreams({screenShare: {audio: audioShareStream}});
7708
8161
 
7709
8162
  checkScreenShareAudioPublished(audioShareStream);
@@ -8590,13 +9043,19 @@ describe('plugin-meetings', () => {
8590
9043
  const fakeErrorMessage = 'test error';
8591
9044
  const fakeRootCauseName = 'root cause name';
8592
9045
  const fakeErrorName = 'test error name';
9046
+ let clock;
8593
9047
 
8594
9048
  beforeEach(() => {
9049
+ clock = sinon.useFakeTimers();
8595
9050
  meeting.setupMediaConnectionListeners();
8596
9051
  webex.internal.newMetrics.submitClientEvent.resetHistory();
8597
9052
  Metrics.sendBehavioralMetric.resetHistory();
8598
9053
  });
8599
9054
 
9055
+ afterEach(() => {
9056
+ clock.restore();
9057
+ });
9058
+
8600
9059
  const checkMetricSent = (event, error) => {
8601
9060
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
8602
9061
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
@@ -8665,6 +9124,13 @@ describe('plugin-meetings', () => {
8665
9124
  });
8666
9125
 
8667
9126
  it('should send metrics for SdpAnswerHandlingError error', () => {
9127
+ meeting.sdpResponseTimer = '1234';
9128
+ meeting.deferSDPAnswer = {
9129
+ reject: sinon.stub(),
9130
+ };
9131
+
9132
+ const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
9133
+
8668
9134
  const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
8669
9135
  name: fakeErrorName,
8670
9136
  cause: {name: fakeRootCauseName},
@@ -8679,6 +9145,8 @@ describe('plugin-meetings', () => {
8679
9145
  fakeErrorMessage,
8680
9146
  fakeRootCauseName
8681
9147
  );
9148
+ assert.calledOnce(meeting.deferSDPAnswer.reject);
9149
+ assert.calledOnce(clearTimeoutSpy);
8682
9150
  });
8683
9151
 
8684
9152
  it('should send metrics for SdpError error', () => {
@@ -9223,22 +9691,22 @@ describe('plugin-meetings', () => {
9223
9691
  const assertBrb = (enabled) => {
9224
9692
  meeting.brbState = createBrbState(meeting, false);
9225
9693
  meeting.locusInfo.emit(
9226
- { function: 'test', file: 'test' },
9694
+ {function: 'test', file: 'test'},
9227
9695
  LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
9228
- { brb: { enabled } },
9229
- )
9696
+ {brb: {enabled}}
9697
+ );
9230
9698
  assert.calledWithExactly(
9231
9699
  TriggerProxy.trigger,
9232
9700
  meeting,
9233
9701
  {file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
9234
9702
  EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
9235
- { payload: { brb: { enabled } } },
9703
+ {payload: {brb: {enabled}}}
9236
9704
  );
9237
- }
9705
+ };
9238
9706
 
9239
9707
  assertBrb(true);
9240
9708
  assertBrb(false);
9241
- })
9709
+ });
9242
9710
 
9243
9711
  it('listens to the interpretation changed event', () => {
9244
9712
  meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
@@ -9584,6 +10052,60 @@ describe('plugin-meetings', () => {
9584
10052
  );
9585
10053
  });
9586
10054
 
10055
+ it('listens to CONTROLS_ANNOTATION_CHANGED', async () => {
10056
+ const state = {example: 'value'};
10057
+
10058
+ await meeting.locusInfo.emitScoped(
10059
+ {function: 'test', file: 'test'},
10060
+ LOCUSINFO.EVENTS.CONTROLS_ANNOTATION_CHANGED,
10061
+ {state}
10062
+ );
10063
+
10064
+ assert.calledWith(
10065
+ TriggerProxy.trigger,
10066
+ meeting,
10067
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10068
+ EVENT_TRIGGERS.MEETING_CONTROLS_ANNOTATION_UPDATED,
10069
+ {state}
10070
+ );
10071
+ });
10072
+
10073
+ it('listens to CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED', async () => {
10074
+ const state = {example: 'value'};
10075
+
10076
+ await meeting.locusInfo.emitScoped(
10077
+ {function: 'test', file: 'test'},
10078
+ LOCUSINFO.EVENTS.CONTROLS_REMOTE_DESKTOP_CONTROL_CHANGED,
10079
+ {state}
10080
+ );
10081
+
10082
+ assert.calledWith(
10083
+ TriggerProxy.trigger,
10084
+ meeting,
10085
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10086
+ EVENT_TRIGGERS.MEETING_CONTROLS_REMOTE_DESKTOP_CONTROL_UPDATED,
10087
+ {state}
10088
+ );
10089
+ });
10090
+
10091
+ it('listens to CONTROLS_POLLING_QA_CHANGED', async () => {
10092
+ const state = {example: 'value'};
10093
+
10094
+ await meeting.locusInfo.emitScoped(
10095
+ {function: 'test', file: 'test'},
10096
+ LOCUSINFO.EVENTS.CONTROLS_POLLING_QA_CHANGED,
10097
+ {state}
10098
+ );
10099
+
10100
+ assert.calledWith(
10101
+ TriggerProxy.trigger,
10102
+ meeting,
10103
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
10104
+ EVENT_TRIGGERS.MEETING_CONTROLS_POLLING_QA_UPDATED,
10105
+ {state}
10106
+ );
10107
+ });
10108
+
9587
10109
  it('listens to the locus interpretation update event', () => {
9588
10110
  const interpretation = {
9589
10111
  siLanguages: [{languageCode: 20, languageName: 'en'}],
@@ -9922,6 +10444,22 @@ describe('plugin-meetings', () => {
9922
10444
  });
9923
10445
  });
9924
10446
 
10447
+ describe('#emailInput', () => {
10448
+ it('should set the email input', () => {
10449
+ assert.notOk(meeting.emailInput);
10450
+ meeting.emailInput = 'current';
10451
+ assert.equal(meeting.emailInput, 'current');
10452
+ });
10453
+ });
10454
+
10455
+ describe('#userNameInput', () => {
10456
+ it('should set the user name input', () => {
10457
+ assert.notOk(meeting.userNameInput);
10458
+ meeting.userNameInput = 'current';
10459
+ assert.equal(meeting.userNameInput, 'current');
10460
+ });
10461
+ });
10462
+
9925
10463
  describe('#setPermissionTokenPayload', () => {
9926
10464
  let now;
9927
10465
  let clock;
@@ -10463,9 +11001,11 @@ describe('plugin-meetings', () => {
10463
11001
  let canUserLowerSomeoneElsesHandSpy;
10464
11002
  let waitingForOthersToJoinSpy;
10465
11003
  let canSendReactionsSpy;
11004
+ let requiresPostMeetingDataConsentPromptSpy;
10466
11005
  let canUserRenameSelfAndObservedSpy;
10467
11006
  let canUserRenameOthersSpy;
10468
11007
  let canShareWhiteBoardSpy;
11008
+ let canMoveToLobbySpy;
10469
11009
  // Due to import tree issues, hasHints must be stubed within the scope of the `it`.
10470
11010
 
10471
11011
  beforeEach(() => {
@@ -10490,8 +11030,13 @@ describe('plugin-meetings', () => {
10490
11030
  waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
10491
11031
  canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
10492
11032
  canUserRenameSelfAndObservedSpy = sinon.spy(MeetingUtil, 'canUserRenameSelfAndObserved');
11033
+ requiresPostMeetingDataConsentPromptSpy = sinon.spy(
11034
+ MeetingUtil,
11035
+ 'requiresPostMeetingDataConsentPrompt'
11036
+ );
10493
11037
  canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
10494
11038
  canShareWhiteBoardSpy = sinon.spy(MeetingUtil, 'canShareWhiteBoard');
11039
+ canMoveToLobbySpy = sinon.spy(MeetingUtil, 'canMoveToLobby');
10495
11040
  });
10496
11041
 
10497
11042
  afterEach(() => {
@@ -10589,6 +11134,16 @@ describe('plugin-meetings', () => {
10589
11134
  requiredDisplayHints: [],
10590
11135
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
10591
11136
  },
11137
+ {
11138
+ actionName: 'canRealtimeCloseCaption',
11139
+ requiredDisplayHints: [],
11140
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION],
11141
+ },
11142
+ {
11143
+ actionName: 'canRealtimeCloseCaptionManual',
11144
+ requiredDisplayHints: [],
11145
+ requiredPolicies: [SELF_POLICY.SUPPORT_REALTIME_CLOSE_CAPTION_MANUAL],
11146
+ },
10592
11147
  {
10593
11148
  actionName: 'canChat',
10594
11149
  requiredDisplayHints: [],
@@ -10618,6 +11173,11 @@ describe('plugin-meetings', () => {
10618
11173
  requiredDisplayHints: [],
10619
11174
  requiredPolicies: [SELF_POLICY.SUPPORT_POLLING_AND_QA],
10620
11175
  },
11176
+ {
11177
+ actionName: 'canShareWhiteBoard',
11178
+ requiredDisplayHints: [DISPLAY_HINTS.SHARE_WHITEBOARD],
11179
+ requiredPolicies: [SELF_POLICY.SUPPORT_WHITEBOARD],
11180
+ },
10621
11181
  ],
10622
11182
  ({
10623
11183
  actionName,
@@ -11025,8 +11585,10 @@ describe('plugin-meetings', () => {
11025
11585
  assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
11026
11586
  assert.calledWith(canSendReactionsSpy, null, userDisplayHints);
11027
11587
  assert.calledWith(canUserRenameSelfAndObservedSpy, userDisplayHints);
11588
+ assert.calledWith(requiresPostMeetingDataConsentPromptSpy, userDisplayHints);
11028
11589
  assert.calledWith(canUserRenameOthersSpy, userDisplayHints);
11029
- assert.calledWith(canShareWhiteBoardSpy, userDisplayHints);
11590
+ assert.calledWith(canShareWhiteBoardSpy, userDisplayHints, selfUserPolicies);
11591
+ assert.calledWith(canMoveToLobbySpy, userDisplayHints);
11030
11592
 
11031
11593
  assert.calledWith(ControlsOptionsUtil.hasHints, {
11032
11594
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
@@ -11120,6 +11682,30 @@ describe('plugin-meetings', () => {
11120
11682
  requiredPolicies: [SELF_POLICY.SUPPORT_VOIP],
11121
11683
  policies: selfUserPolicies,
11122
11684
  });
11685
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11686
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
11687
+ displayHints: userDisplayHints,
11688
+ });
11689
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11690
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
11691
+ displayHints: userDisplayHints,
11692
+ });
11693
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11694
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
11695
+ displayHints: userDisplayHints,
11696
+ });
11697
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11698
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
11699
+ displayHints: userDisplayHints,
11700
+ });
11701
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11702
+ requiredHints: [DISPLAY_HINTS.ENABLE_ATTENDEE_START_POLLING_QA],
11703
+ displayHints: userDisplayHints,
11704
+ });
11705
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
11706
+ requiredHints: [DISPLAY_HINTS.DISABLE_ATTENDEE_START_POLLING_QA],
11707
+ displayHints: userDisplayHints,
11708
+ });
11123
11709
 
11124
11710
  assert.calledWith(
11125
11711
  TriggerProxy.trigger,
@@ -11251,7 +11837,10 @@ describe('plugin-meetings', () => {
11251
11837
 
11252
11838
  const result = await meeting.updateLLMConnection();
11253
11839
 
11254
- assert.calledWith(webex.internal.llm.disconnectLLM);
11840
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
11841
+ code: 3050,
11842
+ reason: 'done (permanent)',
11843
+ });
11255
11844
  assert.calledWith(
11256
11845
  webex.internal.llm.registerAndConnect,
11257
11846
  'a different url',
@@ -11281,7 +11870,10 @@ describe('plugin-meetings', () => {
11281
11870
 
11282
11871
  const result = await meeting.updateLLMConnection();
11283
11872
 
11284
- assert.calledWith(webex.internal.llm.disconnectLLM);
11873
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
11874
+ code: 3050,
11875
+ reason: 'done (permanent)',
11876
+ });
11285
11877
  assert.calledWith(
11286
11878
  webex.internal.llm.registerAndConnect,
11287
11879
  'a url',
@@ -11310,7 +11902,7 @@ describe('plugin-meetings', () => {
11310
11902
 
11311
11903
  const result = await meeting.updateLLMConnection();
11312
11904
 
11313
- assert.calledWith(webex.internal.llm.disconnectLLM);
11905
+ assert.calledWith(webex.internal.llm.disconnectLLM, undefined);
11314
11906
  assert.notCalled(webex.internal.llm.registerAndConnect);
11315
11907
  assert.equal(result, undefined);
11316
11908
  assert.calledOnceWithExactly(
@@ -11320,18 +11912,21 @@ describe('plugin-meetings', () => {
11320
11912
  );
11321
11913
  });
11322
11914
 
11323
-
11324
11915
  it('connect ps data channel if ps started in webinar', async () => {
11325
11916
  meeting.joinedWith = {state: 'JOINED'};
11326
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
11917
+ meeting.locusInfo = {
11918
+ url: 'a url',
11919
+ info: {
11920
+ datachannelUrl: 'a datachannel url',
11921
+ practiceSessionDatachannelUrl: 'a ps datachannel url',
11922
+ },
11923
+ };
11327
11924
  meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
11328
11925
  await meeting.updateLLMConnection();
11329
11926
 
11330
11927
  assert.notCalled(webex.internal.llm.disconnectLLM);
11331
11928
  assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
11332
-
11333
11929
  });
11334
-
11335
11930
  });
11336
11931
 
11337
11932
  describe('#setLocus', () => {
@@ -11749,24 +12344,29 @@ describe('plugin-meetings', () => {
11749
12344
 
11750
12345
  activeSharingId.whiteboard = beneficiaryId;
11751
12346
 
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;
12347
+ eventTrigger.share.push(
12348
+ meeting.webinar.selfIsAttendee
12349
+ ? {
12350
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12351
+ functionName: 'remoteShare',
12352
+ eventPayload: {
12353
+ memberId: null,
12354
+ url,
12355
+ shareInstanceId,
12356
+ annotationInfo: undefined,
12357
+ resourceType: undefined,
12358
+ },
12359
+ }
12360
+ : {
12361
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
12362
+ functionName: 'startWhiteboardShare',
12363
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
12364
+ }
12365
+ );
11769
12366
 
12367
+ shareStatus = meeting.webinar.selfIsAttendee
12368
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12369
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11770
12370
  }
11771
12371
 
11772
12372
  if (eventTrigger.member) {
@@ -11798,24 +12398,29 @@ describe('plugin-meetings', () => {
11798
12398
  newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
11799
12399
  newPayload.current.content.beneficiaryId = otherBeneficiaryId;
11800
12400
 
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;
12401
+ eventTrigger.share.push(
12402
+ meeting.webinar.selfIsAttendee
12403
+ ? {
12404
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12405
+ functionName: 'remoteShare',
12406
+ eventPayload: {
12407
+ memberId: null,
12408
+ url,
12409
+ shareInstanceId,
12410
+ annotationInfo: undefined,
12411
+ resourceType: undefined,
12412
+ },
12413
+ }
12414
+ : {
12415
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
12416
+ functionName: 'startWhiteboardShare',
12417
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
12418
+ }
12419
+ );
11818
12420
 
12421
+ shareStatus = meeting.webinar.selfIsAttendee
12422
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12423
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11819
12424
  } else {
11820
12425
  eventTrigger.share.push({
11821
12426
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
@@ -11945,24 +12550,26 @@ describe('plugin-meetings', () => {
11945
12550
  describe('Whiteboard Share - Webinar Attendee', () => {
11946
12551
  it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
11947
12552
  // Set the webinar attendee flag
11948
- meeting.webinar = { selfIsAttendee: true };
12553
+ meeting.webinar = {selfIsAttendee: true};
11949
12554
  meeting.locusInfo.info.isWebinar = true;
12555
+ meeting.shareCAEventSentStatus.receiveStart = true;
12556
+ meeting.shareCAEventSentStatus.receiveStop = true;
11950
12557
 
11951
12558
  // Step 1: Start sharing whiteboard A
11952
12559
  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
12560
+ blankPayload, // Initial payload
12561
+ true, // isGranting: Granting share
12562
+ false, // isContent: Whiteboard (not content)
12563
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
11957
12564
  RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
11958
12565
  );
11959
12566
 
11960
12567
  // Step 2: Stop sharing whiteboard A
11961
12568
  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
12569
+ data1.payload, // Updated payload from Step 1
12570
+ false, // isGranting: Stopping share
12571
+ false, // isContent: Whiteboard
12572
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
11966
12573
  );
11967
12574
 
11968
12575
  // Validate the payload changes and status updates
@@ -11970,10 +12577,11 @@ describe('plugin-meetings', () => {
11970
12577
 
11971
12578
  // Specific assertions for webinar attendee status
11972
12579
  assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
12580
+ assert.equal(meeting.shareCAEventSentStatus.receiveStart, false);
12581
+ assert.equal(meeting.shareCAEventSentStatus.receiveStop, false);
11973
12582
  });
11974
12583
  });
11975
12584
 
11976
-
11977
12585
  describe('Whiteboard A --> Whiteboard B', () => {
11978
12586
  it('Scenario #1: you share both whiteboards', () => {
11979
12587
  const data1 = generateData(
@@ -12626,6 +13234,31 @@ describe('plugin-meetings', () => {
12626
13234
  });
12627
13235
  });
12628
13236
  });
13237
+
13238
+ describe('handleShareVideoStreamMuteStateChange', () => {
13239
+ it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
13240
+ meeting.isMultistream = true;
13241
+ meeting.statsAnalyzer = {shareVideoEncoderImplementation: 'OpenH264'};
13242
+ meeting.mediaProperties.shareVideoStream = {
13243
+ getSettings: sinon.stub().returns({displaySurface: 'monitor', frameRate: 30}),
13244
+ };
13245
+
13246
+ meeting.handleShareVideoStreamMuteStateChange(true);
13247
+
13248
+ assert.calledOnceWithExactly(
13249
+ Metrics.sendBehavioralMetric,
13250
+ BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE,
13251
+ {
13252
+ correlationId: meeting.correlationId,
13253
+ muted: true,
13254
+ encoderImplementation: 'OpenH264',
13255
+ displaySurface: 'monitor',
13256
+ isMultistream: true,
13257
+ frameRate: 30,
13258
+ }
13259
+ );
13260
+ });
13261
+ });
12629
13262
  });
12630
13263
 
12631
13264
  describe('#startKeepAlive', () => {
@@ -12793,6 +13426,38 @@ describe('plugin-meetings', () => {
12793
13426
  });
12794
13427
  });
12795
13428
 
13429
+ describe('#setPostMeetingDataConsent', () => {
13430
+ it('should have #setPostMeetingDataConsent', () => {
13431
+ assert.exists(meeting.setPostMeetingDataConsent);
13432
+ });
13433
+
13434
+ beforeEach(() => {
13435
+ meeting.meetingRequest.setPostMeetingDataConsent = sinon
13436
+ .stub()
13437
+ .returns(Promise.resolve());
13438
+ });
13439
+
13440
+ [true, false].forEach((accept) => {
13441
+ it(`should send consent with ${accept}`, async () => {
13442
+ const id = uuidv4();
13443
+ meeting.locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${accept}`;
13444
+ meeting.deviceUrl = `https://wdm-test.wbx2.com/wdm/api/v1/devices/${accept}`;
13445
+ meeting.members.selfId = id;
13446
+
13447
+ const consentPromise = meeting.setPostMeetingDataConsent(accept);
13448
+
13449
+ assert.exists(consentPromise.then);
13450
+ await consentPromise;
13451
+ assert.calledOnceWithExactly(meeting.meetingRequest.setPostMeetingDataConsent, {
13452
+ locusUrl: `https://locus-test.wbx2.com/locus/api/v1/loci/${accept}`,
13453
+ postMeetingDataConsent: accept,
13454
+ selfId: id,
13455
+ deviceUrl: `https://wdm-test.wbx2.com/wdm/api/v1/devices/${accept}`,
13456
+ });
13457
+ });
13458
+ });
13459
+ });
13460
+
12796
13461
  describe('#sendReaction', () => {
12797
13462
  it('should have #sendReaction', () => {
12798
13463
  assert.exists(meeting.sendReaction);
@@ -13284,7 +13949,7 @@ describe('plugin-meetings', () => {
13284
13949
  await meeting.roapMessageReceived(fakeMessage);
13285
13950
 
13286
13951
  assert.fail('Expected MultistreamNotSupportedError to be thrown');
13287
- } catch(e) {
13952
+ } catch (e) {
13288
13953
  assert.isTrue(e instanceof MultistreamNotSupportedError);
13289
13954
  }
13290
13955