@webex/plugin-meetings 3.0.0-beta.21 → 3.0.0-beta.211

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 (422) hide show
  1. package/README.md +45 -7
  2. package/dist/annotation/annotation.types.js +7 -0
  3. package/dist/annotation/annotation.types.js.map +1 -0
  4. package/dist/annotation/constants.js +49 -0
  5. package/dist/annotation/constants.js.map +1 -0
  6. package/dist/annotation/index.js +342 -0
  7. package/dist/annotation/index.js.map +1 -0
  8. package/dist/breakouts/breakout.js +114 -14
  9. package/dist/breakouts/breakout.js.map +1 -1
  10. package/dist/breakouts/edit-lock-error.js +52 -0
  11. package/dist/breakouts/edit-lock-error.js.map +1 -0
  12. package/dist/breakouts/events.js +45 -0
  13. package/dist/breakouts/events.js.map +1 -0
  14. package/dist/breakouts/index.js +841 -19
  15. package/dist/breakouts/index.js.map +1 -1
  16. package/dist/breakouts/request.js +78 -0
  17. package/dist/breakouts/request.js.map +1 -0
  18. package/dist/breakouts/utils.js +67 -0
  19. package/dist/breakouts/utils.js.map +1 -0
  20. package/dist/common/errors/webex-errors.js +3 -2
  21. package/dist/common/errors/webex-errors.js.map +1 -1
  22. package/dist/common/logs/logger-proxy.js +1 -1
  23. package/dist/common/logs/logger-proxy.js.map +1 -1
  24. package/dist/common/queue.js +24 -9
  25. package/dist/common/queue.js.map +1 -1
  26. package/dist/config.js +3 -8
  27. package/dist/config.js.map +1 -1
  28. package/dist/constants.js +179 -30
  29. package/dist/constants.js.map +1 -1
  30. package/dist/controls-options-manager/constants.js +14 -0
  31. package/dist/controls-options-manager/constants.js.map +1 -0
  32. package/dist/controls-options-manager/enums.js +27 -0
  33. package/dist/controls-options-manager/enums.js.map +1 -0
  34. package/dist/controls-options-manager/index.js +297 -0
  35. package/dist/controls-options-manager/index.js.map +1 -0
  36. package/dist/controls-options-manager/types.js +7 -0
  37. package/dist/controls-options-manager/types.js.map +1 -0
  38. package/dist/controls-options-manager/util.js +319 -0
  39. package/dist/controls-options-manager/util.js.map +1 -0
  40. package/dist/index.js +106 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/interpretation/collection.js +23 -0
  43. package/dist/interpretation/collection.js.map +1 -0
  44. package/dist/interpretation/index.js +366 -0
  45. package/dist/interpretation/index.js.map +1 -0
  46. package/dist/interpretation/siLanguage.js +25 -0
  47. package/dist/interpretation/siLanguage.js.map +1 -0
  48. package/dist/locus-info/controlsUtils.js +91 -2
  49. package/dist/locus-info/controlsUtils.js.map +1 -1
  50. package/dist/locus-info/index.js +359 -64
  51. package/dist/locus-info/index.js.map +1 -1
  52. package/dist/locus-info/infoUtils.js +7 -1
  53. package/dist/locus-info/infoUtils.js.map +1 -1
  54. package/dist/locus-info/mediaSharesUtils.js +43 -1
  55. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  56. package/dist/locus-info/parser.js +219 -63
  57. package/dist/locus-info/parser.js.map +1 -1
  58. package/dist/locus-info/selfUtils.js +89 -14
  59. package/dist/locus-info/selfUtils.js.map +1 -1
  60. package/dist/media/index.js +48 -135
  61. package/dist/media/index.js.map +1 -1
  62. package/dist/media/properties.js +29 -90
  63. package/dist/media/properties.js.map +1 -1
  64. package/dist/mediaQualityMetrics/config.js +505 -493
  65. package/dist/mediaQualityMetrics/config.js.map +1 -1
  66. package/dist/meeting/in-meeting-actions.js +90 -2
  67. package/dist/meeting/in-meeting-actions.js.map +1 -1
  68. package/dist/meeting/index.js +2770 -2547
  69. package/dist/meeting/index.js.map +1 -1
  70. package/dist/meeting/locusMediaRequest.js +291 -0
  71. package/dist/meeting/locusMediaRequest.js.map +1 -0
  72. package/dist/meeting/muteState.js +229 -124
  73. package/dist/meeting/muteState.js.map +1 -1
  74. package/dist/meeting/request.js +199 -193
  75. package/dist/meeting/request.js.map +1 -1
  76. package/dist/meeting/util.js +532 -414
  77. package/dist/meeting/util.js.map +1 -1
  78. package/dist/meeting-info/index.js +48 -7
  79. package/dist/meeting-info/index.js.map +1 -1
  80. package/dist/meeting-info/meeting-info-v2.js +171 -51
  81. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  82. package/dist/meeting-info/utilv2.js +20 -5
  83. package/dist/meeting-info/utilv2.js.map +1 -1
  84. package/dist/meetings/collection.js +22 -0
  85. package/dist/meetings/collection.js.map +1 -1
  86. package/dist/meetings/index.js +357 -66
  87. package/dist/meetings/index.js.map +1 -1
  88. package/dist/meetings/meetings.types.js +7 -0
  89. package/dist/meetings/meetings.types.js.map +1 -0
  90. package/dist/meetings/request.js +2 -0
  91. package/dist/meetings/request.js.map +1 -1
  92. package/dist/meetings/util.js +88 -1
  93. package/dist/meetings/util.js.map +1 -1
  94. package/dist/member/index.js +49 -0
  95. package/dist/member/index.js.map +1 -1
  96. package/dist/member/types.js +25 -0
  97. package/dist/member/types.js.map +1 -0
  98. package/dist/member/util.js +121 -25
  99. package/dist/member/util.js.map +1 -1
  100. package/dist/members/collection.js +10 -0
  101. package/dist/members/collection.js.map +1 -1
  102. package/dist/members/index.js +86 -5
  103. package/dist/members/index.js.map +1 -1
  104. package/dist/members/request.js +106 -38
  105. package/dist/members/request.js.map +1 -1
  106. package/dist/members/types.js +15 -0
  107. package/dist/members/types.js.map +1 -0
  108. package/dist/members/util.js +316 -233
  109. package/dist/members/util.js.map +1 -1
  110. package/dist/metrics/constants.js +3 -5
  111. package/dist/metrics/constants.js.map +1 -1
  112. package/dist/metrics/index.js +1 -468
  113. package/dist/metrics/index.js.map +1 -1
  114. package/dist/multistream/mediaRequestManager.js +238 -49
  115. package/dist/multistream/mediaRequestManager.js.map +1 -1
  116. package/dist/multistream/receiveSlot.js +49 -16
  117. package/dist/multistream/receiveSlot.js.map +1 -1
  118. package/dist/multistream/receiveSlotManager.js +52 -34
  119. package/dist/multistream/receiveSlotManager.js.map +1 -1
  120. package/dist/multistream/remoteMedia.js +44 -18
  121. package/dist/multistream/remoteMedia.js.map +1 -1
  122. package/dist/multistream/remoteMediaGroup.js +60 -3
  123. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  124. package/dist/multistream/remoteMediaManager.js +173 -59
  125. package/dist/multistream/remoteMediaManager.js.map +1 -1
  126. package/dist/networkQualityMonitor/index.js +4 -2
  127. package/dist/networkQualityMonitor/index.js.map +1 -1
  128. package/dist/reachability/index.js +72 -27
  129. package/dist/reachability/index.js.map +1 -1
  130. package/dist/reachability/request.js +12 -5
  131. package/dist/reachability/request.js.map +1 -1
  132. package/dist/reconnection-manager/index.js +196 -155
  133. package/dist/reconnection-manager/index.js.map +1 -1
  134. package/dist/recording-controller/index.js +21 -2
  135. package/dist/recording-controller/index.js.map +1 -1
  136. package/dist/recording-controller/util.js +9 -8
  137. package/dist/recording-controller/util.js.map +1 -1
  138. package/dist/roap/index.js +21 -29
  139. package/dist/roap/index.js.map +1 -1
  140. package/dist/roap/request.js +110 -89
  141. package/dist/roap/request.js.map +1 -1
  142. package/dist/roap/turnDiscovery.js +93 -36
  143. package/dist/roap/turnDiscovery.js.map +1 -1
  144. package/dist/rtcMetrics/constants.js +12 -0
  145. package/dist/rtcMetrics/constants.js.map +1 -0
  146. package/dist/rtcMetrics/index.js +117 -0
  147. package/dist/rtcMetrics/index.js.map +1 -0
  148. package/dist/statsAnalyzer/global.js +1 -93
  149. package/dist/statsAnalyzer/global.js.map +1 -1
  150. package/dist/statsAnalyzer/index.js +326 -311
  151. package/dist/statsAnalyzer/index.js.map +1 -1
  152. package/dist/statsAnalyzer/mqaUtil.js +90 -53
  153. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  154. package/dist/types/annotation/annotation.types.d.ts +42 -0
  155. package/dist/types/annotation/constants.d.ts +31 -0
  156. package/dist/types/annotation/index.d.ts +117 -0
  157. package/dist/types/breakouts/breakout.d.ts +8 -0
  158. package/dist/types/breakouts/collection.d.ts +5 -0
  159. package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
  160. package/dist/types/breakouts/events.d.ts +8 -0
  161. package/dist/types/breakouts/index.d.ts +5 -0
  162. package/dist/types/breakouts/request.d.ts +22 -0
  163. package/dist/types/breakouts/utils.d.ts +15 -0
  164. package/dist/types/common/browser-detection.d.ts +9 -0
  165. package/dist/types/common/collection.d.ts +48 -0
  166. package/dist/types/common/config.d.ts +2 -0
  167. package/dist/types/common/errors/captcha-error.d.ts +15 -0
  168. package/dist/types/common/errors/intent-to-join.d.ts +16 -0
  169. package/dist/types/common/errors/join-meeting.d.ts +17 -0
  170. package/dist/types/common/errors/media.d.ts +15 -0
  171. package/dist/types/common/errors/parameter.d.ts +15 -0
  172. package/dist/types/common/errors/password-error.d.ts +15 -0
  173. package/dist/types/common/errors/permission.d.ts +14 -0
  174. package/dist/types/common/errors/reconnection-in-progress.d.ts +9 -0
  175. package/dist/types/common/errors/reconnection.d.ts +15 -0
  176. package/dist/types/common/errors/stats.d.ts +15 -0
  177. package/dist/types/common/errors/webex-errors.d.ts +69 -0
  178. package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
  179. package/dist/types/common/events/events-scope.d.ts +17 -0
  180. package/dist/types/common/events/events.d.ts +12 -0
  181. package/dist/types/common/events/trigger-proxy.d.ts +2 -0
  182. package/dist/types/common/events/util.d.ts +2 -0
  183. package/dist/types/common/logs/logger-config.d.ts +2 -0
  184. package/dist/types/common/logs/logger-proxy.d.ts +2 -0
  185. package/dist/types/common/logs/request.d.ts +34 -0
  186. package/dist/types/common/queue.d.ts +34 -0
  187. package/dist/types/config.d.ts +72 -0
  188. package/dist/types/constants.d.ts +1020 -0
  189. package/dist/types/controls-options-manager/constants.d.ts +4 -0
  190. package/dist/types/controls-options-manager/enums.d.ts +15 -0
  191. package/dist/types/controls-options-manager/index.d.ts +136 -0
  192. package/dist/types/controls-options-manager/types.d.ts +43 -0
  193. package/dist/types/controls-options-manager/util.d.ts +1 -0
  194. package/dist/types/index.d.ts +7 -0
  195. package/dist/types/interpretation/collection.d.ts +5 -0
  196. package/dist/types/interpretation/index.d.ts +5 -0
  197. package/dist/types/interpretation/siLanguage.d.ts +5 -0
  198. package/dist/types/locus-info/controlsUtils.d.ts +2 -0
  199. package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
  200. package/dist/types/locus-info/fullState.d.ts +2 -0
  201. package/dist/types/locus-info/hostUtils.d.ts +2 -0
  202. package/dist/types/locus-info/index.d.ts +322 -0
  203. package/dist/types/locus-info/infoUtils.d.ts +2 -0
  204. package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
  205. package/dist/types/locus-info/parser.d.ts +271 -0
  206. package/dist/types/locus-info/selfUtils.d.ts +2 -0
  207. package/dist/types/media/index.d.ts +34 -0
  208. package/dist/types/media/properties.d.ts +93 -0
  209. package/dist/types/media/util.d.ts +2 -0
  210. package/dist/types/mediaQualityMetrics/config.d.ts +365 -0
  211. package/dist/types/meeting/in-meeting-actions.d.ts +163 -0
  212. package/dist/types/meeting/index.d.ts +1482 -0
  213. package/dist/types/meeting/locusMediaRequest.d.ts +72 -0
  214. package/dist/types/meeting/muteState.d.ts +184 -0
  215. package/dist/types/meeting/request.d.ts +257 -0
  216. package/dist/types/meeting/request.type.d.ts +11 -0
  217. package/dist/types/meeting/state.d.ts +9 -0
  218. package/dist/types/meeting/util.d.ts +79 -0
  219. package/dist/types/meeting-info/collection.d.ts +20 -0
  220. package/dist/types/meeting-info/index.d.ts +62 -0
  221. package/dist/types/meeting-info/meeting-info-v2.d.ts +122 -0
  222. package/dist/types/meeting-info/request.d.ts +22 -0
  223. package/dist/types/meeting-info/util.d.ts +2 -0
  224. package/dist/types/meeting-info/utilv2.d.ts +2 -0
  225. package/dist/types/meetings/collection.d.ts +31 -0
  226. package/dist/types/meetings/index.d.ts +367 -0
  227. package/dist/types/meetings/meetings.types.d.ts +4 -0
  228. package/dist/types/meetings/request.d.ts +27 -0
  229. package/dist/types/meetings/util.d.ts +18 -0
  230. package/dist/types/member/index.d.ts +159 -0
  231. package/dist/types/member/types.d.ts +32 -0
  232. package/dist/types/member/util.d.ts +2 -0
  233. package/dist/types/members/collection.d.ts +29 -0
  234. package/dist/types/members/index.d.ts +353 -0
  235. package/dist/types/members/request.d.ts +114 -0
  236. package/dist/types/members/types.d.ts +24 -0
  237. package/dist/types/members/util.d.ts +210 -0
  238. package/dist/types/metrics/constants.d.ts +55 -0
  239. package/dist/types/metrics/index.d.ts +45 -0
  240. package/dist/types/multistream/mediaRequestManager.d.ts +118 -0
  241. package/dist/types/multistream/receiveSlot.d.ts +68 -0
  242. package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
  243. package/dist/types/multistream/remoteMedia.d.ts +72 -0
  244. package/dist/types/multistream/remoteMediaGroup.d.ts +47 -0
  245. package/dist/types/multistream/remoteMediaManager.d.ts +277 -0
  246. package/dist/types/networkQualityMonitor/index.d.ts +70 -0
  247. package/dist/types/personal-meeting-room/index.d.ts +47 -0
  248. package/dist/types/personal-meeting-room/request.d.ts +14 -0
  249. package/dist/types/personal-meeting-room/util.d.ts +2 -0
  250. package/dist/types/reachability/index.d.ts +152 -0
  251. package/dist/types/reachability/request.d.ts +37 -0
  252. package/dist/types/reactions/constants.d.ts +3 -0
  253. package/dist/types/reactions/reactions.d.ts +4 -0
  254. package/dist/types/reactions/reactions.type.d.ts +52 -0
  255. package/dist/types/reconnection-manager/index.d.ts +126 -0
  256. package/dist/types/recording-controller/enums.d.ts +7 -0
  257. package/dist/types/recording-controller/index.d.ts +207 -0
  258. package/dist/types/recording-controller/util.d.ts +14 -0
  259. package/dist/types/roap/index.d.ts +77 -0
  260. package/dist/types/roap/request.d.ts +36 -0
  261. package/dist/types/roap/turnDiscovery.d.ts +91 -0
  262. package/dist/types/rtcMetrics/constants.d.ts +4 -0
  263. package/dist/types/rtcMetrics/index.d.ts +46 -0
  264. package/dist/types/statsAnalyzer/global.d.ts +36 -0
  265. package/dist/types/statsAnalyzer/index.d.ts +200 -0
  266. package/dist/types/statsAnalyzer/mqaUtil.d.ts +24 -0
  267. package/dist/types/transcription/index.d.ts +64 -0
  268. package/package.json +23 -20
  269. package/src/annotation/annotation.types.ts +50 -0
  270. package/src/annotation/constants.ts +36 -0
  271. package/src/annotation/index.ts +328 -0
  272. package/src/breakouts/README.md +44 -14
  273. package/src/breakouts/breakout.ts +87 -9
  274. package/src/breakouts/edit-lock-error.ts +25 -0
  275. package/src/breakouts/events.ts +56 -0
  276. package/src/breakouts/index.ts +710 -10
  277. package/src/breakouts/request.ts +55 -0
  278. package/src/breakouts/utils.ts +57 -0
  279. package/src/common/errors/webex-errors.ts +6 -2
  280. package/src/common/logs/logger-proxy.ts +1 -1
  281. package/src/common/queue.ts +22 -8
  282. package/src/config.ts +2 -7
  283. package/src/constants.ts +165 -21
  284. package/src/controls-options-manager/constants.ts +5 -0
  285. package/src/controls-options-manager/enums.ts +18 -0
  286. package/src/controls-options-manager/index.ts +278 -0
  287. package/src/controls-options-manager/types.ts +59 -0
  288. package/src/controls-options-manager/util.ts +300 -0
  289. package/src/index.ts +39 -0
  290. package/src/interpretation/README.md +60 -0
  291. package/src/interpretation/collection.ts +19 -0
  292. package/src/interpretation/index.ts +332 -0
  293. package/src/interpretation/siLanguage.ts +18 -0
  294. package/src/locus-info/controlsUtils.ts +108 -0
  295. package/src/locus-info/index.ts +383 -61
  296. package/src/locus-info/infoUtils.ts +10 -2
  297. package/src/locus-info/mediaSharesUtils.ts +48 -0
  298. package/src/locus-info/parser.ts +224 -39
  299. package/src/locus-info/selfUtils.ts +81 -5
  300. package/src/media/index.ts +87 -140
  301. package/src/media/properties.ts +49 -90
  302. package/src/mediaQualityMetrics/config.ts +379 -377
  303. package/src/meeting/in-meeting-actions.ts +179 -3
  304. package/src/meeting/index.ts +2099 -2083
  305. package/src/meeting/locusMediaRequest.ts +311 -0
  306. package/src/meeting/muteState.ts +228 -132
  307. package/src/meeting/request.ts +105 -115
  308. package/src/meeting/util.ts +511 -397
  309. package/src/meeting-info/index.ts +54 -8
  310. package/src/meeting-info/meeting-info-v2.ts +148 -14
  311. package/src/meeting-info/utilv2.ts +13 -3
  312. package/src/meetings/collection.ts +20 -0
  313. package/src/meetings/index.ts +392 -84
  314. package/src/meetings/meetings.types.ts +12 -0
  315. package/src/meetings/request.ts +2 -0
  316. package/src/meetings/util.ts +103 -4
  317. package/src/member/index.ts +49 -0
  318. package/src/member/types.ts +38 -0
  319. package/src/member/util.ts +127 -25
  320. package/src/members/collection.ts +8 -0
  321. package/src/members/index.ts +107 -6
  322. package/src/members/request.ts +97 -17
  323. package/src/members/types.ts +28 -0
  324. package/src/members/util.ts +319 -240
  325. package/src/metrics/constants.ts +2 -4
  326. package/src/metrics/index.ts +1 -490
  327. package/src/multistream/mediaRequestManager.ts +289 -79
  328. package/src/multistream/receiveSlot.ts +55 -18
  329. package/src/multistream/receiveSlotManager.ts +46 -24
  330. package/src/multistream/remoteMedia.ts +27 -2
  331. package/src/multistream/remoteMediaGroup.ts +59 -0
  332. package/src/multistream/remoteMediaManager.ts +113 -32
  333. package/src/networkQualityMonitor/index.ts +6 -6
  334. package/src/reachability/index.ts +62 -15
  335. package/src/reachability/request.ts +10 -5
  336. package/src/reconnection-manager/index.ts +68 -43
  337. package/src/recording-controller/index.ts +20 -3
  338. package/src/recording-controller/util.ts +26 -9
  339. package/src/roap/index.ts +21 -30
  340. package/src/roap/request.ts +101 -95
  341. package/src/roap/turnDiscovery.ts +47 -25
  342. package/src/rtcMetrics/constants.ts +3 -0
  343. package/src/rtcMetrics/index.ts +100 -0
  344. package/src/statsAnalyzer/global.ts +1 -94
  345. package/src/statsAnalyzer/index.ts +376 -386
  346. package/src/statsAnalyzer/mqaUtil.ts +100 -99
  347. package/test/integration/spec/converged-space-meetings.js +233 -0
  348. package/test/integration/spec/journey.js +336 -259
  349. package/test/integration/spec/space-meeting.js +77 -4
  350. package/test/unit/spec/annotation/index.ts +418 -0
  351. package/test/unit/spec/breakouts/breakout.ts +142 -24
  352. package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
  353. package/test/unit/spec/breakouts/events.ts +89 -0
  354. package/test/unit/spec/breakouts/index.ts +1545 -48
  355. package/test/unit/spec/breakouts/request.ts +104 -0
  356. package/test/unit/spec/breakouts/utils.js +72 -0
  357. package/test/unit/spec/common/queue.js +31 -2
  358. package/test/unit/spec/controls-options-manager/index.js +287 -0
  359. package/test/unit/spec/controls-options-manager/util.js +582 -0
  360. package/test/unit/spec/fixture/locus.js +1 -0
  361. package/test/unit/spec/interpretation/collection.ts +15 -0
  362. package/test/unit/spec/interpretation/index.ts +589 -0
  363. package/test/unit/spec/interpretation/siLanguage.ts +28 -0
  364. package/test/unit/spec/locus-info/controlsUtils.js +316 -43
  365. package/test/unit/spec/locus-info/index.js +1169 -36
  366. package/test/unit/spec/locus-info/infoUtils.js +37 -15
  367. package/test/unit/spec/locus-info/mediaSharesUtils.ts +22 -0
  368. package/test/unit/spec/locus-info/parser.js +62 -22
  369. package/test/unit/spec/locus-info/selfConstant.js +27 -4
  370. package/test/unit/spec/locus-info/selfUtils.js +208 -17
  371. package/test/unit/spec/media/index.ts +138 -28
  372. package/test/unit/spec/meeting/in-meeting-actions.ts +89 -3
  373. package/test/unit/spec/meeting/index.js +3573 -1663
  374. package/test/unit/spec/meeting/locusMediaRequest.ts +438 -0
  375. package/test/unit/spec/meeting/muteState.js +370 -208
  376. package/test/unit/spec/meeting/request.js +339 -44
  377. package/test/unit/spec/meeting/utils.js +456 -53
  378. package/test/unit/spec/meeting-info/index.js +181 -0
  379. package/test/unit/spec/meeting-info/meetinginfov2.js +383 -5
  380. package/test/unit/spec/meeting-info/utilv2.js +21 -0
  381. package/test/unit/spec/meetings/collection.js +14 -0
  382. package/test/unit/spec/meetings/index.js +867 -125
  383. package/test/unit/spec/meetings/utils.js +206 -2
  384. package/test/unit/spec/member/index.js +58 -4
  385. package/test/unit/spec/member/util.js +479 -35
  386. package/test/unit/spec/members/index.js +319 -1
  387. package/test/unit/spec/members/request.js +206 -27
  388. package/test/unit/spec/members/utils.js +184 -0
  389. package/test/unit/spec/metrics/index.js +1 -50
  390. package/test/unit/spec/multistream/mediaRequestManager.ts +803 -162
  391. package/test/unit/spec/multistream/receiveSlot.ts +72 -13
  392. package/test/unit/spec/multistream/receiveSlotManager.ts +58 -28
  393. package/test/unit/spec/multistream/remoteMedia.ts +30 -0
  394. package/test/unit/spec/multistream/remoteMediaGroup.ts +266 -0
  395. package/test/unit/spec/multistream/remoteMediaManager.ts +318 -0
  396. package/test/unit/spec/networkQualityMonitor/index.js +4 -4
  397. package/test/unit/spec/reachability/index.ts +125 -8
  398. package/test/unit/spec/reachability/request.js +66 -0
  399. package/test/unit/spec/reconnection-manager/index.js +59 -6
  400. package/test/unit/spec/recording-controller/index.js +294 -218
  401. package/test/unit/spec/recording-controller/util.js +223 -96
  402. package/test/unit/spec/roap/index.ts +26 -51
  403. package/test/unit/spec/roap/request.ts +196 -85
  404. package/test/unit/spec/roap/turnDiscovery.ts +30 -7
  405. package/test/unit/spec/rtcMetrics/index.ts +60 -0
  406. package/test/unit/spec/stats-analyzer/index.js +92 -41
  407. package/test/utils/constants.js +9 -0
  408. package/test/utils/integrationTestUtils.js +46 -0
  409. package/test/utils/testUtils.js +0 -45
  410. package/test/utils/webex-config.js +4 -0
  411. package/test/utils/webex-test-users.js +6 -3
  412. package/dist/meeting/effectsState.js +0 -262
  413. package/dist/meeting/effectsState.js.map +0 -1
  414. package/dist/metrics/config.js +0 -299
  415. package/dist/metrics/config.js.map +0 -1
  416. package/dist/multistream/multistreamMedia.js +0 -110
  417. package/dist/multistream/multistreamMedia.js.map +0 -1
  418. package/src/index.js +0 -16
  419. package/src/meeting/effectsState.ts +0 -211
  420. package/src/metrics/config.ts +0 -495
  421. package/src/multistream/multistreamMedia.ts +0 -97
  422. package/test/unit/spec/meeting/effectsState.js +0 -285
