@webex/plugin-meetings 2.60.0 → 2.60.1-next.2

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 (535) hide show
  1. package/README.md +46 -8
  2. package/dist/annotation/annotation.types.d.ts +42 -0
  3. package/dist/annotation/annotation.types.js +7 -0
  4. package/dist/annotation/annotation.types.js.map +1 -0
  5. package/dist/annotation/constants.d.ts +31 -0
  6. package/dist/annotation/constants.js +41 -0
  7. package/dist/annotation/constants.js.map +1 -0
  8. package/dist/annotation/index.d.ts +117 -0
  9. package/dist/annotation/index.js +357 -0
  10. package/dist/annotation/index.js.map +1 -0
  11. package/dist/breakouts/breakout.d.ts +8 -0
  12. package/dist/breakouts/breakout.js +215 -0
  13. package/dist/breakouts/breakout.js.map +1 -0
  14. package/dist/breakouts/collection.d.ts +5 -0
  15. package/dist/breakouts/collection.js +22 -0
  16. package/dist/breakouts/collection.js.map +1 -0
  17. package/dist/breakouts/edit-lock-error.d.ts +15 -0
  18. package/dist/breakouts/edit-lock-error.js +51 -0
  19. package/dist/breakouts/edit-lock-error.js.map +1 -0
  20. package/dist/breakouts/events.d.ts +8 -0
  21. package/dist/breakouts/events.js +44 -0
  22. package/dist/breakouts/events.js.map +1 -0
  23. package/dist/breakouts/index.d.ts +5 -0
  24. package/dist/breakouts/index.js +1047 -0
  25. package/dist/breakouts/index.js.map +1 -0
  26. package/dist/breakouts/request.d.ts +22 -0
  27. package/dist/breakouts/request.js +77 -0
  28. package/dist/breakouts/request.js.map +1 -0
  29. package/dist/breakouts/utils.d.ts +15 -0
  30. package/dist/breakouts/utils.js +64 -0
  31. package/dist/breakouts/utils.js.map +1 -0
  32. package/dist/common/browser-detection.js +2 -3
  33. package/dist/common/browser-detection.js.map +1 -1
  34. package/dist/common/collection.js +3 -4
  35. package/dist/common/collection.js.map +1 -1
  36. package/dist/common/config.js +1 -2
  37. package/dist/common/config.js.map +1 -1
  38. package/dist/common/errors/captcha-error.js +1 -2
  39. package/dist/common/errors/captcha-error.js.map +1 -1
  40. package/dist/common/errors/intent-to-join.js +1 -2
  41. package/dist/common/errors/intent-to-join.js.map +1 -1
  42. package/dist/common/errors/join-meeting.js +1 -2
  43. package/dist/common/errors/join-meeting.js.map +1 -1
  44. package/dist/common/errors/media.js +1 -2
  45. package/dist/common/errors/media.js.map +1 -1
  46. package/dist/common/errors/no-meeting-info.d.ts +14 -0
  47. package/dist/common/errors/no-meeting-info.js +50 -0
  48. package/dist/common/errors/no-meeting-info.js.map +1 -0
  49. package/dist/common/errors/parameter.js +3 -4
  50. package/dist/common/errors/parameter.js.map +1 -1
  51. package/dist/common/errors/password-error.js +1 -2
  52. package/dist/common/errors/password-error.js.map +1 -1
  53. package/dist/common/errors/permission.js +1 -2
  54. package/dist/common/errors/permission.js.map +1 -1
  55. package/dist/common/errors/{reclaim-host-role-error.js → reclaim-host-role-errors.js} +7 -11
  56. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  57. package/dist/common/errors/reconnection-in-progress.js +1 -2
  58. package/dist/common/errors/reconnection-in-progress.js.map +1 -1
  59. package/dist/common/errors/reconnection.js +1 -2
  60. package/dist/common/errors/reconnection.js.map +1 -1
  61. package/dist/common/errors/stats.js +1 -2
  62. package/dist/common/errors/stats.js.map +1 -1
  63. package/dist/common/errors/webex-errors.d.ts +20 -8
  64. package/dist/common/errors/webex-errors.js +48 -28
  65. package/dist/common/errors/webex-errors.js.map +1 -1
  66. package/dist/common/errors/webex-meetings-error.js +1 -2
  67. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  68. package/dist/common/events/events-scope.js +1 -2
  69. package/dist/common/events/events-scope.js.map +1 -1
  70. package/dist/common/events/events.js +1 -2
  71. package/dist/common/events/events.js.map +1 -1
  72. package/dist/common/events/trigger-proxy.js +1 -2
  73. package/dist/common/events/trigger-proxy.js.map +1 -1
  74. package/dist/common/events/util.js +1 -2
  75. package/dist/common/events/util.js.map +1 -1
  76. package/dist/common/logs/logger-config.js +1 -2
  77. package/dist/common/logs/logger-config.js.map +1 -1
  78. package/dist/common/logs/logger-proxy.js +2 -3
  79. package/dist/common/logs/logger-proxy.js.map +1 -1
  80. package/dist/common/logs/request.d.ts +3 -1
  81. package/dist/common/logs/request.js +8 -5
  82. package/dist/common/logs/request.js.map +1 -1
  83. package/dist/common/queue.d.ts +9 -7
  84. package/dist/common/queue.js +22 -9
  85. package/dist/common/queue.js.map +1 -1
  86. package/dist/config.d.ts +6 -7
  87. package/dist/config.js +8 -10
  88. package/dist/config.js.map +1 -1
  89. package/dist/constants.d.ts +217 -97
  90. package/dist/constants.js +416 -441
  91. package/dist/constants.js.map +1 -1
  92. package/dist/controls-options-manager/constants.js +3 -6
  93. package/dist/controls-options-manager/constants.js.map +1 -1
  94. package/dist/controls-options-manager/enums.d.ts +11 -1
  95. package/dist/controls-options-manager/enums.js +15 -6
  96. package/dist/controls-options-manager/enums.js.map +1 -1
  97. package/dist/controls-options-manager/index.d.ts +17 -1
  98. package/dist/controls-options-manager/index.js +127 -38
  99. package/dist/controls-options-manager/index.js.map +1 -1
  100. package/dist/controls-options-manager/types.d.ts +43 -0
  101. package/dist/controls-options-manager/types.js +7 -0
  102. package/dist/controls-options-manager/types.js.map +1 -0
  103. package/dist/controls-options-manager/util.d.ts +1 -7
  104. package/dist/controls-options-manager/util.js +309 -19
  105. package/dist/controls-options-manager/util.js.map +1 -1
  106. package/dist/index.d.ts +6 -3
  107. package/dist/index.js +121 -5
  108. package/dist/index.js.map +1 -1
  109. package/dist/interceptors/index.d.ts +2 -0
  110. package/dist/interceptors/index.js +15 -0
  111. package/dist/interceptors/index.js.map +1 -0
  112. package/dist/interceptors/locusRetry.d.ts +27 -0
  113. package/dist/interceptors/locusRetry.js +94 -0
  114. package/dist/interceptors/locusRetry.js.map +1 -0
  115. package/dist/interpretation/collection.d.ts +5 -0
  116. package/dist/interpretation/collection.js +22 -0
  117. package/dist/interpretation/collection.js.map +1 -0
  118. package/dist/interpretation/index.d.ts +5 -0
  119. package/dist/interpretation/index.js +365 -0
  120. package/dist/interpretation/index.js.map +1 -0
  121. package/dist/interpretation/siLanguage.d.ts +5 -0
  122. package/dist/interpretation/siLanguage.js +24 -0
  123. package/dist/interpretation/siLanguage.js.map +1 -0
  124. package/dist/locus-info/controlsUtils.js +100 -11
  125. package/dist/locus-info/controlsUtils.js.map +1 -1
  126. package/dist/locus-info/embeddedAppsUtils.js +3 -4
  127. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  128. package/dist/locus-info/fullState.js +1 -2
  129. package/dist/locus-info/fullState.js.map +1 -1
  130. package/dist/locus-info/hostUtils.js +1 -2
  131. package/dist/locus-info/hostUtils.js.map +1 -1
  132. package/dist/locus-info/index.d.ts +57 -4
  133. package/dist/locus-info/index.js +425 -84
  134. package/dist/locus-info/index.js.map +1 -1
  135. package/dist/locus-info/infoUtils.js +13 -5
  136. package/dist/locus-info/infoUtils.js.map +1 -1
  137. package/dist/locus-info/mediaSharesUtils.js +58 -3
  138. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  139. package/dist/locus-info/parser.d.ts +66 -6
  140. package/dist/locus-info/parser.js +253 -80
  141. package/dist/locus-info/parser.js.map +1 -1
  142. package/dist/locus-info/selfUtils.js +97 -13
  143. package/dist/locus-info/selfUtils.js.map +1 -1
  144. package/dist/media/index.d.ts +2 -0
  145. package/dist/media/index.js +107 -319
  146. package/dist/media/index.js.map +1 -1
  147. package/dist/media/properties.d.ts +38 -53
  148. package/dist/media/properties.js +96 -153
  149. package/dist/media/properties.js.map +1 -1
  150. package/dist/media/util.js +1 -22
  151. package/dist/media/util.js.map +1 -1
  152. package/dist/mediaQualityMetrics/config.d.ts +234 -230
  153. package/dist/mediaQualityMetrics/config.js +302 -498
  154. package/dist/mediaQualityMetrics/config.js.map +1 -1
  155. package/dist/meeting/in-meeting-actions.d.ts +88 -0
  156. package/dist/meeting/in-meeting-actions.js +94 -3
  157. package/dist/meeting/in-meeting-actions.js.map +1 -1
  158. package/dist/meeting/index.d.ts +591 -494
  159. package/dist/meeting/index.js +4732 -2990
  160. package/dist/meeting/index.js.map +1 -1
  161. package/dist/meeting/locusMediaRequest.d.ts +74 -0
  162. package/dist/meeting/locusMediaRequest.js +291 -0
  163. package/dist/meeting/locusMediaRequest.js.map +1 -0
  164. package/dist/meeting/muteState.d.ts +93 -25
  165. package/dist/meeting/muteState.js +224 -133
  166. package/dist/meeting/muteState.js.map +1 -1
  167. package/dist/meeting/request.d.ts +82 -47
  168. package/dist/meeting/request.js +297 -199
  169. package/dist/meeting/request.js.map +1 -1
  170. package/dist/meeting/request.type.d.ts +11 -0
  171. package/dist/meeting/request.type.js +7 -0
  172. package/dist/meeting/request.type.js.map +1 -0
  173. package/dist/meeting/state.js +1 -2
  174. package/dist/meeting/state.js.map +1 -1
  175. package/dist/meeting/util.d.ts +102 -1
  176. package/dist/meeting/util.js +605 -435
  177. package/dist/meeting/util.js.map +1 -1
  178. package/dist/meeting-info/collection.js +3 -4
  179. package/dist/meeting-info/collection.js.map +1 -1
  180. package/dist/meeting-info/index.d.ts +13 -1
  181. package/dist/meeting-info/index.js +74 -7
  182. package/dist/meeting-info/index.js.map +1 -1
  183. package/dist/meeting-info/meeting-info-v2.d.ts +31 -1
  184. package/dist/meeting-info/meeting-info-v2.js +200 -63
  185. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  186. package/dist/meeting-info/request.js +1 -2
  187. package/dist/meeting-info/request.js.map +1 -1
  188. package/dist/meeting-info/util.js +2 -3
  189. package/dist/meeting-info/util.js.map +1 -1
  190. package/dist/meeting-info/utilv2.js +39 -41
  191. package/dist/meeting-info/utilv2.js.map +1 -1
  192. package/dist/meetings/collection.d.ts +17 -0
  193. package/dist/meetings/collection.js +42 -4
  194. package/dist/meetings/collection.js.map +1 -1
  195. package/dist/meetings/index.d.ts +93 -21
  196. package/dist/meetings/index.js +490 -127
  197. package/dist/meetings/index.js.map +1 -1
  198. package/dist/meetings/meetings.types.d.ts +4 -0
  199. package/dist/meetings/meetings.types.js +7 -0
  200. package/dist/meetings/meetings.types.js.map +1 -0
  201. package/dist/meetings/request.js +4 -3
  202. package/dist/meetings/request.js.map +1 -1
  203. package/dist/meetings/util.js +107 -6
  204. package/dist/meetings/util.js.map +1 -1
  205. package/dist/member/index.d.ts +13 -1
  206. package/dist/member/index.js +45 -2
  207. package/dist/member/index.js.map +1 -1
  208. package/dist/member/member.types.js +3 -4
  209. package/dist/member/member.types.js.map +1 -1
  210. package/dist/member/types.d.ts +32 -0
  211. package/dist/member/types.js +23 -0
  212. package/dist/member/types.js.map +1 -0
  213. package/dist/member/util.js +120 -29
  214. package/dist/member/util.js.map +1 -1
  215. package/dist/members/collection.d.ts +5 -0
  216. package/dist/members/collection.js +11 -2
  217. package/dist/members/collection.js.map +1 -1
  218. package/dist/members/index.d.ts +56 -11
  219. package/dist/members/index.js +174 -47
  220. package/dist/members/index.js.map +1 -1
  221. package/dist/members/request.d.ts +67 -11
  222. package/dist/members/request.js +102 -54
  223. package/dist/members/request.js.map +1 -1
  224. package/dist/members/types.js +3 -4
  225. package/dist/members/types.js.map +1 -1
  226. package/dist/members/util.d.ts +214 -1
  227. package/dist/members/util.js +327 -284
  228. package/dist/members/util.js.map +1 -1
  229. package/dist/metrics/constants.d.ts +15 -6
  230. package/dist/metrics/constants.js +17 -9
  231. package/dist/metrics/constants.js.map +1 -1
  232. package/dist/metrics/index.d.ts +4 -111
  233. package/dist/metrics/index.js +4 -452
  234. package/dist/metrics/index.js.map +1 -1
  235. package/dist/multistream/mediaRequestManager.d.ts +118 -0
  236. package/dist/multistream/mediaRequestManager.js +344 -0
  237. package/dist/multistream/mediaRequestManager.js.map +1 -0
  238. package/dist/multistream/receiveSlot.d.ts +68 -0
  239. package/dist/multistream/receiveSlot.js +200 -0
  240. package/dist/multistream/receiveSlot.js.map +1 -0
  241. package/dist/multistream/receiveSlotManager.d.ts +56 -0
  242. package/dist/multistream/receiveSlotManager.js +174 -0
  243. package/dist/multistream/receiveSlotManager.js.map +1 -0
  244. package/dist/multistream/remoteMedia.d.ts +72 -0
  245. package/dist/multistream/remoteMedia.js +268 -0
  246. package/dist/multistream/remoteMedia.js.map +1 -0
  247. package/dist/multistream/remoteMediaGroup.d.ts +47 -0
  248. package/dist/multistream/remoteMediaGroup.js +267 -0
  249. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  250. package/dist/multistream/remoteMediaManager.d.ts +285 -0
  251. package/dist/multistream/remoteMediaManager.js +1211 -0
  252. package/dist/multistream/remoteMediaManager.js.map +1 -0
  253. package/dist/multistream/sendSlotManager.d.ts +61 -0
  254. package/dist/multistream/sendSlotManager.js +236 -0
  255. package/dist/multistream/sendSlotManager.js.map +1 -0
  256. package/dist/networkQualityMonitor/index.js +5 -4
  257. package/dist/networkQualityMonitor/index.js.map +1 -1
  258. package/dist/personal-meeting-room/index.js +2 -3
  259. package/dist/personal-meeting-room/index.js.map +1 -1
  260. package/dist/personal-meeting-room/request.js +2 -3
  261. package/dist/personal-meeting-room/request.js.map +1 -1
  262. package/dist/personal-meeting-room/util.js +1 -2
  263. package/dist/personal-meeting-room/util.js.map +1 -1
  264. package/dist/reachability/clusterReachability.d.ts +109 -0
  265. package/dist/reachability/clusterReachability.js +357 -0
  266. package/dist/reachability/clusterReachability.js.map +1 -0
  267. package/dist/reachability/index.d.ts +61 -95
  268. package/dist/reachability/index.js +300 -393
  269. package/dist/reachability/index.js.map +1 -1
  270. package/dist/reachability/request.d.ts +7 -3
  271. package/dist/reachability/request.js +18 -10
  272. package/dist/reachability/request.js.map +1 -1
  273. package/dist/reachability/util.d.ts +8 -0
  274. package/dist/reachability/util.js +29 -0
  275. package/dist/reachability/util.js.map +1 -0
  276. package/dist/reactions/constants.d.ts +3 -0
  277. package/dist/reactions/constants.js +12 -0
  278. package/dist/reactions/constants.js.map +1 -0
  279. package/dist/reactions/reactions.d.ts +2 -2
  280. package/dist/reactions/reactions.js +4 -6
  281. package/dist/reactions/reactions.js.map +1 -1
  282. package/dist/reactions/reactions.type.d.ts +23 -3
  283. package/dist/reactions/reactions.type.js +21 -23
  284. package/dist/reactions/reactions.type.js.map +1 -1
  285. package/dist/reconnection-manager/index.d.ts +32 -8
  286. package/dist/reconnection-manager/index.js +282 -231
  287. package/dist/reconnection-manager/index.js.map +1 -1
  288. package/dist/recording-controller/enums.js +4 -5
  289. package/dist/recording-controller/enums.js.map +1 -1
  290. package/dist/recording-controller/index.d.ts +15 -1
  291. package/dist/recording-controller/index.js +57 -46
  292. package/dist/recording-controller/index.js.map +1 -1
  293. package/dist/recording-controller/util.d.ts +5 -4
  294. package/dist/recording-controller/util.js +10 -10
  295. package/dist/recording-controller/util.js.map +1 -1
  296. package/dist/roap/index.d.ts +9 -47
  297. package/dist/roap/index.js +101 -235
  298. package/dist/roap/index.js.map +1 -1
  299. package/dist/roap/request.d.ts +18 -12
  300. package/dist/roap/request.js +126 -180
  301. package/dist/roap/request.js.map +1 -1
  302. package/dist/roap/turnDiscovery.d.ts +27 -16
  303. package/dist/roap/turnDiscovery.js +115 -105
  304. package/dist/roap/turnDiscovery.js.map +1 -1
  305. package/dist/rtcMetrics/constants.d.ts +4 -0
  306. package/dist/rtcMetrics/constants.js +11 -0
  307. package/dist/rtcMetrics/constants.js.map +1 -0
  308. package/dist/rtcMetrics/index.d.ts +54 -0
  309. package/dist/rtcMetrics/index.js +140 -0
  310. package/dist/rtcMetrics/index.js.map +1 -0
  311. package/dist/statsAnalyzer/global.d.ts +1 -83
  312. package/dist/statsAnalyzer/global.js +2 -85
  313. package/dist/statsAnalyzer/global.js.map +1 -1
  314. package/dist/statsAnalyzer/index.d.ts +50 -30
  315. package/dist/statsAnalyzer/index.js +435 -510
  316. package/dist/statsAnalyzer/index.js.map +1 -1
  317. package/dist/statsAnalyzer/mqaUtil.d.ts +8 -6
  318. package/dist/statsAnalyzer/mqaUtil.js +120 -83
  319. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  320. package/dist/transcription/index.js +1 -2
  321. package/dist/transcription/index.js.map +1 -1
  322. package/dist/webinar/collection.d.ts +16 -0
  323. package/dist/webinar/collection.js +43 -0
  324. package/dist/webinar/collection.js.map +1 -0
  325. package/dist/webinar/index.d.ts +5 -0
  326. package/dist/webinar/index.js +68 -0
  327. package/dist/webinar/index.js.map +1 -0
  328. package/package.json +38 -26
  329. package/src/annotation/annotation.types.ts +50 -0
  330. package/src/annotation/constants.ts +36 -0
  331. package/src/annotation/index.ts +328 -0
  332. package/src/breakouts/README.md +220 -0
  333. package/src/breakouts/breakout.ts +188 -0
  334. package/src/breakouts/collection.ts +19 -0
  335. package/src/breakouts/edit-lock-error.ts +25 -0
  336. package/src/breakouts/events.ts +56 -0
  337. package/src/breakouts/index.ts +925 -0
  338. package/src/breakouts/request.ts +55 -0
  339. package/src/breakouts/utils.ts +57 -0
  340. package/src/common/errors/no-meeting-info.ts +24 -0
  341. package/src/common/errors/webex-errors.ts +36 -12
  342. package/src/common/logs/logger-proxy.ts +1 -1
  343. package/src/common/logs/request.ts +5 -1
  344. package/src/common/queue.ts +22 -8
  345. package/src/config.ts +6 -7
  346. package/src/constants.ts +244 -97
  347. package/src/controls-options-manager/enums.ts +12 -0
  348. package/src/controls-options-manager/index.ts +116 -21
  349. package/src/controls-options-manager/types.ts +59 -0
  350. package/src/controls-options-manager/util.ts +294 -14
  351. package/src/index.ts +44 -0
  352. package/src/interceptors/index.ts +3 -0
  353. package/src/interceptors/locusRetry.ts +67 -0
  354. package/src/interpretation/README.md +60 -0
  355. package/src/interpretation/collection.ts +19 -0
  356. package/src/interpretation/index.ts +332 -0
  357. package/src/interpretation/siLanguage.ts +18 -0
  358. package/src/locus-info/controlsUtils.ts +110 -0
  359. package/src/locus-info/index.ts +449 -61
  360. package/src/locus-info/infoUtils.ts +14 -2
  361. package/src/locus-info/mediaSharesUtils.ts +64 -0
  362. package/src/locus-info/parser.ts +258 -47
  363. package/src/locus-info/selfUtils.ts +85 -2
  364. package/src/media/index.ts +153 -370
  365. package/src/media/properties.ts +106 -136
  366. package/src/media/util.ts +0 -21
  367. package/src/mediaQualityMetrics/config.ts +244 -377
  368. package/src/meeting/in-meeting-actions.ts +176 -0
  369. package/src/meeting/index.ts +3944 -2489
  370. package/src/meeting/locusMediaRequest.ts +313 -0
  371. package/src/meeting/muteState.ts +224 -138
  372. package/src/meeting/request.ts +207 -127
  373. package/src/meeting/request.type.ts +13 -0
  374. package/src/meeting/util.ts +590 -423
  375. package/src/meeting-info/index.ts +81 -8
  376. package/src/meeting-info/meeting-info-v2.ts +163 -13
  377. package/src/meeting-info/util.ts +1 -1
  378. package/src/meeting-info/utilv2.ts +28 -28
  379. package/src/meetings/collection.ts +33 -0
  380. package/src/meetings/index.ts +487 -126
  381. package/src/meetings/meetings.types.ts +12 -0
  382. package/src/meetings/request.ts +2 -0
  383. package/src/meetings/util.ts +116 -5
  384. package/src/member/index.ts +43 -1
  385. package/src/member/types.ts +38 -0
  386. package/src/member/util.ts +125 -28
  387. package/src/members/collection.ts +8 -0
  388. package/src/members/index.ts +187 -52
  389. package/src/members/request.ts +87 -27
  390. package/src/members/util.ts +332 -291
  391. package/src/metrics/constants.ts +15 -6
  392. package/src/metrics/index.ts +1 -471
  393. package/src/multistream/mediaRequestManager.ts +440 -0
  394. package/src/multistream/receiveSlot.ts +184 -0
  395. package/src/multistream/receiveSlotManager.ts +166 -0
  396. package/src/multistream/remoteMedia.ts +254 -0
  397. package/src/multistream/remoteMediaGroup.ts +284 -0
  398. package/src/multistream/remoteMediaManager.ts +1145 -0
  399. package/src/multistream/sendSlotManager.ts +170 -0
  400. package/src/networkQualityMonitor/index.ts +6 -6
  401. package/src/reachability/clusterReachability.ts +320 -0
  402. package/src/reachability/index.ts +243 -347
  403. package/src/reachability/request.ts +17 -8
  404. package/src/reachability/util.ts +24 -0
  405. package/src/reactions/constants.ts +4 -0
  406. package/src/reactions/reactions.ts +4 -4
  407. package/src/reactions/reactions.type.ts +30 -4
  408. package/src/reconnection-manager/index.ts +168 -156
  409. package/src/recording-controller/index.ts +20 -3
  410. package/src/recording-controller/util.ts +26 -9
  411. package/src/roap/index.ts +98 -241
  412. package/src/roap/request.ts +74 -148
  413. package/src/roap/turnDiscovery.ts +62 -56
  414. package/src/rtcMetrics/constants.ts +3 -0
  415. package/src/rtcMetrics/index.ts +124 -0
  416. package/src/statsAnalyzer/global.ts +1 -84
  417. package/src/statsAnalyzer/index.ts +477 -643
  418. package/src/statsAnalyzer/mqaUtil.ts +115 -114
  419. package/src/webinar/collection.ts +31 -0
  420. package/src/webinar/index.ts +62 -0
  421. package/test/integration/spec/converged-space-meetings.js +233 -0
  422. package/test/integration/spec/journey.js +320 -264
  423. package/test/integration/spec/space-meeting.js +77 -4
  424. package/test/unit/spec/annotation/index.ts +418 -0
  425. package/test/unit/spec/breakouts/breakout.ts +237 -0
  426. package/test/unit/spec/breakouts/collection.ts +15 -0
  427. package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
  428. package/test/unit/spec/breakouts/events.ts +89 -0
  429. package/test/unit/spec/breakouts/index.ts +1790 -0
  430. package/test/unit/spec/breakouts/request.ts +104 -0
  431. package/test/unit/spec/breakouts/utils.js +72 -0
  432. package/test/unit/spec/common/queue.js +31 -2
  433. package/test/unit/spec/controls-options-manager/index.js +163 -0
  434. package/test/unit/spec/controls-options-manager/util.js +576 -60
  435. package/test/unit/spec/fixture/locus.js +1 -0
  436. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  437. package/test/unit/spec/interpretation/collection.ts +15 -0
  438. package/test/unit/spec/interpretation/index.ts +589 -0
  439. package/test/unit/spec/interpretation/siLanguage.ts +28 -0
  440. package/test/unit/spec/locus-info/controlsUtils.js +323 -30
  441. package/test/unit/spec/locus-info/index.js +1390 -16
  442. package/test/unit/spec/locus-info/infoUtils.js +54 -16
  443. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  444. package/test/unit/spec/locus-info/lib/selfConstant.js +48 -0
  445. package/test/unit/spec/locus-info/mediaSharesUtils.ts +32 -0
  446. package/test/unit/spec/locus-info/parser.js +116 -35
  447. package/test/unit/spec/locus-info/selfUtils.js +275 -0
  448. package/test/unit/spec/media/index.ts +290 -0
  449. package/test/unit/spec/media/properties.ts +75 -84
  450. package/test/unit/spec/meeting/in-meeting-actions.ts +86 -0
  451. package/test/unit/spec/meeting/index.js +8187 -2769
  452. package/test/unit/spec/meeting/locusMediaRequest.ts +442 -0
  453. package/test/unit/spec/meeting/muteState.js +409 -213
  454. package/test/unit/spec/meeting/request.js +512 -42
  455. package/test/unit/spec/meeting/utils.js +741 -24
  456. package/test/unit/spec/meeting-info/index.js +300 -0
  457. package/test/unit/spec/meeting-info/meetinginfov2.js +527 -5
  458. package/test/unit/spec/meeting-info/utilv2.js +21 -0
  459. package/test/unit/spec/meetings/collection.js +26 -0
  460. package/test/unit/spec/meetings/index.js +1313 -243
  461. package/test/unit/spec/meetings/utils.js +202 -2
  462. package/test/unit/spec/member/index.js +32 -9
  463. package/test/unit/spec/member/util.js +499 -61
  464. package/test/unit/spec/members/index.js +394 -5
  465. package/test/unit/spec/members/request.js +206 -27
  466. package/test/unit/spec/members/utils.js +173 -38
  467. package/test/unit/spec/metrics/index.js +1 -50
  468. package/test/unit/spec/multistream/mediaRequestManager.ts +1418 -0
  469. package/test/unit/spec/multistream/receiveSlot.ts +163 -0
  470. package/test/unit/spec/multistream/receiveSlotManager.ts +203 -0
  471. package/test/unit/spec/multistream/remoteMedia.ts +255 -0
  472. package/test/unit/spec/multistream/remoteMediaGroup.ts +662 -0
  473. package/test/unit/spec/multistream/remoteMediaManager.ts +1924 -0
  474. package/test/unit/spec/multistream/sendSlotManager.ts +242 -0
  475. package/test/unit/spec/networkQualityMonitor/index.js +4 -4
  476. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  477. package/test/unit/spec/reachability/index.ts +531 -24
  478. package/test/unit/spec/reachability/request.js +68 -0
  479. package/test/unit/spec/reachability/util.ts +40 -0
  480. package/test/unit/spec/reconnection-manager/index.js +162 -24
  481. package/test/unit/spec/recording-controller/index.js +293 -218
  482. package/test/unit/spec/recording-controller/util.js +223 -96
  483. package/test/unit/spec/roap/index.ts +200 -76
  484. package/test/unit/spec/roap/request.ts +255 -0
  485. package/test/unit/spec/roap/turnDiscovery.ts +86 -48
  486. package/test/unit/spec/rtcMetrics/index.ts +93 -0
  487. package/test/unit/spec/stats-analyzer/index.js +261 -167
  488. package/test/unit/spec/webinar/collection.ts +13 -0
  489. package/test/unit/spec/webinar/index.ts +60 -0
  490. package/test/utils/constants.js +9 -0
  491. package/test/utils/integrationTestUtils.js +46 -0
  492. package/test/utils/testUtils.js +0 -45
  493. package/test/utils/webex-config.js +4 -0
  494. package/test/utils/webex-test-users.js +7 -3
  495. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  496. package/dist/meeting/effectsState.d.ts +0 -42
  497. package/dist/meeting/effectsState.js +0 -260
  498. package/dist/meeting/effectsState.js.map +0 -1
  499. package/dist/metrics/config.d.ts +0 -169
  500. package/dist/metrics/config.js +0 -289
  501. package/dist/metrics/config.js.map +0 -1
  502. package/dist/peer-connection-manager/index.d.ts +0 -6
  503. package/dist/peer-connection-manager/index.js +0 -671
  504. package/dist/peer-connection-manager/index.js.map +0 -1
  505. package/dist/peer-connection-manager/util.d.ts +0 -6
  506. package/dist/peer-connection-manager/util.js +0 -110
  507. package/dist/peer-connection-manager/util.js.map +0 -1
  508. package/dist/roap/collection.d.ts +0 -10
  509. package/dist/roap/collection.js +0 -63
  510. package/dist/roap/collection.js.map +0 -1
  511. package/dist/roap/handler.d.ts +0 -47
  512. package/dist/roap/handler.js +0 -279
  513. package/dist/roap/handler.js.map +0 -1
  514. package/dist/roap/state.d.ts +0 -9
  515. package/dist/roap/state.js +0 -127
  516. package/dist/roap/state.js.map +0 -1
  517. package/dist/roap/util.d.ts +0 -2
  518. package/dist/roap/util.js +0 -76
  519. package/dist/roap/util.js.map +0 -1
  520. package/src/index.js +0 -15
  521. package/src/meeting/effectsState.ts +0 -209
  522. package/src/metrics/config.ts +0 -485
  523. package/src/peer-connection-manager/index.ts +0 -847
  524. package/src/peer-connection-manager/util.ts +0 -119
  525. package/src/roap/collection.ts +0 -62
  526. package/src/roap/handler.ts +0 -294
  527. package/src/roap/state.ts +0 -156
  528. package/src/roap/util.ts +0 -100
  529. package/test/unit/spec/meeting/effectsState.js +0 -281
  530. package/test/unit/spec/peerconnection-manager/index.js +0 -218
  531. package/test/unit/spec/peerconnection-manager/utils.js +0 -49
  532. package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -388
  533. package/test/unit/spec/roap/util.js +0 -30
  534. /package/dist/common/errors/{reclaim-host-role-error.d.ts → reclaim-host-role-errors.d.ts} +0 -0
  535. /package/src/common/errors/{reclaim-host-role-error.ts → reclaim-host-role-errors.ts} +0 -0