@@ -3,6 +3,7 @@ import sinon from 'sinon';
3
3
  import {cloneDeep} from 'lodash';
4
4
  import {assert} from '@webex/test-helper-chai';
5
5
  import MockWebex from '@webex/test-helper-mock-webex';
6
+ import testUtils from '../../../utils/testUtils';
6
7
  import Meetings from '@webex/plugin-meetings';
7
8
  import LocusInfo from '@webex/plugin-meetings/src/locus-info';
8
9
  import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
@@ -16,9 +17,14 @@ import {
16
17
  LOCUSEVENT,
17
18
  EVENTS,
18
19
  DISPLAY_HINTS,
20
+ _CALL_,
21
+ LOCUS,
22
+ MEETING_STATE,
23
+ _MEETING_,
19
24
  } from '../../../../src/constants';
20
25
 
21
26
  import {self, selfWithInactivity} from './selfConstant';
27
+ import uuid from 'uuid';
22
28
 
23
29
  describe('plugin-meetings', () => {
24
30
  describe('LocusInfo index', () => {
@@ -66,8 +72,12 @@ describe('plugin-meetings', () => {
66
72
 
67
73
  beforeEach('setup new controls', () => {
68
74
  newControls = {
75
+ disallowUnmute: {enabled: true},
69
76
  lock: {},
70
77
  meetingFull: {},
78
+ muteOnEntry: {enabled: true},
79
+ raiseHand: {enabled: true},
80
+ reactions: {enabled: true, showDisplayNameWithReactions: true},
71
81
  record: {
72
82
  recording: false,
73
83
  paused: false,
@@ -76,12 +86,14 @@ describe('plugin-meetings', () => {
76
86
  modifiedBy: 'George Kittle',
77
87
  },
78
88
  },
79
- shareControl: {},
89
+ shareControl: {control: 'example-value'},
80
90
  transcribe: {},
91
+ viewTheParticipantList: {enabled: true},
81
92
  meetingContainer: {
82
93
  meetingContainerUrl: 'http://new-url.com',
83
94
  },
84
95
  entryExitTone: {enabled: true, mode: 'foo'},
96
+ video: {enabled: true},
85
97
  };
86
98
  });
87
99
 
@@ -95,6 +107,97 @@ describe('plugin-meetings', () => {
95
107
  assert.equal(locusInfo.controls, newControls);
96
108
  });
97
109
 
110
+ it('should trigger the CONTROLS_MUTE_ON_ENTRY_CHANGED event when necessary', () => {
111
+ locusInfo.controls = {};
112
+ locusInfo.emitScoped = sinon.stub();
113
+ locusInfo.updateControls(newControls);
114
+
115
+ assert.calledWith(
116
+ locusInfo.emitScoped,
117
+ {file: 'locus-info', function: 'updateControls'},
118
+ LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED,
119
+ {state: newControls.muteOnEntry}
120
+ );
121
+ });
122
+
123
+ it('should trigger the CONTROLS_SHARE_CONTROL_CHANGED event when necessary', () => {
124
+ locusInfo.controls = {};
125
+ locusInfo.emitScoped = sinon.stub();
126
+ locusInfo.updateControls(newControls);
127
+
128
+ assert.calledWith(
129
+ locusInfo.emitScoped,
130
+ {file: 'locus-info', function: 'updateControls'},
131
+ LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED,
132
+ {state: newControls.shareControl}
133
+ );
134
+ });
135
+
136
+ it('should trigger the CONTROLS_DISALLOW_UNMUTE_CHANGED event when necessary', () => {
137
+ locusInfo.controls = {};
138
+ locusInfo.emitScoped = sinon.stub();
139
+ locusInfo.updateControls(newControls);
140
+
141
+ assert.calledWith(
142
+ locusInfo.emitScoped,
143
+ {file: 'locus-info', function: 'updateControls'},
144
+ LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED,
145
+ {state: newControls.disallowUnmute}
146
+ );
147
+ });
148
+
149
+ it('should trigger the CONTROLS_REACTIONS_CHANGED event when necessary', () => {
150
+ locusInfo.controls = {};
151
+ locusInfo.emitScoped = sinon.stub();
152
+ locusInfo.updateControls(newControls);
153
+
154
+ assert.calledWith(
155
+ locusInfo.emitScoped,
156
+ {file: 'locus-info', function: 'updateControls'},
157
+ LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED,
158
+ {state: newControls.reactions}
159
+ );
160
+ });
161
+
162
+ it('should trigger the CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED event when necessary', () => {
163
+ locusInfo.controls = {};
164
+ locusInfo.emitScoped = sinon.stub();
165
+ locusInfo.updateControls(newControls);
166
+
167
+ assert.calledWith(
168
+ locusInfo.emitScoped,
169
+ {file: 'locus-info', function: 'updateControls'},
170
+ LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED,
171
+ {state: newControls.viewTheParticipantList}
172
+ );
173
+ });
174
+
175
+ it('should trigger the CONTROLS_RAISE_HAND_CHANGED event when necessary', () => {
176
+ locusInfo.controls = {};
177
+ locusInfo.emitScoped = sinon.stub();
178
+ locusInfo.updateControls(newControls);
179
+
180
+ assert.calledWith(
181
+ locusInfo.emitScoped,
182
+ {file: 'locus-info', function: 'updateControls'},
183
+ LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED,
184
+ {state: newControls.raiseHand}
185
+ );
186
+ });
187
+
188
+ it('should trigger the CONTROLS_VIDEO_CHANGED event when necessary', () => {
189
+ locusInfo.controls = {};
190
+ locusInfo.emitScoped = sinon.stub();
191
+ locusInfo.updateControls(newControls);
192
+
193
+ assert.calledWith(
194
+ locusInfo.emitScoped,
195
+ {file: 'locus-info', function: 'updateControls'},
196
+ LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED,
197
+ {state: newControls.video}
198
+ );
199
+ });
200
+
98
201
  it('should not trigger the CONTROLS_RECORDING_UPDATED event', () => {
99
202
  locusInfo.controls = {};
100
203
  locusInfo.emitScoped = sinon.stub();
@@ -279,9 +382,11 @@ describe('plugin-meetings', () => {
279
382
 
280
383
  it('should update the breakout state', () => {
281
384
  locusInfo.emitScoped = sinon.stub();
282
- newControls.breakout = 'new breakout';
385
+ let tmpStub = sinon.stub(SelfUtils, 'getReplacedBreakoutMoveId').returns('breakoutMoveId');
386
+ newControls.breakout = {breakout: {}};
387
+ let selfInfo = {};
283
388
 
284
- locusInfo.updateControls(newControls);
389
+ locusInfo.updateControls(newControls, selfInfo);
285
390
 
286
391
  assert.calledWith(
287
392
  locusInfo.emitScoped,
@@ -291,7 +396,28 @@ describe('plugin-meetings', () => {
291
396
  },
292
397
  LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED,
293
398
  {
294
- breakout: 'new breakout'
399
+ breakout: newControls.breakout,
400
+ }
401
+ );
402
+ tmpStub.restore();
403
+ });
404
+
405
+ it('should update the interpretation state', () => {
406
+ locusInfo.emitScoped = sinon.stub();
407
+ newControls.interpretation = {siLanguages: [{languageCode: 20, languageName: 'en'}]};
408
+ let selfInfo = {};
409
+
410
+ locusInfo.updateControls(newControls, selfInfo);
411
+
412
+ assert.calledWith(
413
+ locusInfo.emitScoped,
414
+ {
415
+ file: 'locus-info',
416
+ function: 'updateControls',
417
+ },
418
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_INTERPRETATION_UPDATED,
419
+ {
420
+ interpretation: newControls.interpretation,
295
421
  }
296
422
  );
297
423
  });
@@ -417,6 +543,39 @@ describe('plugin-meetings', () => {
417
543
  assert.notEqual(x.args[1], LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED);
418
544
  });
419
545
  });
546
+
547
+ it('should update videoEnabled when changed', () => {
548
+ locusInfo.controls = {};
549
+
550
+ locusInfo.emitScoped = sinon.stub();
551
+ locusInfo.updateControls(newControls);
552
+
553
+ assert.calledWith(
554
+ locusInfo.emitScoped,
555
+ {
556
+ file: 'locus-info',
557
+ function: 'updateControls',
558
+ },
559
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
560
+ {unmuteAllowed: true}
561
+ );
562
+
563
+ assert.equal(mockMeeting.unmuteVideoAllowed, true);
564
+ });
565
+
566
+ it('should not update videoEnabled when unchanged', () => {
567
+ locusInfo.controls = {videoEnabled: true};
568
+
569
+ locusInfo.emitScoped = sinon.stub();
570
+ locusInfo.updateControls(newControls);
571
+
572
+ locusInfo.emitScoped.getCalls().forEach((x) => {
573
+ // check that no calls in emitScoped are for SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED
574
+ assert.notEqual(x.args[1], LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED);
575
+ });
576
+
577
+ assert.equal(mockMeeting.unmuteVideoAllowed, undefined);
578
+ });
420
579
  });
421
580
 
422
581
  describe('#updateParticipants()', () => {
@@ -470,6 +629,7 @@ describe('plugin-meetings', () => {
470
629
  selfIdentity: '123',
471
630
  selfId: '2',
472
631
  hostId: '3',
632
+ isReplace: undefined,
473
633
  }
474
634
  );
475
635
  // note: in a real use case, recordingId, selfId, and hostId would all be the same
@@ -477,6 +637,43 @@ describe('plugin-meetings', () => {
477
637
  // are being correctly grabbed from locusInfo.parsedLocus within updateParticipants
478
638
  });
479
639
 
640
+ it('should call with breakout control info', () => {
641
+ locusInfo.parsedLocus = {
642
+ controls: {
643
+ record: {
644
+ modifiedBy: '1',
645
+ },
646
+ },
647
+ self: {
648
+ selfIdentity: '123',
649
+ selfId: '2',
650
+ },
651
+ host: {
652
+ hostId: '3',
653
+ },
654
+ };
655
+
656
+ locusInfo.emitScoped = sinon.stub();
657
+ locusInfo.updateParticipants({}, true);
658
+
659
+ assert.calledWith(
660
+ locusInfo.emitScoped,
661
+ {
662
+ file: 'locus-info',
663
+ function: 'updateParticipants',
664
+ },
665
+ EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS,
666
+ {
667
+ participants: {},
668
+ recordingId: '1',
669
+ selfIdentity: '123',
670
+ selfId: '2',
671
+ hostId: '3',
672
+ isReplace: true,
673
+ }
674
+ );
675
+ });
676
+
480
677
  it('should update the deltaParticipants object', () => {
481
678
  const prev = locusInfo.deltaParticipants;
482
679
 
@@ -683,6 +880,83 @@ describe('plugin-meetings', () => {
683
880
  );
684
881
  });
685
882
 
883
+ describe('SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED', () => {
884
+ it('should emit event when video muted on entry', () => {
885
+ // usually "previous self" is just undefined when we get first self from locus
886
+ locusInfo.self = undefined;
887
+ const selfWithMutedByOthers = cloneDeep(self);
888
+
889
+ // remoteVideoMuted
890
+ selfWithMutedByOthers.controls.video.muted = true;
891
+
892
+ locusInfo.webex.internal.device.url = self.deviceUrl;
893
+ locusInfo.emitScoped = sinon.stub();
894
+ locusInfo.updateSelf(selfWithMutedByOthers, []);
895
+
896
+ assert.calledWith(
897
+ locusInfo.emitScoped,
898
+ {
899
+ file: 'locus-info',
900
+ function: 'updateSelf',
901
+ },
902
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
903
+ {muted: true}
904
+ );
905
+
906
+ // but sometimes "previous self" is defined, but without controls.audio.muted, so we test this here:
907
+ locusInfo.self = cloneDeep(self);
908
+ locusInfo.self.controls.video = {};
909
+
910
+ locusInfo.updateSelf(selfWithMutedByOthers, []);
911
+ assert.calledWith(
912
+ locusInfo.emitScoped,
913
+ {
914
+ file: 'locus-info',
915
+ function: 'updateSelf',
916
+ },
917
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
918
+ {muted: true}
919
+ );
920
+ });
921
+
922
+ it('should not emit event when not muted on entry', () => {
923
+ locusInfo.self = undefined;
924
+ const selfWithMutedByOthersFalse = cloneDeep(self);
925
+
926
+ selfWithMutedByOthersFalse.controls.video.muted = false;
927
+
928
+ locusInfo.webex.internal.device.url = self.deviceUrl;
929
+ locusInfo.emitScoped = sinon.stub();
930
+ locusInfo.updateSelf(selfWithMutedByOthersFalse, []);
931
+
932
+ // we might get some calls to emitScoped, but we need to check that none of them are for SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED
933
+ locusInfo.emitScoped.getCalls().forEach((x) => {
934
+ assert.notEqual(x.args[1], LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED);
935
+ });
936
+ });
937
+
938
+ it('should emit event when remoteVideoMuted changed', () => {
939
+ locusInfo.self = self;
940
+ const selfWithMutedByOthers = cloneDeep(self);
941
+
942
+ selfWithMutedByOthers.controls.video.muted = true;
943
+
944
+ locusInfo.webex.internal.device.url = self.deviceUrl;
945
+ locusInfo.emitScoped = sinon.stub();
946
+ locusInfo.updateSelf(selfWithMutedByOthers, []);
947
+
948
+ assert.calledWith(
949
+ locusInfo.emitScoped,
950
+ {
951
+ file: 'locus-info',
952
+ function: 'updateSelf',
953
+ },
954
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
955
+ {muted: true}
956
+ );
957
+ });
958
+ });
959
+
686
960
  it('should trigger SELF_MEETING_BREAKOUTS_CHANGED when breakouts changed', () => {
687
961
  locusInfo.self = self;
688
962
  const selfWithBreakoutsChanged = cloneDeep(self);
@@ -701,19 +975,23 @@ describe('plugin-meetings', () => {
701
975
  LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED,
702
976
  {
703
977
  breakoutSessions: {
704
- active: [{
705
- name: 'new name',
706
- groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
707
- sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
708
- sessionType: 'BREAKOUT'
709
- }],
710
- allowed: [{
711
- name: 'Breakout session 2',
712
- groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
713
- sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
714
- sessionType: 'BREAKOUT'
715
- }]
716
- }
978
+ active: [
979
+ {
980
+ name: 'new name',
981
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
982
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
983
+ sessionType: 'BREAKOUT',
984
+ },
985
+ ],
986
+ allowed: [
987
+ {
988
+ name: 'Breakout session 2',
989
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
990
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
991
+ sessionType: 'BREAKOUT',
992
+ },
993
+ ],
994
+ },
717
995
  }
718
996
  );
719
997
  });
@@ -789,6 +1067,8 @@ describe('plugin-meetings', () => {
789
1067
  const selfWithRequestedToUnmute = cloneDeep(self);
790
1068
 
791
1069
  selfWithRequestedToUnmute.controls.audio.requestedToUnmute = true;
1070
+ selfWithRequestedToUnmute.controls.audio.lastModifiedRequestedToUnmute =
1071
+ '2023-06-16T19:25:04.369Z';
792
1072
 
793
1073
  locusInfo.webex.internal.device.url = self.deviceUrl;
794
1074
  locusInfo.emitScoped = sinon.stub();
@@ -943,6 +1223,88 @@ describe('plugin-meetings', () => {
943
1223
  {isSharingBlocked: true}
944
1224
  );
945
1225
  });
1226
+
1227
+ it('should trigger SELF_ROLES_CHANGED if self roles changed', () => {
1228
+ locusInfo.self = self;
1229
+ locusInfo.emitScoped = sinon.stub();
1230
+ const sampleNewSelf = cloneDeep(self);
1231
+ sampleNewSelf.controls.role.roles = [{type: 'COHOST', hasRole: true}];
1232
+
1233
+ locusInfo.updateSelf(sampleNewSelf, []);
1234
+
1235
+ assert.calledWith(
1236
+ locusInfo.emitScoped,
1237
+ {
1238
+ file: 'locus-info',
1239
+ function: 'updateSelf',
1240
+ },
1241
+ LOCUSINFO.EVENTS.SELF_ROLES_CHANGED,
1242
+ {oldRoles: ['PRESENTER'], newRoles: ['COHOST']}
1243
+ );
1244
+ });
1245
+
1246
+ it('should not trigger SELF_ROLES_CHANGED if self roles not changed', () => {
1247
+ locusInfo.self = self;
1248
+ locusInfo.emitScoped = sinon.stub();
1249
+ const sampleNewSelf = cloneDeep(self);
1250
+ sampleNewSelf.controls.role.roles = [{type: 'PRESENTER', hasRole: true}];
1251
+
1252
+ locusInfo.updateSelf(sampleNewSelf, []);
1253
+
1254
+ assert.neverCalledWith(
1255
+ locusInfo.emitScoped,
1256
+ {
1257
+ file: 'locus-info',
1258
+ function: 'updateSelf',
1259
+ },
1260
+ LOCUSINFO.EVENTS.SELF_ROLES_CHANGED,
1261
+ {oldRoles: ['PRESENTER'], newRoles: ['PRESENTER']}
1262
+ );
1263
+ });
1264
+
1265
+ it('should trigger SELF_MEETING_INTERPRETATION_CHANGED if self interpretation info changed', () => {
1266
+ locusInfo.self = self;
1267
+ locusInfo.emitScoped = sinon.stub();
1268
+ const sampleNewSelf = cloneDeep(self);
1269
+ sampleNewSelf.controls.interpretation.targetLanguage = 'it';
1270
+
1271
+ locusInfo.updateSelf(sampleNewSelf, []);
1272
+
1273
+ assert.calledWith(
1274
+ locusInfo.emitScoped,
1275
+ {
1276
+ file: 'locus-info',
1277
+ function: 'updateSelf',
1278
+ },
1279
+ LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
1280
+ {
1281
+ interpretation: sampleNewSelf.controls.interpretation,
1282
+ selfParticipantId: self.id,
1283
+ }
1284
+ );
1285
+ });
1286
+
1287
+ it('should not trigger SELF_MEETING_INTERPRETATION_CHANGED if self interpretation info not changed', () => {
1288
+ locusInfo.self = self;
1289
+ locusInfo.emitScoped = sinon.stub();
1290
+ const sampleNewSelf = cloneDeep(self);
1291
+ sampleNewSelf.controls.interpretation.targetLanguage = 'cn'; // same with previous one
1292
+
1293
+ locusInfo.updateSelf(sampleNewSelf, []);
1294
+
1295
+ assert.neverCalledWith(
1296
+ locusInfo.emitScoped,
1297
+ {
1298
+ file: 'locus-info',
1299
+ function: 'updateSelf',
1300
+ },
1301
+ LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
1302
+ {
1303
+ interpretation: sampleNewSelf.controls.interpretation,
1304
+ selfParticipantId: self.id,
1305
+ }
1306
+ );
1307
+ });
946
1308
  });
947
1309
 
948
1310
  describe('#updateMeetingInfo', () => {
@@ -1016,7 +1378,6 @@ describe('plugin-meetings', () => {
1016
1378
  function: 'updateMeetingInfo',
1017
1379
  },
1018
1380
  LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1019
- {info: locusInfo.parsedLocus.info, self},
1020
1381
  ];
1021
1382
 
1022
1383
  if (expected) {
@@ -1027,15 +1388,58 @@ describe('plugin-meetings', () => {
1027
1388
  locusInfo.emitScoped.resetHistory();
1028
1389
  };
1029
1390
 
1030
- it('emits MEETING_INFO_UPDATED if the info changes', () => {
1391
+ const checkMeetingInfoUpdatedCalledForRoles = (expected) => {
1392
+ const expectedArgs = [
1393
+ locusInfo.emitScoped,
1394
+ {
1395
+ file: 'locus-info',
1396
+ function: 'updateMeetingInfo',
1397
+ },
1398
+ LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1399
+ ];
1400
+
1401
+ if (expected) {
1402
+ assert.calledWith(...expectedArgs);
1403
+ } else {
1404
+ assert.neverCalledWith(...expectedArgs);
1405
+ }
1406
+ locusInfo.emitScoped.resetHistory();
1407
+ };
1408
+
1409
+ it('emits MEETING_INFO_UPDATED and updates the meeting if the info changes', () => {
1031
1410
  const initialInfo = cloneDeep(meetingInfo);
1032
1411
 
1033
- locusInfo.emitScoped = sinon.stub();
1412
+ let expectedMeeting;
1413
+
1414
+ /*
1415
+ When the event is triggered, it is required that the meeting has already
1416
+ been updated. This is why the meeting is being checked within the stubbed event emitter
1417
+ */
1418
+ sinon.stub(locusInfo, 'emitScoped').callsFake(() => {
1419
+ assert.deepEqual(mockMeeting, expectedMeeting);
1420
+ })
1421
+
1034
1422
 
1035
1423
  // set the info initially as locusInfo.info starts as undefined
1424
+ expectedMeeting = {
1425
+ coHost: {
1426
+ LOWER_SOMEONE_ELSES_HAND: true,
1427
+ },
1428
+ isLocked: false,
1429
+ isUnlocked: true,
1430
+ moderator: {
1431
+ LOWER_SOMEONE_ELSES_HAND: true,
1432
+ },
1433
+ policy: {
1434
+ LOCK_STATUS_UNLOCKED: true,
1435
+ ROSTER_IN_MEETING: true,
1436
+ },
1437
+ userDisplayHints: ['ROSTER_IN_MEETING', 'LOCK_STATUS_UNLOCKED'],
1438
+ };
1036
1439
  locusInfo.updateMeetingInfo(initialInfo, self);
1037
1440
 
1038
1441
  // since it was initially undefined, this should trigger the event
1442
+
1039
1443
  checkMeetingInfoUpdatedCalled(true);
1040
1444
 
1041
1445
  const newInfo = cloneDeep(meetingInfo);
@@ -1043,19 +1447,82 @@ describe('plugin-meetings', () => {
1043
1447
  newInfo.displayHints.coHost = [DISPLAY_HINTS.LOCK_CONTROL_LOCK];
1044
1448
 
1045
1449
  // Updating with different info should trigger the event
1450
+ expectedMeeting = {
1451
+ coHost: {
1452
+ LOWER_SOMEONE_ELSES_HAND: true,
1453
+ LOCK_CONTROL_LOCK: true,
1454
+ },
1455
+ isLocked: false,
1456
+ isUnlocked: true,
1457
+ moderator: {
1458
+ LOWER_SOMEONE_ELSES_HAND: true,
1459
+ },
1460
+ policy: {
1461
+ LOCK_STATUS_UNLOCKED: true,
1462
+ ROSTER_IN_MEETING: true,
1463
+ },
1464
+ userDisplayHints: ['ROSTER_IN_MEETING', 'LOCK_STATUS_UNLOCKED'],
1465
+ };
1046
1466
  locusInfo.updateMeetingInfo(newInfo, self);
1047
1467
 
1048
1468
  checkMeetingInfoUpdatedCalled(true);
1049
1469
 
1050
1470
  // update it with the same info
1471
+ expectedMeeting = {
1472
+ coHost: {
1473
+ LOWER_SOMEONE_ELSES_HAND: true,
1474
+ LOCK_CONTROL_LOCK: true,
1475
+ },
1476
+ isLocked: false,
1477
+ isUnlocked: true,
1478
+ moderator: {
1479
+ LOWER_SOMEONE_ELSES_HAND: true,
1480
+ },
1481
+ policy: {
1482
+ LOCK_STATUS_UNLOCKED: true,
1483
+ ROSTER_IN_MEETING: true,
1484
+ },
1485
+ userDisplayHints: ['ROSTER_IN_MEETING', 'LOCK_STATUS_UNLOCKED'],
1486
+ };
1051
1487
  locusInfo.updateMeetingInfo(newInfo, self);
1052
1488
 
1053
1489
  // since the info is the same it should not call trigger the event
1054
1490
  checkMeetingInfoUpdatedCalled(false);
1055
- });
1056
-
1057
- it('gets roles from self if available', () => {
1058
- const initialInfo = cloneDeep(meetingInfo);
1491
+
1492
+ // update it with the same info, but roles changed
1493
+ const updateSelf = cloneDeep(self);
1494
+ updateSelf?.controls?.role?.roles.push({
1495
+ type: 'COHOST',
1496
+ hasRole: true,
1497
+ });
1498
+ expectedMeeting = {
1499
+ coHost: {
1500
+ LOWER_SOMEONE_ELSES_HAND: true,
1501
+ LOCK_CONTROL_LOCK: true,
1502
+ },
1503
+ isLocked: false,
1504
+ isUnlocked: true,
1505
+ moderator: {
1506
+ LOWER_SOMEONE_ELSES_HAND: true,
1507
+ },
1508
+ policy: {
1509
+ LOCK_STATUS_UNLOCKED: true,
1510
+ ROSTER_IN_MEETING: true,
1511
+ },
1512
+ userDisplayHints: [
1513
+ 'ROSTER_IN_MEETING',
1514
+ 'LOCK_STATUS_UNLOCKED',
1515
+ 'LOCK_CONTROL_LOCK',
1516
+ 'LOWER_SOMEONE_ELSES_HAND',
1517
+ ],
1518
+ };
1519
+ locusInfo.updateMeetingInfo(newInfo, updateSelf);
1520
+ // since the info is the same but roles changed, it should call trigger the event
1521
+ checkMeetingInfoUpdatedCalledForRoles(true);
1522
+ });
1523
+
1524
+ it('gets roles from self if available', () => {
1525
+ const initialInfo = cloneDeep(meetingInfo);
1059
1526
 
1060
1527
  const parsedLocusInfo = cloneDeep(locusInfo.parsedLocus.info);
1061
1528
 
@@ -1157,6 +1624,8 @@ describe('plugin-meetings', () => {
1157
1624
  fakeLocus = {
1158
1625
  meeting: true,
1159
1626
  participants: true,
1627
+ url: 'newLocusUrl',
1628
+ syncUrl: 'newSyncUrl',
1160
1629
  };
1161
1630
  });
1162
1631
 
@@ -1205,8 +1674,8 @@ describe('plugin-meetings', () => {
1205
1674
  const newLocus = {
1206
1675
  self: {
1207
1676
  reason: 'MOVED',
1208
- state: 'LEFT'
1209
- }
1677
+ state: 'LEFT',
1678
+ },
1210
1679
  };
1211
1680
 
1212
1681
  locusInfo.updateControls = sinon.stub();
@@ -1258,12 +1727,39 @@ describe('plugin-meetings', () => {
1258
1727
  sandbox.stub(locusInfo, 'updateParticipants');
1259
1728
  sandbox.stub(locusInfo, 'isMeetingActive');
1260
1729
  sandbox.stub(locusInfo, 'handleOneOnOneEvent');
1730
+ sandbox.stub(locusParser, 'isNewFullLocus').returns(true);
1261
1731
 
1262
1732
  locusInfo.onFullLocus(fakeLocus, eventType);
1263
1733
 
1264
1734
  assert.equal(fakeLocus, locusParser.workingCopy);
1265
1735
  });
1266
1736
 
1737
+ it('onFullLocus() does not do anything if the incoming full locus DTO is old', () => {
1738
+ const eventType = 'fakeEvent';
1739
+
1740
+ locusParser.workingCopy = {};
1741
+
1742
+ const oldWorkingCopy = locusParser.workingCopy;
1743
+
1744
+ const spies = [
1745
+ sandbox.stub(locusInfo, 'updateParticipantDeltas'),
1746
+ sandbox.stub(locusInfo, 'updateLocusInfo'),
1747
+ sandbox.stub(locusInfo, 'updateParticipants'),
1748
+ sandbox.stub(locusInfo, 'isMeetingActive'),
1749
+ sandbox.stub(locusInfo, 'handleOneOnOneEvent'),
1750
+ ];
1751
+
1752
+ sandbox.stub(locusParser, 'isNewFullLocus').returns(false);
1753
+
1754
+ locusInfo.onFullLocus(fakeLocus, eventType);
1755
+
1756
+ spies.forEach((spy) => {
1757
+ assert.notCalled(spy);
1758
+ });
1759
+
1760
+ assert.equal(oldWorkingCopy, locusParser.workingCopy);
1761
+ });
1762
+
1267
1763
  it('onDeltaAction applies locus delta data to meeting', () => {
1268
1764
  const action = 'fake action';
1269
1765
  const parsedLoci = 'fake loci';
@@ -1290,33 +1786,82 @@ describe('plugin-meetings', () => {
1290
1786
  assert.calledWith(meeting.locusInfo.onDeltaLocus, fakeLocus);
1291
1787
  });
1292
1788
 
1293
- it('applyLocusDeltaData gets full locus on DESYNC action', () => {
1789
+ it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
1790
+ const {DESYNC} = LocusDeltaParser.loci;
1791
+ const fakeDeltaLocus = {id: 'fake delta locus'};
1792
+ const meeting = {
1793
+ meetingRequest: {
1794
+ getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
1795
+ },
1796
+ locusInfo: {
1797
+ handleLocusDelta: sandbox.stub(),
1798
+ },
1799
+ locusUrl: 'oldLocusUrl',
1800
+ };
1801
+
1802
+ locusInfo.locusParser.workingCopy = {
1803
+ syncUrl: 'oldSyncUrl',
1804
+ };
1805
+
1806
+ // Since we have a promise inside a function we want to test that's not returned,
1807
+ // we will wait and stub it's last function to resolve this waiting promise.
1808
+ // Also ensures .handleLocusDelta() is called before .resume()
1809
+ return new Promise((resolve) => {
1810
+ locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1811
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1812
+ }).then(() => {
1813
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldSyncUrl'});
1814
+
1815
+ assert.calledOnceWithExactly(meeting.locusInfo.handleLocusDelta, fakeDeltaLocus, meeting);
1816
+ assert.calledOnce(locusInfo.locusParser.resume);
1817
+ });
1818
+ });
1819
+
1820
+ it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl (empty response body)', () => {
1294
1821
  const {DESYNC} = LocusDeltaParser.loci;
1295
1822
  const meeting = {
1296
1823
  meetingRequest: {
1297
- getFullLocus: sandbox.stub().resolves(true),
1824
+ getLocusDTO: sandbox.stub().resolves({body: {}}),
1298
1825
  },
1299
1826
  locusInfo: {
1827
+ handleLocusDelta: sandbox.stub(),
1300
1828
  onFullLocus: sandbox.stub(),
1301
1829
  },
1830
+ locusUrl: 'oldLocusUrl',
1302
1831
  };
1303
1832
 
1304
- locusInfo.locusParser.resume = sandbox.stub();
1305
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1833
+ locusInfo.locusParser.workingCopy = {
1834
+ syncUrl: 'oldSyncUrl',
1835
+ };
1306
1836
 
1307
- assert.calledOnce(meeting.meetingRequest.getFullLocus);
1837
+ // Since we have a promise inside a function we want to test that's not returned,
1838
+ // we will wait and stub it's last function to resolve this waiting promise.
1839
+ return new Promise((resolve) => {
1840
+ locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1841
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1842
+ }).then(() => {
1843
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldSyncUrl'});
1844
+
1845
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1846
+ assert.notCalled(meeting.locusInfo.onFullLocus);
1847
+ assert.calledOnce(locusInfo.locusParser.resume);
1848
+ });
1308
1849
  });
1309
1850
 
1310
- it('getFullLocus handles DESYNC action correctly', () => {
1851
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl', () => {
1311
1852
  const {DESYNC} = LocusDeltaParser.loci;
1853
+ const fakeFullLocusDto = {id: 'fake full locus dto'};
1312
1854
  const meeting = {
1313
1855
  meetingRequest: {
1314
- getFullLocus: sandbox.stub().resolves({body: true}),
1856
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
1315
1857
  },
1316
- locusInfo,
1858
+ locusInfo: {
1859
+ onFullLocus: sandbox.stub(),
1860
+ },
1861
+ locusUrl: 'oldLocusUrl',
1317
1862
  };
1318
1863
 
1319
- locusInfo.onFullLocus = sandbox.stub();
1864
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl
1320
1865
 
1321
1866
  // Since we have a promise inside a function we want to test that's not returned,
1322
1867
  // we will wait and stub it's last function to resolve this waiting promise.
@@ -1325,10 +1870,201 @@ describe('plugin-meetings', () => {
1325
1870
  locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1326
1871
  locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1327
1872
  }).then(() => {
1328
- assert.calledOnce(meeting.locusInfo.onFullLocus);
1873
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldLocusUrl'});
1874
+
1875
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
1329
1876
  assert.calledOnce(locusInfo.locusParser.resume);
1330
1877
  });
1331
1878
  });
1879
+
1880
+ it('onDeltaLocus handle delta data', () => {
1881
+ fakeLocus.participants = {};
1882
+ const fakeBreakout = {
1883
+ sessionId: 'sessionId',
1884
+ groupId: 'groupId',
1885
+ };
1886
+
1887
+ fakeLocus.controls = {
1888
+ breakout: fakeBreakout,
1889
+ };
1890
+ locusInfo.controls = {
1891
+ breakout: {
1892
+ sessionId: 'sessionId',
1893
+ groupId: 'groupId',
1894
+ },
1895
+ };
1896
+ locusInfo.updateParticipants = sinon.stub();
1897
+ locusInfo.onDeltaLocus(fakeLocus);
1898
+ assert.calledWith(locusInfo.updateParticipants, {}, false);
1899
+
1900
+ fakeLocus.controls.breakout.sessionId = 'sessionId2';
1901
+ locusInfo.onDeltaLocus(fakeLocus);
1902
+ assert.calledWith(locusInfo.updateParticipants, {}, true);
1903
+ });
1904
+ });
1905
+
1906
+ describe('#updateLocusCache', () => {
1907
+ it('cache it if income locus is main session locus', () => {
1908
+ const locus = {url: 'url'};
1909
+ locusInfo.mainSessionLocusCache = null;
1910
+ locusInfo.updateLocusCache(locus);
1911
+
1912
+ assert.deepEqual(locusInfo.mainSessionLocusCache, locus);
1913
+ });
1914
+
1915
+ it('not cache it if income locus is breakout session locus', () => {
1916
+ const locus = {url: 'url', controls: {breakout: {sessionType: 'BREAKOUT'}}};
1917
+ locusInfo.mainSessionLocusCache = null;
1918
+ locusInfo.updateLocusCache(locus);
1919
+
1920
+ assert.isNull(locusInfo.mainSessionLocusCache);
1921
+ });
1922
+ });
1923
+
1924
+ describe('#getTheLocusToUpdate', () => {
1925
+ it('return the cache locus if return to main session', () => {
1926
+ locusInfo.mainSessionLocusCache = {url: 'url'};
1927
+ locusInfo.controls = {
1928
+ breakout: {
1929
+ sessionType: 'BREAKOUT',
1930
+ },
1931
+ };
1932
+ const newLocus = {
1933
+ controls: {
1934
+ breakout: {
1935
+ sessionType: 'MAIN',
1936
+ },
1937
+ },
1938
+ };
1939
+
1940
+ assert.deepEqual(locusInfo.getTheLocusToUpdate(newLocus), {url: 'url'});
1941
+ });
1942
+
1943
+ it('return the new locus if return to main session but no cache', () => {
1944
+ locusInfo.mainSessionLocusCache = null;
1945
+ locusInfo.controls = {
1946
+ breakout: {
1947
+ sessionType: 'BREAKOUT',
1948
+ },
1949
+ };
1950
+ const newLocus = {
1951
+ controls: {
1952
+ breakout: {
1953
+ sessionType: 'MAIN',
1954
+ },
1955
+ },
1956
+ };
1957
+
1958
+ assert.deepEqual(locusInfo.getTheLocusToUpdate(newLocus), newLocus);
1959
+ });
1960
+
1961
+ it('return the new locus if not return to main session', () => {
1962
+ locusInfo.mainSessionLocusCache = {url: 'url'};
1963
+ locusInfo.controls = {
1964
+ breakout: {
1965
+ sessionType: 'MAIN',
1966
+ },
1967
+ };
1968
+ const newLocus = {
1969
+ controls: {
1970
+ breakout: {
1971
+ sessionType: 'BREAKOUT',
1972
+ },
1973
+ },
1974
+ };
1975
+
1976
+ assert.deepEqual(locusInfo.getTheLocusToUpdate(newLocus), newLocus);
1977
+ });
1978
+ });
1979
+
1980
+ describe('#mergeParticipants', () => {
1981
+ let participants;
1982
+ let sourceParticipants;
1983
+ beforeEach(() => {
1984
+ participants = [{id: '111', status: 'JOINED'}, {id: '222'}];
1985
+ sourceParticipants = [{id: '111', status: 'LEFT'}, {id: '333'}];
1986
+ });
1987
+
1988
+ it('merge the participants, replace it by id if exist in old array', () => {
1989
+ const result = locusInfo.mergeParticipants(participants, sourceParticipants);
1990
+ assert.deepEqual(result, [{id: '111', status: 'LEFT'}, {id: '222'}, {id: '333'}]);
1991
+ });
1992
+
1993
+ it('return new participants if previous participants is empty', () => {
1994
+ const result = locusInfo.mergeParticipants([], sourceParticipants);
1995
+ assert.deepEqual(result, sourceParticipants);
1996
+ });
1997
+
1998
+ it('return new participants if previous participants is null/undefined', () => {
1999
+ let result = locusInfo.mergeParticipants(null, sourceParticipants);
2000
+ assert.deepEqual(result, sourceParticipants);
2001
+
2002
+ result = locusInfo.mergeParticipants(undefined, sourceParticipants);
2003
+ assert.deepEqual(result, sourceParticipants);
2004
+ });
2005
+
2006
+ it('return previous participants if new participants is empty', () => {
2007
+ const result = locusInfo.mergeParticipants(participants, []);
2008
+ assert.deepEqual(result, participants);
2009
+ });
2010
+
2011
+ it('return previous participants if new participants is null/undefined', () => {
2012
+ let result = locusInfo.mergeParticipants(participants, null);
2013
+ assert.deepEqual(result, participants);
2014
+
2015
+ result = locusInfo.mergeParticipants(participants, undefined);
2016
+ assert.deepEqual(result, participants);
2017
+ });
2018
+ });
2019
+
2020
+ describe('#updateMainSessionLocusCache', () => {
2021
+ let cachedLocus;
2022
+ let newLocus;
2023
+ beforeEach(() => {
2024
+ cachedLocus = {
2025
+ controls: {},
2026
+ participants: [],
2027
+ info: {webExMeetingId: 'testId1', topic: 'test'},
2028
+ };
2029
+ newLocus = {
2030
+ self: {},
2031
+ participants: [{id: '111'}],
2032
+ info: {testId: 'testId2', webExMeetingName: 'hello'},
2033
+ };
2034
+ });
2035
+ it('shallow merge new locus into cache', () => {
2036
+ locusInfo.mainSessionLocusCache = cachedLocus;
2037
+ locusInfo.updateMainSessionLocusCache(newLocus);
2038
+
2039
+ assert.deepEqual(locusInfo.mainSessionLocusCache, {
2040
+ controls: {},
2041
+ participants: [{id: '111'}],
2042
+ info: {testId: 'testId2', webExMeetingName: 'hello'},
2043
+ self: {},
2044
+ });
2045
+ });
2046
+
2047
+ it('cache new locus if no cache before', () => {
2048
+ locusInfo.mainSessionLocusCache = null;
2049
+ locusInfo.updateMainSessionLocusCache(newLocus);
2050
+
2051
+ assert.deepEqual(locusInfo.mainSessionLocusCache, newLocus);
2052
+ });
2053
+
2054
+ it('do nothing if new locus is null', () => {
2055
+ locusInfo.mainSessionLocusCache = cachedLocus;
2056
+ locusInfo.updateMainSessionLocusCache(null);
2057
+
2058
+ assert.deepEqual(locusInfo.mainSessionLocusCache, cachedLocus);
2059
+ });
2060
+ });
2061
+
2062
+ describe('#clearMainSessionLocusCache', () => {
2063
+ it('clear main session locus cache', () => {
2064
+ locusInfo.mainSessionLocusCache = {controls: {}};
2065
+ locusInfo.clearMainSessionLocusCache();
2066
+ assert.isNull(locusInfo.mainSessionLocusCache);
2067
+ });
1332
2068
  });
1333
2069
 
1334
2070
  describe('#handleOneonOneEvent', () => {
@@ -1371,5 +2107,402 @@ describe('plugin-meetings', () => {
1371
2107
  );
1372
2108
  });
1373
2109
  });
2110
+
2111
+ describe('#isMeetingActive', () => {
2112
+ it('sends client event correctly for state = inactive', () => {
2113
+ locusInfo.parsedLocus = {
2114
+ fullState: {
2115
+ type: _CALL_,
2116
+ },
2117
+ };
2118
+
2119
+ locusInfo.fullState = {
2120
+ state: LOCUS.STATE.INACTIVE,
2121
+ };
2122
+
2123
+ locusInfo.isMeetingActive();
2124
+
2125
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2126
+ name: 'client.call.remote-ended',
2127
+ options: {
2128
+ meetingId: locusInfo.meetingId,
2129
+ },
2130
+ });
2131
+ });
2132
+
2133
+ it('sends client event correctly for state = PARTNER_LEFT', () => {
2134
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2135
+ locusInfo.parsedLocus = {
2136
+ fullState: {
2137
+ type: _CALL_,
2138
+ },
2139
+ self: {
2140
+ state: MEETING_STATE.STATES.DECLINED,
2141
+ },
2142
+ };
2143
+ locusInfo.isMeetingActive();
2144
+
2145
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2146
+ name: 'client.call.remote-ended',
2147
+ options: {
2148
+ meetingId: locusInfo.meetingId,
2149
+ },
2150
+ });
2151
+ });
2152
+
2153
+ it('sends client event correctly for state = SELF_LEFT', () => {
2154
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2155
+ locusInfo.parsedLocus = {
2156
+ fullState: {
2157
+ type: _CALL_,
2158
+ },
2159
+ self: {
2160
+ state: MEETING_STATE.STATES.LEFT,
2161
+ },
2162
+ };
2163
+
2164
+ locusInfo.isMeetingActive();
2165
+
2166
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2167
+ name: 'client.call.remote-ended',
2168
+ options: {
2169
+ meetingId: locusInfo.meetingId,
2170
+ },
2171
+ });
2172
+ });
2173
+
2174
+ it('sends client event correctly for state = MEETING_INACTIVE_TERMINATING', () => {
2175
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2176
+ locusInfo.parsedLocus = {
2177
+ fullState: {
2178
+ type: _MEETING_,
2179
+ },
2180
+ };
2181
+
2182
+ locusInfo.fullState = {
2183
+ state: LOCUS.STATE.INACTIVE,
2184
+ };
2185
+
2186
+ locusInfo.isMeetingActive();
2187
+
2188
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2189
+ name: 'client.call.remote-ended',
2190
+ options: {
2191
+ meetingId: locusInfo.meetingId,
2192
+ },
2193
+ });
2194
+ });
2195
+
2196
+ it('sends client event correctly for state = FULLSTATE_REMOVED', () => {
2197
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2198
+ locusInfo.parsedLocus = {
2199
+ fullState: {
2200
+ type: _MEETING_,
2201
+ },
2202
+ };
2203
+
2204
+ locusInfo.fullState = {
2205
+ removed: true,
2206
+ };
2207
+
2208
+ locusInfo.isMeetingActive();
2209
+
2210
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2211
+ name: 'client.call.remote-ended',
2212
+ options: {
2213
+ meetingId: locusInfo.meetingId,
2214
+ },
2215
+ });
2216
+ });
2217
+ });
2218
+
2219
+ // semi-integration tests that use real LocusInfo with real Parser
2220
+ // and test various scenarios related to handling out-of-order Locus delta events
2221
+ describe('handling of out-of-order Locus delta events', () => {
2222
+ let clock;
2223
+
2224
+ const generateDeltaEvent = (base, sequence) => {
2225
+ return {
2226
+ baseSequence: {
2227
+ rangeStart: 0,
2228
+ rangeEnd: 0,
2229
+ entries: [base],
2230
+ },
2231
+ sequence: {
2232
+ rangeStart: 0,
2233
+ rangeEnd: 0,
2234
+ entries: [sequence],
2235
+ },
2236
+ syncUrl: `fake sync url for sequence ${sequence}`,
2237
+ self: {
2238
+ person: {
2239
+ id: 'test person id',
2240
+ },
2241
+ },
2242
+ };
2243
+ };
2244
+
2245
+ // a list of example delta events, sorted by time and each event is based on the previous one
2246
+ const deltaEvents = [
2247
+ generateDeltaEvent(10, 20), // 0
2248
+ generateDeltaEvent(20, 30), // 1
2249
+ generateDeltaEvent(30, 40), // 2
2250
+ generateDeltaEvent(40, 50), // 3
2251
+ generateDeltaEvent(50, 60), // 4
2252
+ generateDeltaEvent(60, 70), // 5
2253
+ generateDeltaEvent(70, 80), // 6
2254
+ generateDeltaEvent(80, 90), // 7
2255
+ generateDeltaEvent(90, 100), // 8
2256
+ ];
2257
+
2258
+ let updateLocusInfoStub; // we use this stub to verify that an event has been fully processed
2259
+ let syncRequestStub;
2260
+
2261
+ beforeEach(() => {
2262
+ clock = sinon.useFakeTimers();
2263
+
2264
+ sinon.stub(locusInfo, 'updateParticipantDeltas');
2265
+ sinon.stub(locusInfo, 'updateParticipants');
2266
+ sinon.stub(locusInfo, 'isMeetingActive'),
2267
+ sinon.stub(locusInfo, 'handleOneOnOneEvent'),
2268
+ (updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
2269
+ syncRequestStub = sinon.stub().resolves({body: {}});
2270
+
2271
+ mockMeeting.locusInfo = locusInfo;
2272
+ mockMeeting.locusUrl = 'fake locus url';
2273
+ mockMeeting.meetingRequest = {
2274
+ getLocusDTO: syncRequestStub,
2275
+ };
2276
+
2277
+ locusInfo.onFullLocus({
2278
+ sequence: {
2279
+ rangeStart: 0,
2280
+ rangeEnd: 0,
2281
+ entries: [10],
2282
+ },
2283
+ self: {
2284
+ person: {
2285
+ id: 'test person id',
2286
+ },
2287
+ },
2288
+ });
2289
+
2290
+ updateLocusInfoStub.resetHistory();
2291
+ });
2292
+
2293
+ afterEach(() => {
2294
+ clock.restore();
2295
+ });
2296
+
2297
+ it('queues out-of-order deltas until it receives a correct delta', () => {
2298
+ // send some out-of-order deltas
2299
+ locusInfo.handleLocusDelta(deltaEvents[1], mockMeeting);
2300
+ locusInfo.handleLocusDelta(deltaEvents[4], mockMeeting);
2301
+
2302
+ // they should be queued and not processed
2303
+ assert.notCalled(updateLocusInfoStub);
2304
+
2305
+ // now one of the missing ones, but not the one SDK is really waiting for
2306
+ locusInfo.handleLocusDelta(deltaEvents[2], mockMeeting);
2307
+
2308
+ // still nothing should be processed
2309
+ assert.notCalled(updateLocusInfoStub);
2310
+
2311
+ // now send the one SDK is waiting for
2312
+ locusInfo.handleLocusDelta(deltaEvents[0], mockMeeting);
2313
+
2314
+ // so deltaEvents with indexes 1,2,3 can be processed, but 5 still not, because 4 is missing
2315
+ assert.callCount(updateLocusInfoStub, 3);
2316
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[0]);
2317
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[1]);
2318
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[2]);
2319
+
2320
+ updateLocusInfoStub.resetHistory();
2321
+
2322
+ // now send deltaEvents[4]
2323
+ locusInfo.handleLocusDelta(deltaEvents[3], mockMeeting);
2324
+
2325
+ // and verify deltaEvents[4] and deltaEvents[5] have been processed
2326
+ assert.callCount(updateLocusInfoStub, 2);
2327
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[3]);
2328
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[4]);
2329
+ });
2330
+
2331
+ it('handles out-of-order deltas correctly even if all arrive in reverse order', () => {
2332
+ // send a bunch deltas in reverse order
2333
+ for (let i = 4; i >= 0; i--) {
2334
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2335
+ }
2336
+
2337
+ // they should be queued and then processed in correct order
2338
+ assert.callCount(updateLocusInfoStub, 5);
2339
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[0]);
2340
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[1]);
2341
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[2]);
2342
+ assert.calledWith(updateLocusInfoStub.getCall(3), deltaEvents[3]);
2343
+ assert.calledWith(updateLocusInfoStub.getCall(4), deltaEvents[4]);
2344
+ });
2345
+
2346
+ it('sends a sync request using syncUrl if it receives at least 1 delta event and processes later deltas after sync correctly', async () => {
2347
+ // the test first sends an initial "good" delta
2348
+ const initialDeltaIdx = 0;
2349
+ const initialDelta = deltaEvents[initialDeltaIdx];
2350
+
2351
+ // then it sends a bunch of out-of-order deltas (at least 6 to trigger a sync), last one being lastOooDelta
2352
+ const firstOooDeltaIdx = 2;
2353
+ const lastOooDeltaIdx = 7;
2354
+ const lastOooDelta = deltaEvents[lastOooDeltaIdx];
2355
+
2356
+ // and finally, after the sync it sends another "good" delta
2357
+ const goodDeltaAfterSync = deltaEvents[8];
2358
+
2359
+ const deltaLocusFromSyncResponse = {
2360
+ baseSequence: {
2361
+ rangeStart: 0,
2362
+ rangeEnd: 0,
2363
+ entries: [initialDelta.sequence.entries[0]],
2364
+ },
2365
+ sequence: {
2366
+ rangeStart: 0,
2367
+ rangeEnd: 0,
2368
+ entries: [lastOooDelta.sequence.entries[0]],
2369
+ },
2370
+ syncUrl: `fake sync url for sequence ${lastOooDelta.sequence.entries[0]}`,
2371
+ self: {
2372
+ person: {
2373
+ id: 'test person id',
2374
+ },
2375
+ },
2376
+ };
2377
+
2378
+ syncRequestStub.resolves({
2379
+ body: deltaLocusFromSyncResponse,
2380
+ });
2381
+
2382
+ // send one correct delta so that SDK has the syncUrl
2383
+ locusInfo.handleLocusDelta(initialDelta, mockMeeting);
2384
+
2385
+ updateLocusInfoStub.resetHistory();
2386
+
2387
+ // send 6 out-of-order deltas to trigger a sync (we're skipping deltaEvents[1])
2388
+ for (let i = firstOooDeltaIdx; i <= lastOooDeltaIdx; i++) {
2389
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2390
+ }
2391
+
2392
+ await testUtils.flushPromises();
2393
+
2394
+ // check that sync was done using the correct syncUrl
2395
+ assert.calledOnceWithExactly(syncRequestStub, {url: initialDelta.syncUrl});
2396
+ assert.calledOnceWithExactly(updateLocusInfoStub, deltaLocusFromSyncResponse);
2397
+
2398
+ updateLocusInfoStub.resetHistory();
2399
+
2400
+ // now send another delta - a good one, it should be processed as normal
2401
+ locusInfo.handleLocusDelta(goodDeltaAfterSync, mockMeeting);
2402
+
2403
+ assert.calledOnceWithExactly(updateLocusInfoStub, goodDeltaAfterSync);
2404
+ });
2405
+
2406
+ it('does a sync if blocked on out-of-order deltas for too long', async () => {
2407
+ // stub random so that the timer fires after 12500 ms
2408
+ sinon.stub(Math, 'random').returns(0.5);
2409
+
2410
+ const oooDelta = deltaEvents[3];
2411
+
2412
+ // setup the stubs so that the sync request receives a full DTO with the sequence equal to the out-of-order delta we simulate
2413
+ const fullLocus = {
2414
+ sequence: oooDelta.sequence,
2415
+ };
2416
+ syncRequestStub.resolves({
2417
+ body: fullLocus,
2418
+ });
2419
+
2420
+ // send an out-of-order delta
2421
+ locusInfo.handleLocusDelta(oooDelta, mockMeeting);
2422
+
2423
+ await clock.tickAsync(12499);
2424
+ await testUtils.flushPromises();
2425
+ assert.notCalled(syncRequestStub);
2426
+ assert.notCalled(updateLocusInfoStub);
2427
+
2428
+ await clock.tickAsync(1);
2429
+ await testUtils.flushPromises();
2430
+
2431
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2432
+ assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
2433
+ });
2434
+
2435
+ it('does a sync if out-of-order deltas queue becomes too big', async () => {
2436
+ // setup the stubs so that the sync request receives a full DTO with the sequence equal to the out-of-order delta we simulate
2437
+ const fullLocus = {
2438
+ sequence: deltaEvents[6].sequence,
2439
+ };
2440
+ syncRequestStub.resolves({
2441
+ body: fullLocus,
2442
+ });
2443
+
2444
+ // send 5 deltas, starting from deltaEvents[1] so that SDK is blocked waiting for deltaEvents[0]
2445
+ for (let i = 0; i < 5; i++) {
2446
+ locusInfo.handleLocusDelta(deltaEvents[i + 1], mockMeeting);
2447
+ }
2448
+
2449
+ // nothing should happen, SDK should still be waiting for deltaEvents[0]
2450
+ assert.notCalled(syncRequestStub);
2451
+ assert.notCalled(updateLocusInfoStub);
2452
+
2453
+ // now send one more out-of-order delta to trigger a sync request
2454
+ locusInfo.handleLocusDelta(deltaEvents[6], mockMeeting);
2455
+
2456
+ await testUtils.flushPromises();
2457
+
2458
+ // check sync was done
2459
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2460
+ assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
2461
+ });
2462
+
2463
+ it('processes delta events that are not included in sync response', async () => {
2464
+ // this test sends a bunch of out-of-order deltas, this triggers a sync
2465
+ // but the full locus response doesn't include the last 2 deltas received, so
2466
+ // we check that these 2 deltas are also processed after sync response
2467
+ const fullLocusFromSyncResponse = {
2468
+ baseSequence: {
2469
+ rangeStart: 0,
2470
+ rangeEnd: 0,
2471
+ entries: [deltaEvents[0].sequence.entries[0]],
2472
+ },
2473
+ sequence: {
2474
+ rangeStart: 0,
2475
+ rangeEnd: 0,
2476
+ entries: [deltaEvents[5].sequence.entries[0]],
2477
+ },
2478
+ syncUrl: `fake sync url for sequence ${deltaEvents[5].sequence.entries[0]}`,
2479
+ self: {
2480
+ person: {
2481
+ id: 'test person id',
2482
+ },
2483
+ },
2484
+ };
2485
+
2486
+ syncRequestStub.resolves({
2487
+ body: fullLocusFromSyncResponse,
2488
+ });
2489
+
2490
+ // send at least 6 out-of-order deltas to trigger a sync (we're skipping deltaEvents[0])
2491
+ for (let i = 1; i <= 7; i++) {
2492
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2493
+ }
2494
+
2495
+ await testUtils.flushPromises();
2496
+
2497
+ // check that sync was done
2498
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2499
+
2500
+ // and that remaining deltas from the queue that were not included in full Locus were also processed
2501
+ assert.callCount(updateLocusInfoStub, 3);
2502
+ assert.calledWith(updateLocusInfoStub.getCall(0), fullLocusFromSyncResponse);
2503
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[6]);
2504
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
2505
+ });
2506
+ });
1374
2507
  });
1375
2508
  });