@@ -3,12 +3,14 @@ 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';
9
10
  import InfoUtils from '@webex/plugin-meetings/src/locus-info/infoUtils';
10
11
  import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
11
12
  import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
13
+ import Metrics from '@webex/plugin-meetings/src/metrics';
12
14
 
13
15
  import {
14
16
  LOCUSINFO,
@@ -16,6 +18,10 @@ import {
16
18
  LOCUSEVENT,
17
19
  EVENTS,
18
20
  DISPLAY_HINTS,
21
+ _CALL_,
22
+ LOCUS,
23
+ MEETING_STATE,
24
+ _MEETING_,
19
25
  } from '../../../../src/constants';
20
26
 
21
27
  import {self, selfWithInactivity} from './lib/selfConstant';
@@ -33,6 +39,7 @@ describe('plugin-meetings', () => {
33
39
  const locus = {};
34
40
  const meetingId = 'meetingId';
35
41
  let locusInfo;
42
+ let sendBehavioralMetricStub;
36
43
 
37
44
  const webex = new MockWebex({
38
45
  children: {
@@ -59,6 +66,12 @@ describe('plugin-meetings', () => {
59
66
  },
60
67
  },
61
68
  };
69
+
70
+ sendBehavioralMetricStub = sinon.stub(Metrics, 'sendBehavioralMetric');
71
+ });
72
+
73
+ afterEach(() => {
74
+ sinon.restore();
62
75
  });
63
76
 
64
77
  describe('#updateControls', () => {
@@ -66,8 +79,12 @@ describe('plugin-meetings', () => {
66
79
 
67
80
  beforeEach('setup new controls', () => {
68
81
  newControls = {
82
+ disallowUnmute: {enabled: true},
69
83
  lock: {},
70
84
  meetingFull: {},
85
+ muteOnEntry: {enabled: true},
86
+ raiseHand: {enabled: true},
87
+ reactions: {enabled: true, showDisplayNameWithReactions: true},
71
88
  record: {
72
89
  recording: false,
73
90
  paused: false,
@@ -76,12 +93,14 @@ describe('plugin-meetings', () => {
76
93
  modifiedBy: 'George Kittle',
77
94
  },
78
95
  },
79
- shareControl: {},
96
+ shareControl: {control: 'example-value'},
80
97
  transcribe: {},
98
+ viewTheParticipantList: {enabled: true},
81
99
  meetingContainer: {
82
100
  meetingContainerUrl: 'http://new-url.com',
83
101
  },
84
102
  entryExitTone: {enabled: true, mode: 'foo'},
103
+ video: {enabled: true},
85
104
  };
86
105
  });
87
106
 
@@ -95,6 +114,97 @@ describe('plugin-meetings', () => {
95
114
  assert.equal(locusInfo.controls, newControls);
96
115
  });
97
116
 
117
+ it('should trigger the CONTROLS_MUTE_ON_ENTRY_CHANGED event when necessary', () => {
118
+ locusInfo.controls = {};
119
+ locusInfo.emitScoped = sinon.stub();
120
+ locusInfo.updateControls(newControls);
121
+
122
+ assert.calledWith(
123
+ locusInfo.emitScoped,
124
+ {file: 'locus-info', function: 'updateControls'},
125
+ LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED,
126
+ {state: newControls.muteOnEntry}
127
+ );
128
+ });
129
+
130
+ it('should trigger the CONTROLS_SHARE_CONTROL_CHANGED event when necessary', () => {
131
+ locusInfo.controls = {};
132
+ locusInfo.emitScoped = sinon.stub();
133
+ locusInfo.updateControls(newControls);
134
+
135
+ assert.calledWith(
136
+ locusInfo.emitScoped,
137
+ {file: 'locus-info', function: 'updateControls'},
138
+ LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED,
139
+ {state: newControls.shareControl}
140
+ );
141
+ });
142
+
143
+ it('should trigger the CONTROLS_DISALLOW_UNMUTE_CHANGED event when necessary', () => {
144
+ locusInfo.controls = {};
145
+ locusInfo.emitScoped = sinon.stub();
146
+ locusInfo.updateControls(newControls);
147
+
148
+ assert.calledWith(
149
+ locusInfo.emitScoped,
150
+ {file: 'locus-info', function: 'updateControls'},
151
+ LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED,
152
+ {state: newControls.disallowUnmute}
153
+ );
154
+ });
155
+
156
+ it('should trigger the CONTROLS_REACTIONS_CHANGED event when necessary', () => {
157
+ locusInfo.controls = {};
158
+ locusInfo.emitScoped = sinon.stub();
159
+ locusInfo.updateControls(newControls);
160
+
161
+ assert.calledWith(
162
+ locusInfo.emitScoped,
163
+ {file: 'locus-info', function: 'updateControls'},
164
+ LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED,
165
+ {state: newControls.reactions}
166
+ );
167
+ });
168
+
169
+ it('should trigger the CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED event when necessary', () => {
170
+ locusInfo.controls = {};
171
+ locusInfo.emitScoped = sinon.stub();
172
+ locusInfo.updateControls(newControls);
173
+
174
+ assert.calledWith(
175
+ locusInfo.emitScoped,
176
+ {file: 'locus-info', function: 'updateControls'},
177
+ LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED,
178
+ {state: newControls.viewTheParticipantList}
179
+ );
180
+ });
181
+
182
+ it('should trigger the CONTROLS_RAISE_HAND_CHANGED event when necessary', () => {
183
+ locusInfo.controls = {};
184
+ locusInfo.emitScoped = sinon.stub();
185
+ locusInfo.updateControls(newControls);
186
+
187
+ assert.calledWith(
188
+ locusInfo.emitScoped,
189
+ {file: 'locus-info', function: 'updateControls'},
190
+ LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED,
191
+ {state: newControls.raiseHand}
192
+ );
193
+ });
194
+
195
+ it('should trigger the CONTROLS_VIDEO_CHANGED event when necessary', () => {
196
+ locusInfo.controls = {};
197
+ locusInfo.emitScoped = sinon.stub();
198
+ locusInfo.updateControls(newControls);
199
+
200
+ assert.calledWith(
201
+ locusInfo.emitScoped,
202
+ {file: 'locus-info', function: 'updateControls'},
203
+ LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED,
204
+ {state: newControls.video}
205
+ );
206
+ });
207
+
98
208
  it('should not trigger the CONTROLS_RECORDING_UPDATED event', () => {
99
209
  locusInfo.controls = {};
100
210
  locusInfo.emitScoped = sinon.stub();
@@ -277,6 +387,48 @@ describe('plugin-meetings', () => {
277
387
  );
278
388
  });
279
389
 
390
+ it('should update the breakout state', () => {
391
+ locusInfo.emitScoped = sinon.stub();
392
+ let tmpStub = sinon.stub(SelfUtils, 'getReplacedBreakoutMoveId').returns('breakoutMoveId');
393
+ newControls.breakout = {breakout: {}};
394
+ let selfInfo = {};
395
+
396
+ locusInfo.updateControls(newControls, selfInfo);
397
+
398
+ assert.calledWith(
399
+ locusInfo.emitScoped,
400
+ {
401
+ file: 'locus-info',
402
+ function: 'updateControls',
403
+ },
404
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED,
405
+ {
406
+ breakout: newControls.breakout,
407
+ }
408
+ );
409
+ tmpStub.restore();
410
+ });
411
+
412
+ it('should update the interpretation state', () => {
413
+ locusInfo.emitScoped = sinon.stub();
414
+ newControls.interpretation = {siLanguages: [{languageCode: 20, languageName: 'en'}]};
415
+ let selfInfo = {};
416
+
417
+ locusInfo.updateControls(newControls, selfInfo);
418
+
419
+ assert.calledWith(
420
+ locusInfo.emitScoped,
421
+ {
422
+ file: 'locus-info',
423
+ function: 'updateControls',
424
+ },
425
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_INTERPRETATION_UPDATED,
426
+ {
427
+ interpretation: newControls.interpretation,
428
+ }
429
+ );
430
+ });
431
+
280
432
  it('should update the transcript state', () => {
281
433
  locusInfo.emitScoped = sinon.stub();
282
434
  locusInfo.controls = {
@@ -398,6 +550,39 @@ describe('plugin-meetings', () => {
398
550
  assert.notEqual(x.args[1], LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED);
399
551
  });
400
552
  });
553
+
554
+ it('should update videoEnabled when changed', () => {
555
+ locusInfo.controls = {};
556
+
557
+ locusInfo.emitScoped = sinon.stub();
558
+ locusInfo.updateControls(newControls);
559
+
560
+ assert.calledWith(
561
+ locusInfo.emitScoped,
562
+ {
563
+ file: 'locus-info',
564
+ function: 'updateControls',
565
+ },
566
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
567
+ {unmuteAllowed: true}
568
+ );
569
+
570
+ assert.equal(mockMeeting.unmuteVideoAllowed, true);
571
+ });
572
+
573
+ it('should not update videoEnabled when unchanged', () => {
574
+ locusInfo.controls = {videoEnabled: true};
575
+
576
+ locusInfo.emitScoped = sinon.stub();
577
+ locusInfo.updateControls(newControls);
578
+
579
+ locusInfo.emitScoped.getCalls().forEach((x) => {
580
+ // check that no calls in emitScoped are for SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED
581
+ assert.notEqual(x.args[1], LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED);
582
+ });
583
+
584
+ assert.equal(mockMeeting.unmuteVideoAllowed, undefined);
585
+ });
401
586
  });
402
587
 
403
588
  describe('#updateParticipants()', () => {
@@ -451,6 +636,7 @@ describe('plugin-meetings', () => {
451
636
  selfIdentity: '123',
452
637
  selfId: '2',
453
638
  hostId: '3',
639
+ isReplace: undefined,
454
640
  }
455
641
  );
456
642
  // note: in a real use case, recordingId, selfId, and hostId would all be the same
@@ -458,6 +644,43 @@ describe('plugin-meetings', () => {
458
644
  // are being correctly grabbed from locusInfo.parsedLocus within updateParticipants
459
645
  });
460
646
 
647
+ it('should call with breakout control info', () => {
648
+ locusInfo.parsedLocus = {
649
+ controls: {
650
+ record: {
651
+ modifiedBy: '1',
652
+ },
653
+ },
654
+ self: {
655
+ selfIdentity: '123',
656
+ selfId: '2',
657
+ },
658
+ host: {
659
+ hostId: '3',
660
+ },
661
+ };
662
+
663
+ locusInfo.emitScoped = sinon.stub();
664
+ locusInfo.updateParticipants({}, true);
665
+
666
+ assert.calledWith(
667
+ locusInfo.emitScoped,
668
+ {
669
+ file: 'locus-info',
670
+ function: 'updateParticipants',
671
+ },
672
+ EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS,
673
+ {
674
+ participants: {},
675
+ recordingId: '1',
676
+ selfIdentity: '123',
677
+ selfId: '2',
678
+ hostId: '3',
679
+ isReplace: true,
680
+ }
681
+ );
682
+ });
683
+
461
684
  it('should update the deltaParticipants object', () => {
462
685
  const prev = locusInfo.deltaParticipants;
463
686
 
@@ -664,6 +887,122 @@ describe('plugin-meetings', () => {
664
887
  );
665
888
  });
666
889
 
890
+ describe('SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED', () => {
891
+ it('should emit event when video muted on entry', () => {
892
+ // usually "previous self" is just undefined when we get first self from locus
893
+ locusInfo.self = undefined;
894
+ const selfWithMutedByOthers = cloneDeep(self);
895
+
896
+ // remoteVideoMuted
897
+ selfWithMutedByOthers.controls.video.muted = true;
898
+
899
+ locusInfo.webex.internal.device.url = self.deviceUrl;
900
+ locusInfo.emitScoped = sinon.stub();
901
+ locusInfo.updateSelf(selfWithMutedByOthers, []);
902
+
903
+ assert.calledWith(
904
+ locusInfo.emitScoped,
905
+ {
906
+ file: 'locus-info',
907
+ function: 'updateSelf',
908
+ },
909
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
910
+ {muted: true}
911
+ );
912
+
913
+ // but sometimes "previous self" is defined, but without controls.audio.muted, so we test this here:
914
+ locusInfo.self = cloneDeep(self);
915
+ locusInfo.self.controls.video = {};
916
+
917
+ locusInfo.updateSelf(selfWithMutedByOthers, []);
918
+ assert.calledWith(
919
+ locusInfo.emitScoped,
920
+ {
921
+ file: 'locus-info',
922
+ function: 'updateSelf',
923
+ },
924
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
925
+ {muted: true}
926
+ );
927
+ });
928
+
929
+ it('should not emit event when not muted on entry', () => {
930
+ locusInfo.self = undefined;
931
+ const selfWithMutedByOthersFalse = cloneDeep(self);
932
+
933
+ selfWithMutedByOthersFalse.controls.video.muted = false;
934
+
935
+ locusInfo.webex.internal.device.url = self.deviceUrl;
936
+ locusInfo.emitScoped = sinon.stub();
937
+ locusInfo.updateSelf(selfWithMutedByOthersFalse, []);
938
+
939
+ // we might get some calls to emitScoped, but we need to check that none of them are for SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED
940
+ locusInfo.emitScoped.getCalls().forEach((x) => {
941
+ assert.notEqual(x.args[1], LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED);
942
+ });
943
+ });
944
+
945
+ it('should emit event when remoteVideoMuted changed', () => {
946
+ locusInfo.self = self;
947
+ const selfWithMutedByOthers = cloneDeep(self);
948
+
949
+ selfWithMutedByOthers.controls.video.muted = true;
950
+
951
+ locusInfo.webex.internal.device.url = self.deviceUrl;
952
+ locusInfo.emitScoped = sinon.stub();
953
+ locusInfo.updateSelf(selfWithMutedByOthers, []);
954
+
955
+ assert.calledWith(
956
+ locusInfo.emitScoped,
957
+ {
958
+ file: 'locus-info',
959
+ function: 'updateSelf',
960
+ },
961
+ LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
962
+ {muted: true}
963
+ );
964
+ });
965
+ });
966
+
967
+ it('should trigger SELF_MEETING_BREAKOUTS_CHANGED when breakouts changed', () => {
968
+ locusInfo.self = self;
969
+ const selfWithBreakoutsChanged = cloneDeep(self);
970
+
971
+ selfWithBreakoutsChanged.controls.breakout.sessions.active[0].name = 'new name';
972
+
973
+ locusInfo.emitScoped = sinon.stub();
974
+ locusInfo.updateSelf(selfWithBreakoutsChanged, []);
975
+
976
+ assert.calledWith(
977
+ locusInfo.emitScoped,
978
+ {
979
+ file: 'locus-info',
980
+ function: 'updateSelf',
981
+ },
982
+ LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED,
983
+ {
984
+ breakoutSessions: {
985
+ active: [
986
+ {
987
+ name: 'new name',
988
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
989
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
990
+ sessionType: 'BREAKOUT',
991
+ },
992
+ ],
993
+ allowed: [
994
+ {
995
+ name: 'Breakout session 2',
996
+ groupId: '0e73abb8-5584-49d8-be8d-806d2a8247ca',
997
+ sessionId: '1cf41ab1-2e57-4d95-b7e9-5613acddfb0f',
998
+ sessionType: 'BREAKOUT',
999
+ },
1000
+ ],
1001
+ },
1002
+ }
1003
+ );
1004
+ });
1005
+
667
1006
  it('should trigger SELF_REMOTE_MUTE_STATUS_UPDATED if muted and disallowUnmute changed', () => {
668
1007
  locusInfo.self = self;
669
1008
  const selfWithMutedByOthersAndDissalowUnmute = cloneDeep(self);
@@ -735,6 +1074,8 @@ describe('plugin-meetings', () => {
735
1074
  const selfWithRequestedToUnmute = cloneDeep(self);
736
1075
 
737
1076
  selfWithRequestedToUnmute.controls.audio.requestedToUnmute = true;
1077
+ selfWithRequestedToUnmute.controls.audio.lastModifiedRequestedToUnmute =
1078
+ '2023-06-16T19:25:04.369Z';
738
1079
 
739
1080
  locusInfo.webex.internal.device.url = self.deviceUrl;
740
1081
  locusInfo.emitScoped = sinon.stub();
@@ -889,6 +1230,88 @@ describe('plugin-meetings', () => {
889
1230
  {isSharingBlocked: true}
890
1231
  );
891
1232
  });
1233
+
1234
+ it('should trigger SELF_ROLES_CHANGED if self roles changed', () => {
1235
+ locusInfo.self = self;
1236
+ locusInfo.emitScoped = sinon.stub();
1237
+ const sampleNewSelf = cloneDeep(self);
1238
+ sampleNewSelf.controls.role.roles = [{type: 'COHOST', hasRole: true}];
1239
+
1240
+ locusInfo.updateSelf(sampleNewSelf, []);
1241
+
1242
+ assert.calledWith(
1243
+ locusInfo.emitScoped,
1244
+ {
1245
+ file: 'locus-info',
1246
+ function: 'updateSelf',
1247
+ },
1248
+ LOCUSINFO.EVENTS.SELF_ROLES_CHANGED,
1249
+ {oldRoles: ['PRESENTER'], newRoles: ['COHOST']}
1250
+ );
1251
+ });
1252
+
1253
+ it('should not trigger SELF_ROLES_CHANGED if self roles not changed', () => {
1254
+ locusInfo.self = self;
1255
+ locusInfo.emitScoped = sinon.stub();
1256
+ const sampleNewSelf = cloneDeep(self);
1257
+ sampleNewSelf.controls.role.roles = [{type: 'PRESENTER', hasRole: true}];
1258
+
1259
+ locusInfo.updateSelf(sampleNewSelf, []);
1260
+
1261
+ assert.neverCalledWith(
1262
+ locusInfo.emitScoped,
1263
+ {
1264
+ file: 'locus-info',
1265
+ function: 'updateSelf',
1266
+ },
1267
+ LOCUSINFO.EVENTS.SELF_ROLES_CHANGED,
1268
+ {oldRoles: ['PRESENTER'], newRoles: ['PRESENTER']}
1269
+ );
1270
+ });
1271
+
1272
+ it('should trigger SELF_MEETING_INTERPRETATION_CHANGED if self interpretation info changed', () => {
1273
+ locusInfo.self = self;
1274
+ locusInfo.emitScoped = sinon.stub();
1275
+ const sampleNewSelf = cloneDeep(self);
1276
+ sampleNewSelf.controls.interpretation.targetLanguage = 'it';
1277
+
1278
+ locusInfo.updateSelf(sampleNewSelf, []);
1279
+
1280
+ assert.calledWith(
1281
+ locusInfo.emitScoped,
1282
+ {
1283
+ file: 'locus-info',
1284
+ function: 'updateSelf',
1285
+ },
1286
+ LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
1287
+ {
1288
+ interpretation: sampleNewSelf.controls.interpretation,
1289
+ selfParticipantId: self.id,
1290
+ }
1291
+ );
1292
+ });
1293
+
1294
+ it('should not trigger SELF_MEETING_INTERPRETATION_CHANGED if self interpretation info not changed', () => {
1295
+ locusInfo.self = self;
1296
+ locusInfo.emitScoped = sinon.stub();
1297
+ const sampleNewSelf = cloneDeep(self);
1298
+ sampleNewSelf.controls.interpretation.targetLanguage = 'cn'; // same with previous one
1299
+
1300
+ locusInfo.updateSelf(sampleNewSelf, []);
1301
+
1302
+ assert.neverCalledWith(
1303
+ locusInfo.emitScoped,
1304
+ {
1305
+ file: 'locus-info',
1306
+ function: 'updateSelf',
1307
+ },
1308
+ LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
1309
+ {
1310
+ interpretation: sampleNewSelf.controls.interpretation,
1311
+ selfParticipantId: self.id,
1312
+ }
1313
+ );
1314
+ });
892
1315
  });
893
1316
 
894
1317
  describe('#updateMeetingInfo', () => {
@@ -962,7 +1385,6 @@ describe('plugin-meetings', () => {
962
1385
  function: 'updateMeetingInfo',
963
1386
  },
964
1387
  LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
965
- {info: locusInfo.parsedLocus.info, self},
966
1388
  ];
967
1389
 
968
1390
  if (expected) {
@@ -973,15 +1395,58 @@ describe('plugin-meetings', () => {
973
1395
  locusInfo.emitScoped.resetHistory();
974
1396
  };
975
1397
 
976
- it('emits MEETING_INFO_UPDATED if the info changes', () => {
977
- const initialInfo = cloneDeep(meetingInfo);
978
-
979
- locusInfo.emitScoped = sinon.stub();
1398
+ const checkMeetingInfoUpdatedCalledForRoles = (expected) => {
1399
+ const expectedArgs = [
1400
+ locusInfo.emitScoped,
1401
+ {
1402
+ file: 'locus-info',
1403
+ function: 'updateMeetingInfo',
1404
+ },
1405
+ LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1406
+ ];
1407
+
1408
+ if (expected) {
1409
+ assert.calledWith(...expectedArgs);
1410
+ } else {
1411
+ assert.neverCalledWith(...expectedArgs);
1412
+ }
1413
+ locusInfo.emitScoped.resetHistory();
1414
+ };
1415
+
1416
+ it('emits MEETING_INFO_UPDATED and updates the meeting if the info changes', () => {
1417
+ const initialInfo = cloneDeep(meetingInfo);
1418
+
1419
+ let expectedMeeting;
1420
+
1421
+ /*
1422
+ When the event is triggered, it is required that the meeting has already
1423
+ been updated. This is why the meeting is being checked within the stubbed event emitter
1424
+ */
1425
+ sinon.stub(locusInfo, 'emitScoped').callsFake(() => {
1426
+ assert.deepEqual(mockMeeting, expectedMeeting);
1427
+ })
1428
+
980
1429
 
981
1430
  // set the info initially as locusInfo.info starts as undefined
1431
+ expectedMeeting = {
1432
+ coHost: {
1433
+ LOWER_SOMEONE_ELSES_HAND: true,
1434
+ },
1435
+ isLocked: false,
1436
+ isUnlocked: true,
1437
+ moderator: {
1438
+ LOWER_SOMEONE_ELSES_HAND: true,
1439
+ },
1440
+ policy: {
1441
+ LOCK_STATUS_UNLOCKED: true,
1442
+ ROSTER_IN_MEETING: true,
1443
+ },
1444
+ userDisplayHints: ['ROSTER_IN_MEETING', 'LOCK_STATUS_UNLOCKED'],
1445
+ };
982
1446
  locusInfo.updateMeetingInfo(initialInfo, self);
983
1447
 
984
1448
  // since it was initially undefined, this should trigger the event
1449
+
985
1450
  checkMeetingInfoUpdatedCalled(true);
986
1451
 
987
1452
  const newInfo = cloneDeep(meetingInfo);
@@ -989,15 +1454,78 @@ describe('plugin-meetings', () => {
989
1454
  newInfo.displayHints.coHost = [DISPLAY_HINTS.LOCK_CONTROL_LOCK];
990
1455
 
991
1456
  // Updating with different info should trigger the event
1457
+ expectedMeeting = {
1458
+ coHost: {
1459
+ LOWER_SOMEONE_ELSES_HAND: true,
1460
+ LOCK_CONTROL_LOCK: true,
1461
+ },
1462
+ isLocked: false,
1463
+ isUnlocked: true,
1464
+ moderator: {
1465
+ LOWER_SOMEONE_ELSES_HAND: true,
1466
+ },
1467
+ policy: {
1468
+ LOCK_STATUS_UNLOCKED: true,
1469
+ ROSTER_IN_MEETING: true,
1470
+ },
1471
+ userDisplayHints: ['ROSTER_IN_MEETING', 'LOCK_STATUS_UNLOCKED'],
1472
+ };
992
1473
  locusInfo.updateMeetingInfo(newInfo, self);
993
1474
 
994
1475
  checkMeetingInfoUpdatedCalled(true);
995
1476
 
996
1477
  // update it with the same info
1478
+ expectedMeeting = {
1479
+ coHost: {
1480
+ LOWER_SOMEONE_ELSES_HAND: true,
1481
+ LOCK_CONTROL_LOCK: true,
1482
+ },
1483
+ isLocked: false,
1484
+ isUnlocked: true,
1485
+ moderator: {
1486
+ LOWER_SOMEONE_ELSES_HAND: true,
1487
+ },
1488
+ policy: {
1489
+ LOCK_STATUS_UNLOCKED: true,
1490
+ ROSTER_IN_MEETING: true,
1491
+ },
1492
+ userDisplayHints: ['ROSTER_IN_MEETING', 'LOCK_STATUS_UNLOCKED'],
1493
+ };
997
1494
  locusInfo.updateMeetingInfo(newInfo, self);
998
1495
 
999
1496
  // since the info is the same it should not call trigger the event
1000
1497
  checkMeetingInfoUpdatedCalled(false);
1498
+
1499
+ // update it with the same info, but roles changed
1500
+ const updateSelf = cloneDeep(self);
1501
+ updateSelf?.controls?.role?.roles.push({
1502
+ type: 'COHOST',
1503
+ hasRole: true,
1504
+ });
1505
+ expectedMeeting = {
1506
+ coHost: {
1507
+ LOWER_SOMEONE_ELSES_HAND: true,
1508
+ LOCK_CONTROL_LOCK: true,
1509
+ },
1510
+ isLocked: false,
1511
+ isUnlocked: true,
1512
+ moderator: {
1513
+ LOWER_SOMEONE_ELSES_HAND: true,
1514
+ },
1515
+ policy: {
1516
+ LOCK_STATUS_UNLOCKED: true,
1517
+ ROSTER_IN_MEETING: true,
1518
+ },
1519
+ userDisplayHints: [
1520
+ 'ROSTER_IN_MEETING',
1521
+ 'LOCK_STATUS_UNLOCKED',
1522
+ 'LOCK_CONTROL_LOCK',
1523
+ 'LOWER_SOMEONE_ELSES_HAND',
1524
+ ],
1525
+ };
1526
+ locusInfo.updateMeetingInfo(newInfo, updateSelf);
1527
+ // since the info is the same but roles changed, it should call trigger the event
1528
+ checkMeetingInfoUpdatedCalledForRoles(true);
1001
1529
  });
1002
1530
 
1003
1531
  it('gets roles from self if available', () => {
@@ -1103,6 +1631,8 @@ describe('plugin-meetings', () => {
1103
1631
  fakeLocus = {
1104
1632
  meeting: true,
1105
1633
  participants: true,
1634
+ url: 'newLocusUrl',
1635
+ syncUrl: 'newSyncUrl',
1106
1636
  };
1107
1637
  });
1108
1638
 
@@ -1147,6 +1677,55 @@ describe('plugin-meetings', () => {
1147
1677
  assert.isFunction(locusParser.onDeltaAction);
1148
1678
  });
1149
1679
 
1680
+ it('#updateLocusInfo ignores breakout LEFT message', () => {
1681
+ const newLocus = {
1682
+ self: {
1683
+ reason: 'MOVED',
1684
+ state: 'LEFT',
1685
+ },
1686
+ };
1687
+
1688
+ locusInfo.updateControls = sinon.stub();
1689
+ locusInfo.updateConversationUrl = sinon.stub();
1690
+ locusInfo.updateCreated = sinon.stub();
1691
+ locusInfo.updateFullState = sinon.stub();
1692
+ locusInfo.updateHostInfo = sinon.stub();
1693
+ locusInfo.updateMeetingInfo = sinon.stub();
1694
+ locusInfo.updateMediaShares = sinon.stub();
1695
+ locusInfo.updateParticipantsUrl = sinon.stub();
1696
+ locusInfo.updateReplace = sinon.stub();
1697
+ locusInfo.updateSelf = sinon.stub();
1698
+ locusInfo.updateLocusUrl = sinon.stub();
1699
+ locusInfo.updateAclUrl = sinon.stub();
1700
+ locusInfo.updateBasequence = sinon.stub();
1701
+ locusInfo.updateSequence = sinon.stub();
1702
+ locusInfo.updateMemberShip = sinon.stub();
1703
+ locusInfo.updateIdentifiers = sinon.stub();
1704
+ locusInfo.updateEmbeddedApps = sinon.stub();
1705
+ locusInfo.compareAndUpdate = sinon.stub();
1706
+
1707
+ locusInfo.updateLocusInfo(newLocus);
1708
+
1709
+ assert.notCalled(locusInfo.updateControls);
1710
+ assert.notCalled(locusInfo.updateConversationUrl);
1711
+ assert.notCalled(locusInfo.updateCreated);
1712
+ assert.notCalled(locusInfo.updateFullState);
1713
+ assert.notCalled(locusInfo.updateHostInfo);
1714
+ assert.notCalled(locusInfo.updateMeetingInfo);
1715
+ assert.notCalled(locusInfo.updateMediaShares);
1716
+ assert.notCalled(locusInfo.updateParticipantsUrl);
1717
+ assert.notCalled(locusInfo.updateReplace);
1718
+ assert.notCalled(locusInfo.updateSelf);
1719
+ assert.notCalled(locusInfo.updateLocusUrl);
1720
+ assert.notCalled(locusInfo.updateAclUrl);
1721
+ assert.notCalled(locusInfo.updateBasequence);
1722
+ assert.notCalled(locusInfo.updateSequence);
1723
+ assert.notCalled(locusInfo.updateMemberShip);
1724
+ assert.notCalled(locusInfo.updateIdentifiers);
1725
+ assert.notCalled(locusInfo.updateEmbeddedApps);
1726
+ assert.notCalled(locusInfo.compareAndUpdate);
1727
+ });
1728
+
1150
1729
  it('onFullLocus() updates the working-copy of locus parser', () => {
1151
1730
  const eventType = 'fakeEvent';
1152
1731
 
@@ -1155,12 +1734,39 @@ describe('plugin-meetings', () => {
1155
1734
  sandbox.stub(locusInfo, 'updateParticipants');
1156
1735
  sandbox.stub(locusInfo, 'isMeetingActive');
1157
1736
  sandbox.stub(locusInfo, 'handleOneOnOneEvent');
1737
+ sandbox.stub(locusParser, 'isNewFullLocus').returns(true);
1158
1738
 
1159
1739
  locusInfo.onFullLocus(fakeLocus, eventType);
1160
1740
 
1161
1741
  assert.equal(fakeLocus, locusParser.workingCopy);
1162
1742
  });
1163
1743
 
1744
+ it('onFullLocus() does not do anything if the incoming full locus DTO is old', () => {
1745
+ const eventType = 'fakeEvent';
1746
+
1747
+ locusParser.workingCopy = {};
1748
+
1749
+ const oldWorkingCopy = locusParser.workingCopy;
1750
+
1751
+ const spies = [
1752
+ sandbox.stub(locusInfo, 'updateParticipantDeltas'),
1753
+ sandbox.stub(locusInfo, 'updateLocusInfo'),
1754
+ sandbox.stub(locusInfo, 'updateParticipants'),
1755
+ sandbox.stub(locusInfo, 'isMeetingActive'),
1756
+ sandbox.stub(locusInfo, 'handleOneOnOneEvent'),
1757
+ ];
1758
+
1759
+ sandbox.stub(locusParser, 'isNewFullLocus').returns(false);
1760
+
1761
+ locusInfo.onFullLocus(fakeLocus, eventType);
1762
+
1763
+ spies.forEach((spy) => {
1764
+ assert.notCalled(spy);
1765
+ });
1766
+
1767
+ assert.equal(oldWorkingCopy, locusParser.workingCopy);
1768
+ });
1769
+
1164
1770
  it('onDeltaAction applies locus delta data to meeting', () => {
1165
1771
  const action = 'fake action';
1166
1772
  const parsedLoci = 'fake loci';
@@ -1187,33 +1793,82 @@ describe('plugin-meetings', () => {
1187
1793
  assert.calledWith(meeting.locusInfo.onDeltaLocus, fakeLocus);
1188
1794
  });
1189
1795
 
1190
- it('applyLocusDeltaData gets full locus on DESYNC action', () => {
1796
+ it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
1191
1797
  const {DESYNC} = LocusDeltaParser.loci;
1798
+ const fakeDeltaLocus = {id: 'fake delta locus'};
1192
1799
  const meeting = {
1193
1800
  meetingRequest: {
1194
- getFullLocus: sandbox.stub().resolves(true),
1801
+ getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
1195
1802
  },
1196
1803
  locusInfo: {
1804
+ handleLocusDelta: sandbox.stub(),
1805
+ },
1806
+ locusUrl: 'oldLocusUrl',
1807
+ };
1808
+
1809
+ locusInfo.locusParser.workingCopy = {
1810
+ syncUrl: 'oldSyncUrl',
1811
+ };
1812
+
1813
+ // Since we have a promise inside a function we want to test that's not returned,
1814
+ // we will wait and stub it's last function to resolve this waiting promise.
1815
+ // Also ensures .handleLocusDelta() is called before .resume()
1816
+ return new Promise((resolve) => {
1817
+ locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1818
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1819
+ }).then(() => {
1820
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldSyncUrl'});
1821
+
1822
+ assert.calledOnceWithExactly(meeting.locusInfo.handleLocusDelta, fakeDeltaLocus, meeting);
1823
+ assert.calledOnce(locusInfo.locusParser.resume);
1824
+ });
1825
+ });
1826
+
1827
+ it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl (empty response body)', () => {
1828
+ const {DESYNC} = LocusDeltaParser.loci;
1829
+ const meeting = {
1830
+ meetingRequest: {
1831
+ getLocusDTO: sandbox.stub().resolves({body: {}}),
1832
+ },
1833
+ locusInfo: {
1834
+ handleLocusDelta: sandbox.stub(),
1197
1835
  onFullLocus: sandbox.stub(),
1198
1836
  },
1837
+ locusUrl: 'oldLocusUrl',
1199
1838
  };
1200
1839
 
1201
- locusInfo.locusParser.resume = sandbox.stub();
1202
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1840
+ locusInfo.locusParser.workingCopy = {
1841
+ syncUrl: 'oldSyncUrl',
1842
+ };
1203
1843
 
1204
- assert.calledOnce(meeting.meetingRequest.getFullLocus);
1844
+ // Since we have a promise inside a function we want to test that's not returned,
1845
+ // we will wait and stub it's last function to resolve this waiting promise.
1846
+ return new Promise((resolve) => {
1847
+ locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1848
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1849
+ }).then(() => {
1850
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldSyncUrl'});
1851
+
1852
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1853
+ assert.notCalled(meeting.locusInfo.onFullLocus);
1854
+ assert.calledOnce(locusInfo.locusParser.resume);
1855
+ });
1205
1856
  });
1206
1857
 
1207
- it('getFullLocus handles DESYNC action correctly', () => {
1858
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl', () => {
1208
1859
  const {DESYNC} = LocusDeltaParser.loci;
1860
+ const fakeFullLocusDto = {id: 'fake full locus dto'};
1209
1861
  const meeting = {
1210
1862
  meetingRequest: {
1211
- getFullLocus: sandbox.stub().resolves({body: true}),
1863
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
1212
1864
  },
1213
- locusInfo,
1865
+ locusInfo: {
1866
+ onFullLocus: sandbox.stub(),
1867
+ },
1868
+ locusUrl: 'oldLocusUrl',
1214
1869
  };
1215
1870
 
1216
- locusInfo.onFullLocus = sandbox.stub();
1871
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl
1217
1872
 
1218
1873
  // Since we have a promise inside a function we want to test that's not returned,
1219
1874
  // we will wait and stub it's last function to resolve this waiting promise.
@@ -1222,10 +1877,330 @@ describe('plugin-meetings', () => {
1222
1877
  locusInfo.locusParser.resume = sandbox.stub().callsFake(() => resolve());
1223
1878
  locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1224
1879
  }).then(() => {
1225
- assert.calledOnce(meeting.locusInfo.onFullLocus);
1880
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'oldLocusUrl'});
1881
+
1882
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
1226
1883
  assert.calledOnce(locusInfo.locusParser.resume);
1227
1884
  });
1228
1885
  });
1886
+
1887
+ it('applyLocusDeltaData handles LOCUS_URL_CHANGED action correctly', () => {
1888
+ const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
1889
+ const fakeDeltaLocus = {id: 'fake delta locus'};
1890
+ const meeting = {
1891
+ meetingRequest: {
1892
+ getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
1893
+ },
1894
+ locusInfo: {
1895
+ handleLocusDelta: sandbox.stub(),
1896
+ },
1897
+ locusUrl: 'current locus url',
1898
+ };
1899
+
1900
+ locusInfo.locusParser.workingCopy = {
1901
+ syncUrl: 'current sync url',
1902
+ };
1903
+
1904
+ locusInfo.applyLocusDeltaData(LOCUS_URL_CHANGED, fakeLocus, meeting);
1905
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'current sync url'});
1906
+ });
1907
+
1908
+ describe('edge cases for sync failing', () => {
1909
+ const {DESYNC} = LocusDeltaParser.loci;
1910
+ const fakeFullLocusDto = {id: 'fake full locus dto'};
1911
+ let meeting;
1912
+
1913
+ beforeEach(() => {
1914
+ sinon.stub(locusInfo.locusParser, 'resume');
1915
+ sinon.stub(webex.meetings, 'destroy');
1916
+
1917
+ meeting = {
1918
+ meetingRequest: {
1919
+ getLocusDTO: sandbox.stub(),
1920
+ },
1921
+ locusInfo: {
1922
+ handleLocusDelta: sandbox.stub(),
1923
+ onFullLocus: sandbox.stub(),
1924
+ },
1925
+ locusUrl: 'fullSyncUrl',
1926
+ };
1927
+
1928
+ locusInfo.locusParser.workingCopy = {
1929
+ syncUrl: 'deltaSyncUrl',
1930
+ };
1931
+ });
1932
+
1933
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
1934
+ meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
1935
+
1936
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl
1937
+
1938
+ // Since we have a promise inside a function we want to test that's not returned,
1939
+ // we will wait and stub it's last function to resolve this waiting promise.
1940
+ return new Promise((resolve) => {
1941
+ webex.meetings.destroy.callsFake(() => resolve());
1942
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1943
+ }).then(() => {
1944
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
1945
+
1946
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1947
+ assert.notCalled(meeting.locusInfo.onFullLocus);
1948
+ assert.notCalled(locusInfo.locusParser.resume);
1949
+
1950
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
1951
+ });
1952
+ });
1953
+
1954
+ it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
1955
+ meeting.meetingRequest.getLocusDTO.onCall(0).rejects(new Error('fake error'));
1956
+ meeting.meetingRequest.getLocusDTO.onCall(1).resolves({body: fakeFullLocusDto});
1957
+
1958
+ // Since we have a promise inside a function we want to test that's not returned,
1959
+ // we will wait and stub it's last function to resolve this waiting promise.
1960
+ return new Promise((resolve) => {
1961
+ locusInfo.locusParser.resume.callsFake(() => resolve());
1962
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1963
+ }).then(() => {
1964
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
1965
+
1966
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [{url: 'deltaSyncUrl'}]);
1967
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [{url: 'fullSyncUrl'}]);
1968
+
1969
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
1970
+ correlationId: meeting.correlationId,
1971
+ url: 'deltaSyncUrl',
1972
+ reason: 'fake error',
1973
+ errorName: 'Error',
1974
+ stack: sinon.match.any,
1975
+ code: sinon.match.any,
1976
+ });
1977
+
1978
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1979
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
1980
+ assert.calledOnce(locusInfo.locusParser.resume);
1981
+ });
1982
+ });
1983
+
1984
+ it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', () => {
1985
+ meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
1986
+
1987
+ // Since we have a promise inside a function we want to test that's not returned,
1988
+ // we will wait and stub it's last function to resolve this waiting promise.
1989
+ return new Promise((resolve) => {
1990
+ webex.meetings.destroy.callsFake(() => resolve());
1991
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1992
+ }).then(() => {
1993
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
1994
+
1995
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [{url: 'deltaSyncUrl'}]);
1996
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [{url: 'fullSyncUrl'}]);
1997
+
1998
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
1999
+ correlationId: meeting.correlationId,
2000
+ url: 'deltaSyncUrl',
2001
+ reason: 'fake error',
2002
+ errorName: 'Error',
2003
+ stack: sinon.match.any,
2004
+ code: sinon.match.any,
2005
+ });
2006
+
2007
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2008
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2009
+ assert.notCalled(locusInfo.locusParser.resume);
2010
+
2011
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2012
+ });
2013
+ });
2014
+ });
2015
+
2016
+ it('onDeltaLocus handle delta data', () => {
2017
+ fakeLocus.participants = {};
2018
+ const fakeBreakout = {
2019
+ sessionId: 'sessionId',
2020
+ groupId: 'groupId',
2021
+ };
2022
+
2023
+ fakeLocus.controls = {
2024
+ breakout: fakeBreakout,
2025
+ };
2026
+ locusInfo.controls = {
2027
+ breakout: {
2028
+ sessionId: 'sessionId',
2029
+ groupId: 'groupId',
2030
+ },
2031
+ };
2032
+ locusInfo.updateParticipants = sinon.stub();
2033
+ locusInfo.onDeltaLocus(fakeLocus);
2034
+ assert.calledWith(locusInfo.updateParticipants, {}, false);
2035
+
2036
+ fakeLocus.controls.breakout.sessionId = 'sessionId2';
2037
+ locusInfo.onDeltaLocus(fakeLocus);
2038
+ assert.calledWith(locusInfo.updateParticipants, {}, true);
2039
+ });
2040
+ });
2041
+
2042
+ describe('#updateLocusCache', () => {
2043
+ it('cache it if income locus is main session locus', () => {
2044
+ const locus = {url: 'url'};
2045
+ locusInfo.mainSessionLocusCache = null;
2046
+ locusInfo.updateLocusCache(locus);
2047
+
2048
+ assert.deepEqual(locusInfo.mainSessionLocusCache, locus);
2049
+ });
2050
+
2051
+ it('not cache it if income locus is breakout session locus', () => {
2052
+ const locus = {url: 'url', controls: {breakout: {sessionType: 'BREAKOUT'}}};
2053
+ locusInfo.mainSessionLocusCache = null;
2054
+ locusInfo.updateLocusCache(locus);
2055
+
2056
+ assert.isNull(locusInfo.mainSessionLocusCache);
2057
+ });
2058
+ });
2059
+
2060
+ describe('#getTheLocusToUpdate', () => {
2061
+ it('return the cache locus if return to main session', () => {
2062
+ locusInfo.mainSessionLocusCache = {url: 'url'};
2063
+ locusInfo.controls = {
2064
+ breakout: {
2065
+ sessionType: 'BREAKOUT',
2066
+ },
2067
+ };
2068
+ const newLocus = {
2069
+ controls: {
2070
+ breakout: {
2071
+ sessionType: 'MAIN',
2072
+ },
2073
+ },
2074
+ };
2075
+
2076
+ assert.deepEqual(locusInfo.getTheLocusToUpdate(newLocus), {url: 'url'});
2077
+ });
2078
+
2079
+ it('return the new locus if return to main session but no cache', () => {
2080
+ locusInfo.mainSessionLocusCache = null;
2081
+ locusInfo.controls = {
2082
+ breakout: {
2083
+ sessionType: 'BREAKOUT',
2084
+ },
2085
+ };
2086
+ const newLocus = {
2087
+ controls: {
2088
+ breakout: {
2089
+ sessionType: 'MAIN',
2090
+ },
2091
+ },
2092
+ };
2093
+
2094
+ assert.deepEqual(locusInfo.getTheLocusToUpdate(newLocus), newLocus);
2095
+ });
2096
+
2097
+ it('return the new locus if not return to main session', () => {
2098
+ locusInfo.mainSessionLocusCache = {url: 'url'};
2099
+ locusInfo.controls = {
2100
+ breakout: {
2101
+ sessionType: 'MAIN',
2102
+ },
2103
+ };
2104
+ const newLocus = {
2105
+ controls: {
2106
+ breakout: {
2107
+ sessionType: 'BREAKOUT',
2108
+ },
2109
+ },
2110
+ };
2111
+
2112
+ assert.deepEqual(locusInfo.getTheLocusToUpdate(newLocus), newLocus);
2113
+ });
2114
+ });
2115
+
2116
+ describe('#mergeParticipants', () => {
2117
+ let participants;
2118
+ let sourceParticipants;
2119
+ beforeEach(() => {
2120
+ participants = [{id: '111', status: 'JOINED'}, {id: '222'}];
2121
+ sourceParticipants = [{id: '111', status: 'LEFT'}, {id: '333'}];
2122
+ });
2123
+
2124
+ it('merge the participants, replace it by id if exist in old array', () => {
2125
+ const result = locusInfo.mergeParticipants(participants, sourceParticipants);
2126
+ assert.deepEqual(result, [{id: '111', status: 'LEFT'}, {id: '222'}, {id: '333'}]);
2127
+ });
2128
+
2129
+ it('return new participants if previous participants is empty', () => {
2130
+ const result = locusInfo.mergeParticipants([], sourceParticipants);
2131
+ assert.deepEqual(result, sourceParticipants);
2132
+ });
2133
+
2134
+ it('return new participants if previous participants is null/undefined', () => {
2135
+ let result = locusInfo.mergeParticipants(null, sourceParticipants);
2136
+ assert.deepEqual(result, sourceParticipants);
2137
+
2138
+ result = locusInfo.mergeParticipants(undefined, sourceParticipants);
2139
+ assert.deepEqual(result, sourceParticipants);
2140
+ });
2141
+
2142
+ it('return previous participants if new participants is empty', () => {
2143
+ const result = locusInfo.mergeParticipants(participants, []);
2144
+ assert.deepEqual(result, participants);
2145
+ });
2146
+
2147
+ it('return previous participants if new participants is null/undefined', () => {
2148
+ let result = locusInfo.mergeParticipants(participants, null);
2149
+ assert.deepEqual(result, participants);
2150
+
2151
+ result = locusInfo.mergeParticipants(participants, undefined);
2152
+ assert.deepEqual(result, participants);
2153
+ });
2154
+ });
2155
+
2156
+ describe('#updateMainSessionLocusCache', () => {
2157
+ let cachedLocus;
2158
+ let newLocus;
2159
+ beforeEach(() => {
2160
+ cachedLocus = {
2161
+ controls: {},
2162
+ participants: [],
2163
+ info: {webExMeetingId: 'testId1', topic: 'test'},
2164
+ };
2165
+ newLocus = {
2166
+ self: {},
2167
+ participants: [{id: '111'}],
2168
+ info: {testId: 'testId2', webExMeetingName: 'hello'},
2169
+ };
2170
+ });
2171
+ it('shallow merge new locus into cache', () => {
2172
+ locusInfo.mainSessionLocusCache = cachedLocus;
2173
+ locusInfo.updateMainSessionLocusCache(newLocus);
2174
+
2175
+ assert.deepEqual(locusInfo.mainSessionLocusCache, {
2176
+ controls: {},
2177
+ participants: [{id: '111'}],
2178
+ info: {testId: 'testId2', webExMeetingName: 'hello'},
2179
+ self: {},
2180
+ });
2181
+ });
2182
+
2183
+ it('cache new locus if no cache before', () => {
2184
+ locusInfo.mainSessionLocusCache = null;
2185
+ locusInfo.updateMainSessionLocusCache(newLocus);
2186
+
2187
+ assert.deepEqual(locusInfo.mainSessionLocusCache, newLocus);
2188
+ });
2189
+
2190
+ it('do nothing if new locus is null', () => {
2191
+ locusInfo.mainSessionLocusCache = cachedLocus;
2192
+ locusInfo.updateMainSessionLocusCache(null);
2193
+
2194
+ assert.deepEqual(locusInfo.mainSessionLocusCache, cachedLocus);
2195
+ });
2196
+ });
2197
+
2198
+ describe('#clearMainSessionLocusCache', () => {
2199
+ it('clear main session locus cache', () => {
2200
+ locusInfo.mainSessionLocusCache = {controls: {}};
2201
+ locusInfo.clearMainSessionLocusCache();
2202
+ assert.isNull(locusInfo.mainSessionLocusCache);
2203
+ });
1229
2204
  });
1230
2205
 
1231
2206
  describe('#handleOneonOneEvent', () => {
@@ -1268,5 +2243,404 @@ describe('plugin-meetings', () => {
1268
2243
  );
1269
2244
  });
1270
2245
  });
2246
+
2247
+ describe('#isMeetingActive', () => {
2248
+ it('sends client event correctly for state = inactive', () => {
2249
+ locusInfo.parsedLocus = {
2250
+ fullState: {
2251
+ type: _CALL_,
2252
+ },
2253
+ };
2254
+
2255
+ locusInfo.fullState = {
2256
+ state: LOCUS.STATE.INACTIVE,
2257
+ };
2258
+
2259
+ locusInfo.isMeetingActive();
2260
+
2261
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2262
+ name: 'client.call.remote-ended',
2263
+ options: {
2264
+ meetingId: locusInfo.meetingId,
2265
+ },
2266
+ });
2267
+ });
2268
+
2269
+ it('sends client event correctly for state = PARTNER_LEFT', () => {
2270
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2271
+ locusInfo.parsedLocus = {
2272
+ fullState: {
2273
+ type: _CALL_,
2274
+ },
2275
+ self: {
2276
+ state: MEETING_STATE.STATES.DECLINED,
2277
+ },
2278
+ };
2279
+ locusInfo.isMeetingActive();
2280
+
2281
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2282
+ name: 'client.call.remote-ended',
2283
+ options: {
2284
+ meetingId: locusInfo.meetingId,
2285
+ },
2286
+ });
2287
+ });
2288
+
2289
+ it('sends client event correctly for state = SELF_LEFT', () => {
2290
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2291
+ locusInfo.parsedLocus = {
2292
+ fullState: {
2293
+ type: _CALL_,
2294
+ },
2295
+ self: {
2296
+ state: MEETING_STATE.STATES.LEFT,
2297
+ },
2298
+ };
2299
+
2300
+ locusInfo.isMeetingActive();
2301
+
2302
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2303
+ name: 'client.call.remote-ended',
2304
+ options: {
2305
+ meetingId: locusInfo.meetingId,
2306
+ },
2307
+ });
2308
+ });
2309
+
2310
+ it('sends client event correctly for state = MEETING_INACTIVE_TERMINATING', () => {
2311
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2312
+ locusInfo.parsedLocus = {
2313
+ fullState: {
2314
+ type: _MEETING_,
2315
+ },
2316
+ };
2317
+
2318
+ locusInfo.fullState = {
2319
+ state: LOCUS.STATE.INACTIVE,
2320
+ };
2321
+
2322
+ locusInfo.isMeetingActive();
2323
+
2324
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2325
+ name: 'client.call.remote-ended',
2326
+ options: {
2327
+ meetingId: locusInfo.meetingId,
2328
+ },
2329
+ });
2330
+ });
2331
+
2332
+ it('sends client event correctly for state = FULLSTATE_REMOVED', () => {
2333
+ locusInfo.getLocusPartner = sinon.stub().returns({state: MEETING_STATE.STATES.LEFT});
2334
+ locusInfo.parsedLocus = {
2335
+ fullState: {
2336
+ type: _MEETING_,
2337
+ },
2338
+ };
2339
+
2340
+ locusInfo.fullState = {
2341
+ removed: true,
2342
+ };
2343
+
2344
+ locusInfo.isMeetingActive();
2345
+
2346
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
2347
+ name: 'client.call.remote-ended',
2348
+ options: {
2349
+ meetingId: locusInfo.meetingId,
2350
+ },
2351
+ });
2352
+ });
2353
+ });
2354
+
2355
+ // semi-integration tests that use real LocusInfo with real Parser
2356
+ // and test various scenarios related to handling out-of-order Locus delta events
2357
+ describe('handling of out-of-order Locus delta events', () => {
2358
+ let clock;
2359
+
2360
+ const generateDeltaEvent = (base, sequence) => {
2361
+ return {
2362
+ baseSequence: {
2363
+ rangeStart: 0,
2364
+ rangeEnd: 0,
2365
+ entries: [base],
2366
+ },
2367
+ sequence: {
2368
+ rangeStart: 0,
2369
+ rangeEnd: 0,
2370
+ entries: [sequence],
2371
+ },
2372
+ syncUrl: `fake sync url for sequence ${sequence}`,
2373
+ self: {
2374
+ person: {
2375
+ id: 'test person id',
2376
+ },
2377
+ },
2378
+ };
2379
+ };
2380
+
2381
+ // a list of example delta events, sorted by time and each event is based on the previous one
2382
+ const deltaEvents = [
2383
+ generateDeltaEvent(10, 20), // 0
2384
+ generateDeltaEvent(20, 30), // 1
2385
+ generateDeltaEvent(30, 40), // 2
2386
+ generateDeltaEvent(40, 50), // 3
2387
+ generateDeltaEvent(50, 60), // 4
2388
+ generateDeltaEvent(60, 70), // 5
2389
+ generateDeltaEvent(70, 80), // 6
2390
+ generateDeltaEvent(80, 90), // 7
2391
+ generateDeltaEvent(90, 100), // 8
2392
+ ];
2393
+
2394
+ let updateLocusInfoStub; // we use this stub to verify that an event has been fully processed
2395
+ let syncRequestStub;
2396
+
2397
+ beforeEach(() => {
2398
+ clock = sinon.useFakeTimers();
2399
+
2400
+ sinon.stub(locusInfo, 'updateParticipantDeltas');
2401
+ sinon.stub(locusInfo, 'updateParticipants');
2402
+ sinon.stub(locusInfo, 'isMeetingActive'),
2403
+ sinon.stub(locusInfo, 'handleOneOnOneEvent'),
2404
+ (updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
2405
+ syncRequestStub = sinon.stub().resolves({body: {}});
2406
+
2407
+ mockMeeting.locusInfo = locusInfo;
2408
+ mockMeeting.locusUrl = 'fake locus url';
2409
+ mockMeeting.meetingRequest = {
2410
+ getLocusDTO: syncRequestStub,
2411
+ };
2412
+
2413
+ locusInfo.onFullLocus({
2414
+ sequence: {
2415
+ rangeStart: 0,
2416
+ rangeEnd: 0,
2417
+ entries: [10],
2418
+ },
2419
+ self: {
2420
+ person: {
2421
+ id: 'test person id',
2422
+ },
2423
+ },
2424
+ });
2425
+
2426
+ updateLocusInfoStub.resetHistory();
2427
+ });
2428
+
2429
+ afterEach(() => {
2430
+ clock.restore();
2431
+ });
2432
+
2433
+ it('queues out-of-order deltas until it receives a correct delta', () => {
2434
+ // send some out-of-order deltas
2435
+ locusInfo.handleLocusDelta(deltaEvents[1], mockMeeting);
2436
+ locusInfo.handleLocusDelta(deltaEvents[4], mockMeeting);
2437
+
2438
+ // they should be queued and not processed
2439
+ assert.notCalled(updateLocusInfoStub);
2440
+
2441
+ // now one of the missing ones, but not the one SDK is really waiting for
2442
+ locusInfo.handleLocusDelta(deltaEvents[2], mockMeeting);
2443
+
2444
+ // still nothing should be processed
2445
+ assert.notCalled(updateLocusInfoStub);
2446
+
2447
+ // now send the one SDK is waiting for
2448
+ locusInfo.handleLocusDelta(deltaEvents[0], mockMeeting);
2449
+
2450
+ // so deltaEvents with indexes 1,2,3 can be processed, but 5 still not, because 4 is missing
2451
+ assert.callCount(updateLocusInfoStub, 3);
2452
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[0]);
2453
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[1]);
2454
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[2]);
2455
+
2456
+ updateLocusInfoStub.resetHistory();
2457
+
2458
+ // now send deltaEvents[4]
2459
+ locusInfo.handleLocusDelta(deltaEvents[3], mockMeeting);
2460
+
2461
+ // and verify deltaEvents[4] and deltaEvents[5] have been processed
2462
+ assert.callCount(updateLocusInfoStub, 2);
2463
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[3]);
2464
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[4]);
2465
+ });
2466
+
2467
+ it('handles out-of-order deltas correctly even if all arrive in reverse order', () => {
2468
+ // send a bunch deltas in reverse order
2469
+ for (let i = 4; i >= 0; i--) {
2470
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2471
+ }
2472
+
2473
+ // they should be queued and then processed in correct order
2474
+ assert.callCount(updateLocusInfoStub, 5);
2475
+ assert.calledWith(updateLocusInfoStub.getCall(0), deltaEvents[0]);
2476
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[1]);
2477
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[2]);
2478
+ assert.calledWith(updateLocusInfoStub.getCall(3), deltaEvents[3]);
2479
+ assert.calledWith(updateLocusInfoStub.getCall(4), deltaEvents[4]);
2480
+ });
2481
+
2482
+ it('sends a sync request using syncUrl if it receives at least 1 delta event and processes later deltas after sync correctly', async () => {
2483
+ // the test first sends an initial "good" delta
2484
+ const initialDeltaIdx = 0;
2485
+ const initialDelta = deltaEvents[initialDeltaIdx];
2486
+
2487
+ // then it sends a bunch of out-of-order deltas (at least 6 to trigger a sync), last one being lastOooDelta
2488
+ const firstOooDeltaIdx = 2;
2489
+ const lastOooDeltaIdx = 7;
2490
+ const lastOooDelta = deltaEvents[lastOooDeltaIdx];
2491
+
2492
+ // and finally, after the sync it sends another "good" delta
2493
+ const goodDeltaAfterSync = deltaEvents[8];
2494
+
2495
+ const deltaLocusFromSyncResponse = {
2496
+ baseSequence: {
2497
+ rangeStart: 0,
2498
+ rangeEnd: 0,
2499
+ entries: [initialDelta.sequence.entries[0]],
2500
+ },
2501
+ sequence: {
2502
+ rangeStart: 0,
2503
+ rangeEnd: 0,
2504
+ entries: [lastOooDelta.sequence.entries[0]],
2505
+ },
2506
+ syncUrl: `fake sync url for sequence ${lastOooDelta.sequence.entries[0]}`,
2507
+ self: {
2508
+ person: {
2509
+ id: 'test person id',
2510
+ },
2511
+ },
2512
+ };
2513
+
2514
+ syncRequestStub.resolves({
2515
+ body: deltaLocusFromSyncResponse,
2516
+ });
2517
+
2518
+ // send one correct delta so that SDK has the syncUrl
2519
+ locusInfo.handleLocusDelta(initialDelta, mockMeeting);
2520
+
2521
+ updateLocusInfoStub.resetHistory();
2522
+
2523
+ // send 6 out-of-order deltas to trigger a sync (we're skipping deltaEvents[1])
2524
+ for (let i = firstOooDeltaIdx; i <= lastOooDeltaIdx; i++) {
2525
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2526
+ }
2527
+
2528
+ await testUtils.flushPromises();
2529
+
2530
+ // check that sync was done using the correct syncUrl
2531
+ assert.calledOnceWithExactly(syncRequestStub, {url: initialDelta.syncUrl});
2532
+ assert.calledOnceWithExactly(updateLocusInfoStub, deltaLocusFromSyncResponse);
2533
+
2534
+ updateLocusInfoStub.resetHistory();
2535
+
2536
+ // now send another delta - a good one, it should be processed as normal
2537
+ locusInfo.handleLocusDelta(goodDeltaAfterSync, mockMeeting);
2538
+
2539
+ assert.calledOnceWithExactly(updateLocusInfoStub, goodDeltaAfterSync);
2540
+ });
2541
+
2542
+ it('does a sync if blocked on out-of-order deltas for too long', async () => {
2543
+ // stub random so that the timer fires after 12500 ms
2544
+ sinon.stub(Math, 'random').returns(0.5);
2545
+
2546
+ const oooDelta = deltaEvents[3];
2547
+
2548
+ // setup the stubs so that the sync request receives a full DTO with the sequence equal to the out-of-order delta we simulate
2549
+ const fullLocus = {
2550
+ sequence: oooDelta.sequence,
2551
+ };
2552
+ syncRequestStub.resolves({
2553
+ body: fullLocus,
2554
+ });
2555
+
2556
+ // send an out-of-order delta
2557
+ locusInfo.handleLocusDelta(oooDelta, mockMeeting);
2558
+
2559
+ assert.calledOnceWithExactly(sendBehavioralMetricStub, 'js_sdk_locus_delta_ooo', { stack: sinon.match.any})
2560
+
2561
+ await clock.tickAsync(12499);
2562
+ await testUtils.flushPromises();
2563
+ assert.notCalled(syncRequestStub);
2564
+ assert.notCalled(updateLocusInfoStub);
2565
+
2566
+ await clock.tickAsync(1);
2567
+ await testUtils.flushPromises();
2568
+
2569
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2570
+ assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
2571
+ });
2572
+
2573
+ it('does a sync if out-of-order deltas queue becomes too big', async () => {
2574
+ // setup the stubs so that the sync request receives a full DTO with the sequence equal to the out-of-order delta we simulate
2575
+ const fullLocus = {
2576
+ sequence: deltaEvents[6].sequence,
2577
+ };
2578
+ syncRequestStub.resolves({
2579
+ body: fullLocus,
2580
+ });
2581
+
2582
+ // send 5 deltas, starting from deltaEvents[1] so that SDK is blocked waiting for deltaEvents[0]
2583
+ for (let i = 0; i < 5; i++) {
2584
+ locusInfo.handleLocusDelta(deltaEvents[i + 1], mockMeeting);
2585
+ }
2586
+
2587
+ // nothing should happen, SDK should still be waiting for deltaEvents[0]
2588
+ assert.notCalled(syncRequestStub);
2589
+ assert.notCalled(updateLocusInfoStub);
2590
+
2591
+ // now send one more out-of-order delta to trigger a sync request
2592
+ locusInfo.handleLocusDelta(deltaEvents[6], mockMeeting);
2593
+
2594
+ await testUtils.flushPromises();
2595
+
2596
+ // check sync was done
2597
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2598
+ assert.calledOnceWithExactly(updateLocusInfoStub, fullLocus);
2599
+ });
2600
+
2601
+ it('processes delta events that are not included in sync response', async () => {
2602
+ // this test sends a bunch of out-of-order deltas, this triggers a sync
2603
+ // but the full locus response doesn't include the last 2 deltas received, so
2604
+ // we check that these 2 deltas are also processed after sync response
2605
+ const fullLocusFromSyncResponse = {
2606
+ baseSequence: {
2607
+ rangeStart: 0,
2608
+ rangeEnd: 0,
2609
+ entries: [deltaEvents[0].sequence.entries[0]],
2610
+ },
2611
+ sequence: {
2612
+ rangeStart: 0,
2613
+ rangeEnd: 0,
2614
+ entries: [deltaEvents[5].sequence.entries[0]],
2615
+ },
2616
+ syncUrl: `fake sync url for sequence ${deltaEvents[5].sequence.entries[0]}`,
2617
+ self: {
2618
+ person: {
2619
+ id: 'test person id',
2620
+ },
2621
+ },
2622
+ };
2623
+
2624
+ syncRequestStub.resolves({
2625
+ body: fullLocusFromSyncResponse,
2626
+ });
2627
+
2628
+ // send at least 6 out-of-order deltas to trigger a sync (we're skipping deltaEvents[0])
2629
+ for (let i = 1; i <= 7; i++) {
2630
+ locusInfo.handleLocusDelta(deltaEvents[i], mockMeeting);
2631
+ }
2632
+
2633
+ await testUtils.flushPromises();
2634
+
2635
+ // check that sync was done
2636
+ assert.calledOnceWithExactly(syncRequestStub, {url: mockMeeting.locusUrl});
2637
+
2638
+ // and that remaining deltas from the queue that were not included in full Locus were also processed
2639
+ assert.callCount(updateLocusInfoStub, 3);
2640
+ assert.calledWith(updateLocusInfoStub.getCall(0), fullLocusFromSyncResponse);
2641
+ assert.calledWith(updateLocusInfoStub.getCall(1), deltaEvents[6]);
2642
+ assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
2643
+ });
2644
+ });
1271
2645
  });
1272
2646
  });