@webex/plugin-meetings 3.10.0-next.3 → 3.10.0-next.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/dist/annotation/annotation.types.js.map +1 -1
  2. package/dist/annotation/constants.js.map +1 -1
  3. package/dist/annotation/index.js +19 -22
  4. package/dist/annotation/index.js.map +1 -1
  5. package/dist/breakouts/breakout.js +6 -6
  6. package/dist/breakouts/breakout.js.map +1 -1
  7. package/dist/breakouts/collection.js.map +1 -1
  8. package/dist/breakouts/edit-lock-error.js +9 -11
  9. package/dist/breakouts/edit-lock-error.js.map +1 -1
  10. package/dist/breakouts/events.js.map +1 -1
  11. package/dist/breakouts/index.js +126 -127
  12. package/dist/breakouts/index.js.map +1 -1
  13. package/dist/breakouts/request.js +6 -8
  14. package/dist/breakouts/request.js.map +1 -1
  15. package/dist/breakouts/utils.js.map +1 -1
  16. package/dist/common/browser-detection.js.map +1 -1
  17. package/dist/common/collection.js +1 -2
  18. package/dist/common/collection.js.map +1 -1
  19. package/dist/common/config.js.map +1 -1
  20. package/dist/common/errors/captcha-error.js +9 -11
  21. package/dist/common/errors/captcha-error.js.map +1 -1
  22. package/dist/common/errors/intent-to-join.js +10 -12
  23. package/dist/common/errors/intent-to-join.js.map +1 -1
  24. package/dist/common/errors/join-forbidden-error.js +10 -12
  25. package/dist/common/errors/join-forbidden-error.js.map +1 -1
  26. package/dist/common/errors/join-meeting.js +10 -12
  27. package/dist/common/errors/join-meeting.js.map +1 -1
  28. package/dist/common/errors/join-webinar-error.js +9 -11
  29. package/dist/common/errors/join-webinar-error.js.map +1 -1
  30. package/dist/common/errors/media.js +9 -11
  31. package/dist/common/errors/media.js.map +1 -1
  32. package/dist/common/errors/multistream-not-supported-error.js +9 -11
  33. package/dist/common/errors/multistream-not-supported-error.js.map +1 -1
  34. package/dist/common/errors/no-meeting-info.js +9 -11
  35. package/dist/common/errors/no-meeting-info.js.map +1 -1
  36. package/dist/common/errors/parameter.js +11 -14
  37. package/dist/common/errors/parameter.js.map +1 -1
  38. package/dist/common/errors/password-error.js +9 -11
  39. package/dist/common/errors/password-error.js.map +1 -1
  40. package/dist/common/errors/permission.js +9 -11
  41. package/dist/common/errors/permission.js.map +1 -1
  42. package/dist/common/errors/reclaim-host-role-errors.js +32 -38
  43. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -1
  44. package/dist/common/errors/reconnection-not-started.js +5 -6
  45. package/dist/common/errors/reconnection-not-started.js.map +1 -1
  46. package/dist/common/errors/reconnection.js +9 -11
  47. package/dist/common/errors/reconnection.js.map +1 -1
  48. package/dist/common/errors/stats.js +9 -11
  49. package/dist/common/errors/stats.js.map +1 -1
  50. package/dist/common/errors/webex-errors.js +20 -29
  51. package/dist/common/errors/webex-errors.js.map +1 -1
  52. package/dist/common/errors/webex-meetings-error.js +9 -12
  53. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  54. package/dist/common/events/events-scope.js +9 -10
  55. package/dist/common/events/events-scope.js.map +1 -1
  56. package/dist/common/events/events.js +9 -10
  57. package/dist/common/events/events.js.map +1 -1
  58. package/dist/common/events/trigger-proxy.js.map +1 -1
  59. package/dist/common/events/util.js.map +1 -1
  60. package/dist/common/logs/logger-config.js.map +1 -1
  61. package/dist/common/logs/logger-proxy.js.map +1 -1
  62. package/dist/common/logs/request.js +17 -17
  63. package/dist/common/logs/request.js.map +1 -1
  64. package/dist/common/queue.js +1 -2
  65. package/dist/common/queue.js.map +1 -1
  66. package/dist/config.js +2 -2
  67. package/dist/config.js.map +1 -1
  68. package/dist/constants.js +11 -8
  69. package/dist/constants.js.map +1 -1
  70. package/dist/controls-options-manager/constants.js.map +1 -1
  71. package/dist/controls-options-manager/enums.js.map +1 -1
  72. package/dist/controls-options-manager/index.js +1 -2
  73. package/dist/controls-options-manager/index.js.map +1 -1
  74. package/dist/controls-options-manager/types.js.map +1 -1
  75. package/dist/controls-options-manager/util.js +1 -2
  76. package/dist/controls-options-manager/util.js.map +1 -1
  77. package/dist/hashTree/constants.js +20 -0
  78. package/dist/hashTree/constants.js.map +1 -0
  79. package/dist/hashTree/hashTree.js +515 -0
  80. package/dist/hashTree/hashTree.js.map +1 -0
  81. package/dist/hashTree/hashTreeParser.js +1250 -0
  82. package/dist/hashTree/hashTreeParser.js.map +1 -0
  83. package/dist/hashTree/types.js +23 -0
  84. package/dist/hashTree/types.js.map +1 -0
  85. package/dist/hashTree/utils.js +59 -0
  86. package/dist/hashTree/utils.js.map +1 -0
  87. package/dist/index.js +1 -2
  88. package/dist/index.js.map +1 -1
  89. package/dist/interceptors/index.js.map +1 -1
  90. package/dist/interceptors/locusRetry.js +6 -8
  91. package/dist/interceptors/locusRetry.js.map +1 -1
  92. package/dist/interceptors/locusRouteToken.js +26 -12
  93. package/dist/interceptors/locusRouteToken.js.map +1 -1
  94. package/dist/interpretation/collection.js.map +1 -1
  95. package/dist/interpretation/index.js +1 -2
  96. package/dist/interpretation/index.js.map +1 -1
  97. package/dist/interpretation/siLanguage.js +1 -1
  98. package/dist/interpretation/siLanguage.js.map +1 -1
  99. package/dist/locus-info/controlsUtils.js.map +1 -1
  100. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  101. package/dist/locus-info/fullState.js.map +1 -1
  102. package/dist/locus-info/hostUtils.js.map +1 -1
  103. package/dist/locus-info/index.js +609 -177
  104. package/dist/locus-info/index.js.map +1 -1
  105. package/dist/locus-info/infoUtils.js.map +1 -1
  106. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  107. package/dist/locus-info/parser.js +3 -4
  108. package/dist/locus-info/parser.js.map +1 -1
  109. package/dist/locus-info/selfUtils.js.map +1 -1
  110. package/dist/locus-info/types.js +7 -0
  111. package/dist/locus-info/types.js.map +1 -0
  112. package/dist/media/MediaConnectionAwaiter.js +1 -2
  113. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  114. package/dist/media/index.js +0 -2
  115. package/dist/media/index.js.map +1 -1
  116. package/dist/media/properties.js +15 -17
  117. package/dist/media/properties.js.map +1 -1
  118. package/dist/media/util.js.map +1 -1
  119. package/dist/meeting/brbState.js +8 -9
  120. package/dist/meeting/brbState.js.map +1 -1
  121. package/dist/meeting/connectionStateHandler.js +10 -13
  122. package/dist/meeting/connectionStateHandler.js.map +1 -1
  123. package/dist/meeting/in-meeting-actions.js.map +1 -1
  124. package/dist/meeting/index.js +1576 -1533
  125. package/dist/meeting/index.js.map +1 -1
  126. package/dist/meeting/locusMediaRequest.js +13 -17
  127. package/dist/meeting/locusMediaRequest.js.map +1 -1
  128. package/dist/meeting/muteState.js +11 -12
  129. package/dist/meeting/muteState.js.map +1 -1
  130. package/dist/meeting/request.js +101 -104
  131. package/dist/meeting/request.js.map +1 -1
  132. package/dist/meeting/request.type.js.map +1 -1
  133. package/dist/meeting/state.js.map +1 -1
  134. package/dist/meeting/type.js.map +1 -1
  135. package/dist/meeting/util.js +24 -23
  136. package/dist/meeting/util.js.map +1 -1
  137. package/dist/meeting/voicea-meeting.js +3 -3
  138. package/dist/meeting/voicea-meeting.js.map +1 -1
  139. package/dist/meeting-info/collection.js +7 -10
  140. package/dist/meeting-info/collection.js.map +1 -1
  141. package/dist/meeting-info/index.js +1 -2
  142. package/dist/meeting-info/index.js.map +1 -1
  143. package/dist/meeting-info/meeting-info-v2.js +135 -146
  144. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  145. package/dist/meeting-info/request.js +1 -2
  146. package/dist/meeting-info/request.js.map +1 -1
  147. package/dist/meeting-info/util.js +36 -37
  148. package/dist/meeting-info/util.js.map +1 -1
  149. package/dist/meeting-info/utilv2.js +30 -31
  150. package/dist/meeting-info/utilv2.js.map +1 -1
  151. package/dist/meetings/collection.js +6 -8
  152. package/dist/meetings/collection.js.map +1 -1
  153. package/dist/meetings/index.js +200 -148
  154. package/dist/meetings/index.js.map +1 -1
  155. package/dist/meetings/meetings.types.js.map +1 -1
  156. package/dist/meetings/request.js +6 -8
  157. package/dist/meetings/request.js.map +1 -1
  158. package/dist/meetings/util.js +36 -30
  159. package/dist/meetings/util.js.map +1 -1
  160. package/dist/member/index.js +1 -2
  161. package/dist/member/index.js.map +1 -1
  162. package/dist/member/types.js +6 -3
  163. package/dist/member/types.js.map +1 -1
  164. package/dist/member/util.js.map +1 -1
  165. package/dist/members/collection.js +1 -2
  166. package/dist/members/collection.js.map +1 -1
  167. package/dist/members/index.js +18 -21
  168. package/dist/members/index.js.map +1 -1
  169. package/dist/members/request.js +8 -11
  170. package/dist/members/request.js.map +1 -1
  171. package/dist/members/types.js.map +1 -1
  172. package/dist/members/util.js.map +1 -1
  173. package/dist/metrics/constants.js +3 -1
  174. package/dist/metrics/constants.js.map +1 -1
  175. package/dist/metrics/index.js +3 -4
  176. package/dist/metrics/index.js.map +1 -1
  177. package/dist/multistream/mediaRequestManager.js +1 -2
  178. package/dist/multistream/mediaRequestManager.js.map +1 -1
  179. package/dist/multistream/receiveSlot.js +34 -45
  180. package/dist/multistream/receiveSlot.js.map +1 -1
  181. package/dist/multistream/receiveSlotManager.js +8 -9
  182. package/dist/multistream/receiveSlotManager.js.map +1 -1
  183. package/dist/multistream/remoteMedia.js +12 -15
  184. package/dist/multistream/remoteMedia.js.map +1 -1
  185. package/dist/multistream/remoteMediaGroup.js +1 -2
  186. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  187. package/dist/multistream/remoteMediaManager.js +122 -123
  188. package/dist/multistream/remoteMediaManager.js.map +1 -1
  189. package/dist/multistream/sendSlotManager.js +29 -30
  190. package/dist/multistream/sendSlotManager.js.map +1 -1
  191. package/dist/personal-meeting-room/index.js +16 -19
  192. package/dist/personal-meeting-room/index.js.map +1 -1
  193. package/dist/personal-meeting-room/request.js +7 -10
  194. package/dist/personal-meeting-room/request.js.map +1 -1
  195. package/dist/personal-meeting-room/util.js.map +1 -1
  196. package/dist/reachability/clusterReachability.js +188 -352
  197. package/dist/reachability/clusterReachability.js.map +1 -1
  198. package/dist/reachability/index.js +206 -206
  199. package/dist/reachability/index.js.map +1 -1
  200. package/dist/reachability/reachability.types.js +14 -1
  201. package/dist/reachability/reachability.types.js.map +1 -1
  202. package/dist/reachability/reachabilityPeerConnection.js +445 -0
  203. package/dist/reachability/reachabilityPeerConnection.js.map +1 -0
  204. package/dist/reachability/request.js.map +1 -1
  205. package/dist/reachability/util.js.map +1 -1
  206. package/dist/reactions/constants.js.map +1 -1
  207. package/dist/reactions/reactions.js.map +1 -1
  208. package/dist/reactions/reactions.type.js.map +1 -1
  209. package/dist/reconnection-manager/index.js +178 -176
  210. package/dist/reconnection-manager/index.js.map +1 -1
  211. package/dist/recording-controller/enums.js.map +1 -1
  212. package/dist/recording-controller/index.js +1 -2
  213. package/dist/recording-controller/index.js.map +1 -1
  214. package/dist/recording-controller/util.js.map +1 -1
  215. package/dist/roap/index.js +12 -15
  216. package/dist/roap/index.js.map +1 -1
  217. package/dist/roap/request.js +24 -26
  218. package/dist/roap/request.js.map +1 -1
  219. package/dist/roap/turnDiscovery.js +75 -76
  220. package/dist/roap/turnDiscovery.js.map +1 -1
  221. package/dist/roap/types.js.map +1 -1
  222. package/dist/transcription/index.js +4 -5
  223. package/dist/transcription/index.js.map +1 -1
  224. package/dist/types/config.d.ts +1 -0
  225. package/dist/types/constants.d.ts +26 -21
  226. package/dist/types/hashTree/constants.d.ts +8 -0
  227. package/dist/types/hashTree/hashTree.d.ts +129 -0
  228. package/dist/types/hashTree/hashTreeParser.d.ts +250 -0
  229. package/dist/types/hashTree/types.d.ts +33 -0
  230. package/dist/types/hashTree/utils.d.ts +16 -0
  231. package/dist/types/interceptors/locusRouteToken.d.ts +1 -0
  232. package/dist/types/locus-info/index.d.ts +98 -80
  233. package/dist/types/locus-info/types.d.ts +54 -0
  234. package/dist/types/meeting/index.d.ts +22 -9
  235. package/dist/types/meetings/index.d.ts +9 -2
  236. package/dist/types/metrics/constants.d.ts +2 -0
  237. package/dist/types/reachability/clusterReachability.d.ts +33 -84
  238. package/dist/types/reachability/reachability.types.d.ts +12 -1
  239. package/dist/types/reachability/reachabilityPeerConnection.d.ts +111 -0
  240. package/dist/webinar/collection.js +1 -2
  241. package/dist/webinar/collection.js.map +1 -1
  242. package/dist/webinar/index.js +148 -158
  243. package/dist/webinar/index.js.map +1 -1
  244. package/package.json +23 -22
  245. package/src/config.ts +1 -0
  246. package/src/constants.ts +13 -1
  247. package/src/hashTree/constants.ts +9 -0
  248. package/src/hashTree/hashTree.ts +463 -0
  249. package/src/hashTree/hashTreeParser.ts +1143 -0
  250. package/src/hashTree/types.ts +39 -0
  251. package/src/hashTree/utils.ts +53 -0
  252. package/src/interceptors/locusRouteToken.ts +16 -4
  253. package/src/locus-info/index.ts +625 -164
  254. package/src/locus-info/types.ts +53 -0
  255. package/src/meeting/index.ts +78 -27
  256. package/src/meeting/util.ts +1 -0
  257. package/src/meetings/index.ts +119 -59
  258. package/src/meetings/util.ts +10 -9
  259. package/src/metrics/constants.ts +2 -0
  260. package/src/reachability/clusterReachability.ts +159 -330
  261. package/src/reachability/index.ts +6 -1
  262. package/src/reachability/reachability.types.ts +15 -1
  263. package/src/reachability/reachabilityPeerConnection.ts +418 -0
  264. package/test/unit/spec/hashTree/hashTree.ts +655 -0
  265. package/test/unit/spec/hashTree/hashTreeParser.ts +1524 -0
  266. package/test/unit/spec/hashTree/utils.ts +140 -0
  267. package/test/unit/spec/interceptors/locusRouteToken.ts +27 -0
  268. package/test/unit/spec/locus-info/index.js +851 -16
  269. package/test/unit/spec/meeting/index.js +120 -20
  270. package/test/unit/spec/meeting/utils.js +77 -0
  271. package/test/unit/spec/meetings/index.js +263 -27
  272. package/test/unit/spec/meetings/utils.js +51 -1
  273. package/test/unit/spec/reachability/clusterReachability.ts +404 -137
  274. package/test/unit/spec/reachability/index.ts +3 -3
@@ -12,6 +12,7 @@ import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedApp
12
12
  import MediaSharesUtils from '@webex/plugin-meetings/src/locus-info//mediaSharesUtils';
13
13
  import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
14
14
  import Metrics from '@webex/plugin-meetings/src/metrics';
15
+ import * as HashTreeParserModule from '@webex/plugin-meetings/src/hashTree/hashTreeParser';
15
16
 
16
17
  import {
17
18
  LOCUSINFO,
@@ -28,6 +29,7 @@ import {
28
29
  } from '../../../../src/constants';
29
30
 
30
31
  import {self, selfWithInactivity} from './selfConstant';
32
+ import { MEETING_REMOVED_REASON } from '@webex/plugin-meetings/src/constants';
31
33
 
32
34
  describe('plugin-meetings', () => {
33
35
  describe('LocusInfo index', () => {
@@ -77,6 +79,771 @@ describe('plugin-meetings', () => {
77
79
  sinon.restore();
78
80
  });
79
81
 
82
+ describe('#initialSetup', () => {
83
+ let HashTreeParserStub;
84
+ let mockHashTreeParser;
85
+ let updateLocusCacheStub;
86
+ let updateLocusInfoStub;
87
+ let isNewFullLocusStub;
88
+
89
+ beforeEach(() => {
90
+ mockHashTreeParser = {
91
+ initializeFromMessage: sinon.stub().resolves(),
92
+ initializeFromGetLociResponse: sinon.stub().resolves(),
93
+ };
94
+ HashTreeParserStub = sinon
95
+ .stub(HashTreeParserModule, 'default')
96
+ .returns(mockHashTreeParser);
97
+ updateLocusCacheStub = sinon.stub(locusInfo, 'updateLocusCache');
98
+ updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo');
99
+ isNewFullLocusStub = sinon.stub(locusInfo.locusParser, 'isNewFullLocus').returns(true);
100
+ });
101
+
102
+ afterEach(() => {
103
+ sinon.restore();
104
+ });
105
+
106
+ const createHashTreeMessage = (visibleDataSets) => ({
107
+ locusStateElements: [
108
+ {
109
+ htMeta: {elementId: {type: 'self'}},
110
+ data: {visibleDataSets},
111
+ },
112
+ ],
113
+ dataSets: [{name: 'dataset1', url: 'test-url'}],
114
+ });
115
+
116
+ const createLocusWithVisibleDataSets = (visibleDataSets) => ({
117
+ self: {visibleDataSets},
118
+ participants: [],
119
+ links: {
120
+ resources: {
121
+ visibleDataSets: {url: 'http://visible-datasets-url.com'},
122
+ },
123
+ },
124
+ });
125
+
126
+ it('should initialize the hash tree parser when triggered from a hash tree locus message', async () => {
127
+ const visibleDataSets = ['dataset1', 'dataset2'];
128
+ const hashTreeMessage = createHashTreeMessage(visibleDataSets);
129
+
130
+ await locusInfo.initialSetup({
131
+ trigger: 'locus-message',
132
+ hashTreeMessage,
133
+ });
134
+
135
+ assert.calledOnceWithExactly(
136
+ HashTreeParserStub,
137
+ sinon.match({
138
+ initialLocus: {
139
+ locus: {self: {visibleDataSets}},
140
+ dataSets: [],
141
+ },
142
+ webexRequest: sinon.match.func,
143
+ locusInfoUpdateCallback: sinon.match.func,
144
+ debugId: sinon.match.string,
145
+ })
146
+ );
147
+ assert.calledOnceWithExactly(mockHashTreeParser.initializeFromMessage, hashTreeMessage);
148
+ assert.notCalled(updateLocusCacheStub);
149
+ assert.notCalled(updateLocusInfoStub);
150
+ assert.isTrue(locusInfo.emitChange);
151
+ });
152
+
153
+ it('should not initialize the hash tree when triggered from a non-hash tree locus message', async () => {
154
+ const locus = {url: 'http://locus-url.com', participants: []};
155
+
156
+ await locusInfo.initialSetup({
157
+ trigger: 'locus-message',
158
+ locus,
159
+ });
160
+
161
+ assert.notCalled(HashTreeParserStub);
162
+ assert.notCalled(mockHashTreeParser.initializeFromMessage);
163
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
164
+ assert.calledOnce(updateLocusInfoStub);
165
+ assert.isTrue(locusInfo.emitChange);
166
+ });
167
+
168
+ it('should initialize the hash tree parser correctly when triggered from a join response containing datasets', async () => {
169
+ const visibleDataSets = ['dataset1', 'dataset2'];
170
+ const locus = createLocusWithVisibleDataSets(visibleDataSets);
171
+ const dataSets = [{name: 'dataset1', url: 'http://dataset-url.com'}];
172
+
173
+ await locusInfo.initialSetup({
174
+ trigger: 'join-response',
175
+ locus,
176
+ dataSets,
177
+ });
178
+
179
+ assert.calledOnceWithExactly(
180
+ HashTreeParserStub,
181
+ sinon.match({
182
+ initialLocus: {
183
+ locus,
184
+ dataSets,
185
+ },
186
+ webexRequest: sinon.match.func,
187
+ locusInfoUpdateCallback: sinon.match.func,
188
+ debugId: sinon.match.string,
189
+ })
190
+ );
191
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
192
+ assert.calledOnce(updateLocusInfoStub);
193
+ assert.isTrue(locusInfo.emitChange);
194
+ });
195
+
196
+ it('should do normal (classic) initialization when triggered from a join response without datasets', async () => {
197
+ const locus = {url: 'http://locus-url.com', participants: []};
198
+
199
+ await locusInfo.initialSetup({
200
+ trigger: 'join-response',
201
+ locus,
202
+ });
203
+
204
+ assert.notCalled(HashTreeParserStub);
205
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
206
+ assert.calledOnce(updateLocusInfoStub);
207
+ assert.isTrue(locusInfo.emitChange);
208
+ });
209
+
210
+ it('should initialize the hash tree parser correctly when triggered from a get loci response containing visible datasets', async () => {
211
+ const visibleDataSets = ['dataset1', 'dataset2'];
212
+ const locus = createLocusWithVisibleDataSets(visibleDataSets);
213
+
214
+ await locusInfo.initialSetup({
215
+ trigger: 'get-loci-response',
216
+ locus,
217
+ });
218
+
219
+ assert.calledOnceWithExactly(
220
+ HashTreeParserStub,
221
+ sinon.match({
222
+ initialLocus: {
223
+ locus: {self: {visibleDataSets}},
224
+ dataSets: [],
225
+ },
226
+ webexRequest: sinon.match.func,
227
+ locusInfoUpdateCallback: sinon.match.func,
228
+ debugId: sinon.match.string,
229
+ })
230
+ );
231
+ assert.calledOnceWithExactly(mockHashTreeParser.initializeFromGetLociResponse, locus);
232
+ assert.notCalled(updateLocusCacheStub);
233
+ assert.notCalled(updateLocusInfoStub);
234
+ assert.isTrue(locusInfo.emitChange);
235
+ });
236
+
237
+ it('should do normal (classic) initialization when triggered from a get loci response without visible datasets', async () => {
238
+ const locus = {url: 'http://locus-url.com', participants: []};
239
+
240
+ await locusInfo.initialSetup({
241
+ trigger: 'get-loci-response',
242
+ locus,
243
+ });
244
+
245
+ assert.notCalled(HashTreeParserStub);
246
+ assert.notCalled(mockHashTreeParser.initializeFromGetLociResponse);
247
+ assert.calledOnceWithExactly(updateLocusCacheStub, locus);
248
+ assert.calledOnce(updateLocusInfoStub);
249
+ assert.isTrue(locusInfo.emitChange);
250
+ });
251
+
252
+ describe('should setup correct locusInfoUpdateCallback when creating HashTreeParser', () => {
253
+ const OBJECTS_UPDATED = HashTreeParserModule.LocusInfoUpdateType.OBJECTS_UPDATED;
254
+ const MEETING_ENDED = HashTreeParserModule.LocusInfoUpdateType.MEETING_ENDED;
255
+
256
+ let locusInfoUpdateCallback;
257
+ let onDeltaLocusStub;
258
+ let expectedLocusInfo;
259
+
260
+ beforeEach(async () => {
261
+ onDeltaLocusStub = sinon.stub(locusInfo, 'onDeltaLocus');
262
+
263
+ await locusInfo.initialSetup({
264
+ trigger: 'locus-message',
265
+ hashTreeMessage: {
266
+ locusStateElements: [
267
+ {
268
+ htMeta: {elementId: {type: 'self'}},
269
+ data: {visibleDataSets: ['dataset1']},
270
+ },
271
+ ],
272
+ dataSets: [{name: 'dataset1', url: 'test-url'}],
273
+ },
274
+ });
275
+
276
+ locusInfoUpdateCallback = HashTreeParserStub.firstCall.args[0].locusInfoUpdateCallback;
277
+
278
+ assert.isDefined(locusInfoUpdateCallback);
279
+
280
+ // setup fake initial locusInfo state
281
+ locusInfo.controls = {id: 'fake-controls'};
282
+ locusInfo.fullState = {id: 'fake-full-state'};
283
+ locusInfo.host = {id: 'fake-host'};
284
+ locusInfo.info = {id: 'fake-info'};
285
+ locusInfo.links = {id: 'fake-links'};
286
+ locusInfo.mediaShares = [
287
+ {
288
+ id: 'fake-media-share-1',
289
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1', version: 1}},
290
+ },
291
+ {
292
+ id: 'fake-media-share-2',
293
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
294
+ },
295
+ ];
296
+ locusInfo.meetings = {id: 'fake-meetings'};
297
+ locusInfo.participants = [
298
+ {id: 'fake-participant-1', name: 'Participant One'},
299
+ {id: 'fake-participant-2', name: 'Participant Two'},
300
+ ];
301
+ locusInfo.hashTreeObjectId2ParticipantId.set(
302
+ 'fake-ht-participant-1',
303
+ 'fake-participant-1'
304
+ );
305
+ locusInfo.hashTreeObjectId2ParticipantId.set(
306
+ 'fake-ht-participant-2',
307
+ 'fake-participant-2'
308
+ );
309
+ locusInfo.replaces = {id: 'fake-replaces'};
310
+ locusInfo.self = {id: 'fake-self'};
311
+ locusInfo.url = 'fake-locus-url';
312
+ locusInfo.htMeta = {elementId: {type: 'locus', id: 'fake-ht-locus-id', version: 1}};
313
+
314
+ // setup the default expected locus info state that each test builds upon
315
+ expectedLocusInfo = {
316
+ controls: {id: 'fake-controls'},
317
+ fullState: {id: 'fake-full-state'},
318
+ host: {id: 'fake-host'},
319
+ info: {id: 'fake-info'},
320
+ links: {id: 'fake-links'},
321
+ mediaShares: [
322
+ {
323
+ id: 'fake-media-share-1',
324
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1', version: 1}},
325
+ },
326
+ {
327
+ id: 'fake-media-share-2',
328
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 1}},
329
+ },
330
+ ],
331
+ meetings: {id: 'fake-meetings'},
332
+ jsSdkMeta: {removedParticipantIds: []},
333
+ participants: [], // empty means there were no participant updates
334
+ replaces: {id: 'fake-replaces'},
335
+ self: {id: 'fake-self'},
336
+ url: 'fake-locus-url',
337
+ htMeta: {elementId: {type: 'locus', id: 'fake-ht-locus-id', version: 1}},
338
+ sequence: null, // not relevant for hash trees, so should remain null
339
+ syncUrl: undefined, // not relevant for hash trees, so should remain undefined
340
+ };
341
+ });
342
+
343
+ it('should process locus update correctly when called with updated SELF', () => {
344
+ const newSelf = {
345
+ id: 'new-self',
346
+ visibleDataSets: ['dataset1', 'dataset2'],
347
+ };
348
+
349
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
350
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
351
+ updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
352
+ });
353
+
354
+ // check onDeltaLocus() was called with correctly updated locus info
355
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
356
+ ...expectedLocusInfo,
357
+ self: newSelf,
358
+ });
359
+ });
360
+
361
+ it('should process locus update correctly when called with updated SELF (webinar non-attendee)', () => {
362
+ const newSelf = {
363
+ id: 'new-self',
364
+ visibleDataSets: ['dataset1', 'dataset2'],
365
+ controls: {
366
+ role: {
367
+ roles: [
368
+ {type: 'PANELIST', hasRole: true},
369
+ {type: 'ATTENDEE', hasRole: false},
370
+ ],
371
+ },
372
+ },
373
+ };
374
+ locusInfo.info.isWebinar = true;
375
+
376
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
377
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
378
+ updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
379
+ });
380
+
381
+ // check onDeltaLocus() was called with correctly updated locus info
382
+ // without any participant generated
383
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
384
+ ...expectedLocusInfo,
385
+ info: {
386
+ ...expectedLocusInfo.info,
387
+ isWebinar: true,
388
+ },
389
+ self: newSelf,
390
+ });
391
+ });
392
+
393
+ it('should generate a participant when called with updated SELF for webinar attendee', () => {
394
+ const newSelf = {
395
+ id: 'new-self',
396
+ visibleDataSets: ['dataset1', 'dataset2'],
397
+ controls: {
398
+ role: {
399
+ roles: [
400
+ {type: 'something else - should be ignored', hasRole: true},
401
+ {type: 'ATTENDEE', hasRole: true},
402
+ ],
403
+ },
404
+ },
405
+ };
406
+
407
+ locusInfo.info.isWebinar = true;
408
+
409
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
410
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
411
+ updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
412
+ });
413
+
414
+ // check onDeltaLocus() was called with correctly updated locus info
415
+ // that contains a participant created from self
416
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
417
+ ...expectedLocusInfo,
418
+ info: {
419
+ ...expectedLocusInfo.info,
420
+ isWebinar: true,
421
+ },
422
+ self: newSelf,
423
+ participants: [
424
+ {
425
+ ...newSelf,
426
+ },
427
+ ],
428
+ });
429
+ });
430
+
431
+ it('should process locus update correctly when called with updated fullState', () => {
432
+ const newFullState = {
433
+ id: 'new-fullState',
434
+ visibleDataSets: ['dataset1', 'dataset2'],
435
+ };
436
+
437
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
438
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
439
+ updatedObjects: [{htMeta: {elementId: {type: 'fullState'}}, data: newFullState}],
440
+ });
441
+
442
+ // check onDeltaLocus() was called with correctly updated locus info
443
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
444
+ ...expectedLocusInfo,
445
+ fullState: newFullState,
446
+ });
447
+ });
448
+
449
+ it('should process locus update correctly when called with updated info', () => {
450
+ const newInfo = {
451
+ id: 'new-info',
452
+ visibleDataSets: ['dataset1', 'dataset2'],
453
+ };
454
+
455
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
456
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
457
+ updatedObjects: [{htMeta: {elementId: {type: 'info'}}, data: newInfo}],
458
+ });
459
+
460
+ // check onDeltaLocus() was called with correctly updated locus info
461
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
462
+ ...expectedLocusInfo,
463
+ info: newInfo,
464
+ });
465
+ });
466
+
467
+ it('should process locus update correctly when called with updated links', () => {
468
+ const newLinks = {
469
+ id: 'new-links',
470
+ visibleDataSets: ['dataset1', 'dataset2'],
471
+ };
472
+
473
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
474
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
475
+ updatedObjects: [{htMeta: {elementId: {type: 'links'}}, data: newLinks}],
476
+ });
477
+
478
+ // check onDeltaLocus() was called with correctly updated locus info
479
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
480
+ ...expectedLocusInfo,
481
+ links: newLinks,
482
+ });
483
+ });
484
+
485
+ it('should process locus update correctly when called with updated LOCUS object', () => {
486
+ // setup new updated locus that has many things missing
487
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
488
+ const newLocus = {
489
+ host: 'new-host',
490
+ htMeta: newLocusHtMeta,
491
+ };
492
+
493
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
494
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
495
+ updatedObjects: [{htMeta: newLocusHtMeta, data: newLocus}],
496
+ });
497
+
498
+ // check onDeltaLocus() was called with correctly updated locus info
499
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
500
+ // these fields are not part of Locus object, so should keep their old values:
501
+ controls: {id: 'fake-controls'},
502
+ info: {id: 'fake-info'},
503
+ fullState: {id: 'fake-full-state'},
504
+ self: {id: 'fake-self'},
505
+ links: {id: 'fake-links'},
506
+ mediaShares: expectedLocusInfo.mediaShares,
507
+ // and now the new fields
508
+ ...newLocus,
509
+ htMeta: newLocusHtMeta,
510
+ participants: [], // empty means there were no participant updates
511
+ jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
512
+ });
513
+ });
514
+
515
+ // this test is checking that we cope with an edge case if Locus
516
+ // sends us something that they shouldn't
517
+ it('should process locus update correctly when called with updated LOCUS object that contains info/fullState/self/participants etc', () => {
518
+ // setup new updated locus that has many things missing
519
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
520
+ const newLocus = {
521
+ host: 'new-host',
522
+ htMeta: newLocusHtMeta,
523
+ };
524
+
525
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
526
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
527
+ updatedObjects: [
528
+ {
529
+ htMeta: newLocusHtMeta,
530
+ data: {
531
+ ...newLocus,
532
+ // all these fields below should be ignored and not override the existing ones in our "old" Locus
533
+ controls: {id: 'new-controls'},
534
+ info: 'new-info',
535
+ fullState: 'new-fullState',
536
+ self: 'new-self',
537
+ participants: 'new-participants',
538
+ mediaShares: 'new-mediaShares',
539
+ },
540
+ },
541
+ ],
542
+ });
543
+
544
+ // check onDeltaLocus() was called with correctly updated locus info
545
+ // with old values for the fields that should be ignored (like "info" or "fullState")
546
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
547
+ // these fields have the "old" values:
548
+ controls: {id: 'fake-controls'},
549
+ info: {id: 'fake-info'},
550
+ fullState: {id: 'fake-full-state'},
551
+ self: {id: 'fake-self'},
552
+ links: {id: 'fake-links'},
553
+ mediaShares: expectedLocusInfo.mediaShares,
554
+ participants: [], // empty means there were no participant updates
555
+ jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
556
+ ...newLocus,
557
+ htMeta: newLocusHtMeta,
558
+ });
559
+ });
560
+
561
+ it('should process locus update correctly when called with removed LOCUS object followed by updated LOCUS object', () => {
562
+ // setup new updated locus that has many things missing
563
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 99}};
564
+ const newLocus = {
565
+ info: 'new-info',
566
+ links: 'new-links',
567
+ htMeta: newLocusHtMeta,
568
+ };
569
+
570
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
571
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
572
+ updatedObjects: [
573
+ // first, a removal of LOCUS object
574
+ {htMeta: {elementId: {type: 'locus'}}, data: null},
575
+ // followed by an update of LOCUS object
576
+ {htMeta: newLocusHtMeta, data: newLocus},
577
+ ],
578
+ });
579
+
580
+ // check onDeltaLocus() was called with correctly updated locus info
581
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
582
+ // these fields are not part of Locus object, so should keep their old values:
583
+ controls: {id: 'fake-controls'},
584
+ info: {id: 'fake-info'},
585
+ fullState: {id: 'fake-full-state'},
586
+ self: {id: 'fake-self'},
587
+ links: {id: 'fake-links'},
588
+ mediaShares: expectedLocusInfo.mediaShares,
589
+ // and now the new fields
590
+ ...newLocus,
591
+ htMeta: newLocusHtMeta,
592
+ participants: [], // empty means there were no participant updates
593
+ jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
594
+ });
595
+ });
596
+
597
+ it('should send a metric if unsupported sequence of LOCUS object updates occurs (update followed by removal)', () => {
598
+ const newLocus = {
599
+ info: 'new-info',
600
+ };
601
+ const newLocusHtMeta = {elementId: {type: 'locus', version: 99}};
602
+
603
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
604
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
605
+ updatedObjects: [
606
+ // first, an update
607
+ {htMeta: newLocusHtMeta, data: newLocus},
608
+ // followed by removal
609
+ {htMeta: {elementId: {type: 'locus'}}, data: null},
610
+ ],
611
+ });
612
+
613
+ assert.calledWith(
614
+ sendBehavioralMetricStub,
615
+ 'js_sdk_locus_hash_tree_unsupported_operation',
616
+ {
617
+ locusUrl: 'fake-locus-url',
618
+ message: 'LOCUS object update followed by removal',
619
+ }
620
+ );
621
+ });
622
+
623
+ it('should send a metric if unsupported sequence of LOCUS object updates occurs (multiple updates)', () => {
624
+ const newLocus1 = {
625
+ info: 'new-info-1',
626
+ };
627
+ const newLocus2 = {
628
+ info: 'new-info-2',
629
+ url: 'new-url-2',
630
+ };
631
+
632
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
633
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
634
+ updatedObjects: [
635
+ // first, an update
636
+ {htMeta: {elementId: {type: 'locus'}}, data: newLocus1},
637
+ // followed by another update
638
+ {htMeta: {elementId: {type: 'locus'}}, data: newLocus2},
639
+ ],
640
+ });
641
+
642
+ assert.calledWith(
643
+ sendBehavioralMetricStub,
644
+ 'js_sdk_locus_hash_tree_unsupported_operation',
645
+ {
646
+ locusUrl: 'new-url-2',
647
+ message: 'multiple LOCUS object updates',
648
+ }
649
+ );
650
+ });
651
+
652
+ it('should process locus update correctly when called with added/updated/removed PARTICIPANT objects', () => {
653
+ const newParticipant = {
654
+ id: 'fake-participant-3',
655
+ name: 'New Participant',
656
+ };
657
+ const updatedParticipant2 = {
658
+ id: 'fake-participant-2',
659
+ name: 'Updated Participant Two',
660
+ };
661
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
662
+ // with 1 participant added, 1 updated, and 1 removed
663
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
664
+ updatedObjects: [
665
+ {htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-1'}}, data: null},
666
+ {
667
+ htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-3'}},
668
+ data: newParticipant,
669
+ },
670
+ {
671
+ htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-2'}},
672
+ data: updatedParticipant2,
673
+ },
674
+ ],
675
+ });
676
+
677
+ // check onDeltaLocus() was called with correctly updated locus info
678
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
679
+ ...expectedLocusInfo,
680
+ participants: [newParticipant, updatedParticipant2],
681
+ jsSdkMeta: {removedParticipantIds: ['fake-participant-1']},
682
+ });
683
+ // and that the hashTreeObjectId2ParticipantId map was updated correctly
684
+ assert.isUndefined(locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-1'));
685
+ assert.equal(
686
+ locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-2'),
687
+ 'fake-participant-2'
688
+ );
689
+ assert.equal(
690
+ locusInfo.hashTreeObjectId2ParticipantId.get('fake-ht-participant-3'),
691
+ 'fake-participant-3'
692
+ );
693
+ });
694
+
695
+ it('should process locus update correctly when called with updated MEDIASHARE objects', () => {
696
+ const newMediaShare = {
697
+ id: 'new-mediaShare-3',
698
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-3', version: 100}},
699
+ };
700
+ const updatedMediaShare2 = {
701
+ id: 'fake-media-share-2',
702
+ someNewProp: 'newValue',
703
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 100}},
704
+ };
705
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
706
+ // with 1 participant added, 1 updated, and 1 removed
707
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
708
+ updatedObjects: [
709
+ {htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1'}}, data: null},
710
+ {
711
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2'}},
712
+ data: updatedMediaShare2,
713
+ },
714
+ {
715
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-3'}},
716
+ data: newMediaShare,
717
+ },
718
+ ],
719
+ });
720
+
721
+ // check onDeltaLocus() was called with correctly updated locus info
722
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
723
+ ...expectedLocusInfo,
724
+ mediaShares: [updatedMediaShare2, newMediaShare],
725
+ });
726
+ });
727
+
728
+ it('should process locus update correctly when called with a combination of various updated objects', () => {
729
+ const newSelf = {
730
+ id: 'new-self',
731
+ visibleDataSets: ['dataset1', 'dataset2'],
732
+ };
733
+ const updatedMediaShare2 = {
734
+ id: 'fake-media-share-2',
735
+ someNewProp: 'newValue',
736
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2', version: 100}},
737
+ };
738
+ const updatedParticipant2 = {
739
+ id: 'fake-participant-2',
740
+ name: 'Updated Participant Two',
741
+ };
742
+
743
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
744
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
745
+ updatedObjects: [
746
+ {
747
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2'}},
748
+ data: updatedMediaShare2,
749
+ },
750
+ {htMeta: {elementId: {type: 'self'}}, data: newSelf},
751
+ {
752
+ htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-2'}},
753
+ data: updatedParticipant2,
754
+ },
755
+ ],
756
+ });
757
+
758
+ // check onDeltaLocus() was called with correctly updated locus info
759
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
760
+ ...expectedLocusInfo,
761
+ mediaShares: [
762
+ {
763
+ id: 'fake-media-share-1',
764
+ htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1', version: 1}},
765
+ },
766
+ updatedMediaShare2,
767
+ ],
768
+ participants: [updatedParticipant2],
769
+ self: newSelf,
770
+ });
771
+ });
772
+
773
+ it('should process locus update correctly when called with multiple CONTROL object updates', () => {
774
+ const firstControl = {
775
+ muteOnEntry: {enabled: true},
776
+ lock: {locked: true, meta: {lastModified: 'YESTERDAY', modifiedBy: 'John Doe'}},
777
+ };
778
+ const secondControl = {
779
+ reactions: {enabled: true},
780
+ };
781
+
782
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
783
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
784
+ updatedObjects: [
785
+ {
786
+ htMeta: {elementId: {type: 'controlentry', id: 'control-1'}},
787
+ data: firstControl,
788
+ },
789
+ {
790
+ htMeta: {elementId: {type: 'controlentry', id: 'control-2'}},
791
+ data: secondControl,
792
+ },
793
+ ],
794
+ });
795
+
796
+ // check onDeltaLocus() was called with correctly updated locus info
797
+ // all keys from both controls should be merged into the controls object
798
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
799
+ ...expectedLocusInfo,
800
+ controls: {
801
+ id: 'fake-controls',
802
+ muteOnEntry: {enabled: true},
803
+ lock: {locked: true, meta: {lastModified: 'YESTERDAY', modifiedBy: 'John Doe'}},
804
+ reactions: {enabled: true},
805
+ },
806
+ });
807
+ });
808
+
809
+ it('should process locus update correctly when CONTROL object is received with no data', () => {
810
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
811
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
812
+ updatedObjects: [
813
+ {
814
+ htMeta: {elementId: {type: 'controlentry', id: 'some-control-id'}},
815
+ data: null,
816
+ },
817
+ ],
818
+ });
819
+
820
+ // check onDeltaLocus() was called with correctly updated locus info
821
+ // when data is null, it should be ignored and not change the controls
822
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
823
+ ...expectedLocusInfo,
824
+ });
825
+ });
826
+
827
+ it('should handle MEETING_ENDED correctly', () => {
828
+ const fakeMeeting = {id: 'fake-meeting-from-collection'};
829
+ const collectionGetStub = sinon
830
+ .stub(locusInfo.webex.meetings.meetingCollection, 'get')
831
+ .returns(fakeMeeting);
832
+ const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
833
+
834
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
835
+ locusInfoUpdateCallback(MEETING_ENDED);
836
+
837
+ assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
838
+ assert.calledOnceWithExactly(
839
+ destroyStub,
840
+ fakeMeeting,
841
+ MEETING_REMOVED_REASON.SELF_REMOVED
842
+ );
843
+ });
844
+ });
845
+ });
846
+
80
847
  describe('#updateControls', () => {
81
848
  let newControls;
82
849
 
@@ -2041,7 +2808,7 @@ describe('plugin-meetings', () => {
2041
2808
  });
2042
2809
 
2043
2810
  describe('#handleLocusAPIResponse', () => {
2044
- it('calls handleLocusDelta', () => {
2811
+ it('calls handleLocusDelta when we are not using hash trees', () => {
2045
2812
  const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
2046
2813
 
2047
2814
  sinon.stub(locusInfo, 'handleLocusDelta');
@@ -2050,6 +2817,23 @@ describe('plugin-meetings', () => {
2050
2817
 
2051
2818
  assert.calledWith(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
2052
2819
  });
2820
+ it('calls hash tree parser when we are using hash trees', () => {
2821
+ const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
2822
+ const fakeDataSets = [{name: 'dataset1', url: 'http://test.com'}];
2823
+ const responseBody = {locus: fakeLocus, dataSets: fakeDataSets};
2824
+
2825
+ // Create a mock hash tree parser
2826
+ const mockHashTreeParser = {
2827
+ handleLocusUpdate: sinon.stub(),
2828
+ };
2829
+ locusInfo.hashTreeParser = mockHashTreeParser;
2830
+
2831
+ sinon.stub(locusInfo, 'onDeltaLocus');
2832
+
2833
+ locusInfo.handleLocusAPIResponse(mockMeeting, responseBody);
2834
+
2835
+ assert.calledOnceWithExactly(mockHashTreeParser.handleLocusUpdate, responseBody);
2836
+ });
2053
2837
  });
2054
2838
 
2055
2839
  describe('#LocusDeltaEvents', () => {
@@ -2122,8 +2906,7 @@ describe('plugin-meetings', () => {
2122
2906
  callOrder.push("updateMeetingInfo");
2123
2907
  });
2124
2908
  sinon.stub(locusInfo, "updateMediaShares");
2125
- sinon.stub(locusInfo, "updateParticipantsUrl");
2126
- sinon.stub(locusInfo, "updateReplace");
2909
+ sinon.stub(locusInfo, "updateReplaces");
2127
2910
  sinon.stub(locusInfo, "updateSelf");
2128
2911
  sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
2129
2912
  callOrder.push("updateLocusUrl");
@@ -2131,10 +2914,8 @@ describe('plugin-meetings', () => {
2131
2914
  sinon.stub(locusInfo, "updateAclUrl");
2132
2915
  sinon.stub(locusInfo, "updateBasequence");
2133
2916
  sinon.stub(locusInfo, "updateSequence");
2134
- sinon.stub(locusInfo, "updateMemberShip");
2135
- sinon.stub(locusInfo, "updateIdentifiers");
2136
2917
  sinon.stub(locusInfo, "updateEmbeddedApps");
2137
- sinon.stub(locusInfo, "updateResources");
2918
+ sinon.stub(locusInfo, "updateLinks");
2138
2919
  sinon.stub(locusInfo, "compareAndUpdate");
2139
2920
 
2140
2921
  locusInfo.updateLocusInfo(locus);
@@ -2158,17 +2939,14 @@ describe('plugin-meetings', () => {
2158
2939
  locusInfo.updateHostInfo = sinon.stub();
2159
2940
  locusInfo.updateMeetingInfo = sinon.stub();
2160
2941
  locusInfo.updateMediaShares = sinon.stub();
2161
- locusInfo.updateParticipantsUrl = sinon.stub();
2162
- locusInfo.updateReplace = sinon.stub();
2942
+ locusInfo.updateReplaces = sinon.stub();
2163
2943
  locusInfo.updateSelf = sinon.stub();
2164
2944
  locusInfo.updateLocusUrl = sinon.stub();
2165
2945
  locusInfo.updateAclUrl = sinon.stub();
2166
2946
  locusInfo.updateBasequence = sinon.stub();
2167
2947
  locusInfo.updateSequence = sinon.stub();
2168
- locusInfo.updateMemberShip = sinon.stub();
2169
- locusInfo.updateIdentifiers = sinon.stub();
2170
2948
  locusInfo.updateEmbeddedApps = sinon.stub();
2171
- locusInfo.updateResources = sinon.stub();
2949
+ locusInfo.updateLinks = sinon.stub();
2172
2950
  locusInfo.compareAndUpdate = sinon.stub();
2173
2951
 
2174
2952
  locusInfo.updateLocusInfo(newLocus);
@@ -2180,21 +2958,49 @@ describe('plugin-meetings', () => {
2180
2958
  assert.notCalled(locusInfo.updateHostInfo);
2181
2959
  assert.notCalled(locusInfo.updateMeetingInfo);
2182
2960
  assert.notCalled(locusInfo.updateMediaShares);
2183
- assert.notCalled(locusInfo.updateParticipantsUrl);
2184
- assert.notCalled(locusInfo.updateReplace);
2961
+ assert.notCalled(locusInfo.updateReplaces);
2185
2962
  assert.notCalled(locusInfo.updateSelf);
2186
2963
  assert.notCalled(locusInfo.updateLocusUrl);
2187
2964
  assert.notCalled(locusInfo.updateAclUrl);
2188
2965
  assert.notCalled(locusInfo.updateBasequence);
2189
2966
  assert.notCalled(locusInfo.updateSequence);
2190
- assert.notCalled(locusInfo.updateMemberShip);
2191
- assert.notCalled(locusInfo.updateIdentifiers);
2192
2967
  assert.notCalled(locusInfo.updateEmbeddedApps);
2193
- assert.notCalled(locusInfo.updateResources);
2968
+ assert.notCalled(locusInfo.updateLinks);
2194
2969
  assert.notCalled(locusInfo.compareAndUpdate);
2195
2970
  });
2196
2971
 
2972
+ it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
2973
+ // this test verifies that the top-level properties of Locus DTO are copied
2974
+ // into LocusInfo class and set as top level properties too
2975
+ // this is important, because the code handling Locus hass trees relies on it, see updateFromHashTree()
2976
+ const info = {id: 'info id'};
2977
+ const fullState = {id: 'fullState id'};
2978
+ const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
2979
+ const self = {id: 'self id'};
2980
+ const mediaShares = [{id: 'fake media share'}];
2981
+
2982
+ sinon.stub(SelfUtils, 'getSelves').returns({
2983
+ current: {},
2984
+ previous: {},
2985
+ updates: {},
2986
+ });
2987
+
2988
+ const newLocus = {
2989
+ info,
2990
+ fullState,
2991
+ links,
2992
+ self,
2993
+ mediaShares,
2994
+ };
2995
+
2996
+ locusInfo.updateLocusInfo(newLocus);
2197
2997
 
2998
+ assert.deepEqual(locusInfo.info, newLocus.info);
2999
+ assert.deepEqual(locusInfo.fullState, newLocus.fullState);
3000
+ assert.deepEqual(locusInfo.links, newLocus.links);
3001
+ assert.deepEqual(locusInfo.self, newLocus.self);
3002
+ assert.deepEqual(locusInfo.mediaShares, newLocus.mediaShares);
3003
+ });
2198
3004
 
2199
3005
  it('onFullLocus() updates the working-copy of locus parser', () => {
2200
3006
  const eventType = 'fakeEvent';
@@ -3351,5 +4157,34 @@ describe('plugin-meetings', () => {
3351
4157
  assert.calledWith(updateLocusInfoStub.getCall(2), deltaEvents[7]);
3352
4158
  });
3353
4159
  });
4160
+
4161
+ describe('#parse', () => {
4162
+ it('handles hash tree messages correctly', () => {
4163
+ const fakeHashTreeMessage = {
4164
+ locusStateElements: [
4165
+ {
4166
+ htMeta: {elementId: {type: 'self'}},
4167
+ data: {visibleDataSets: ['dataset1']},
4168
+ },
4169
+ ],
4170
+ dataSets: [{name: 'dataset1', url: 'http://test.com'}],
4171
+ };
4172
+
4173
+ const data = {
4174
+ eventType: LOCUSEVENT.HASH_TREE_DATA_UPDATED,
4175
+ stateElementsMessage: fakeHashTreeMessage,
4176
+ };
4177
+
4178
+ // Create a mock hash tree parser
4179
+ const mockHashTreeParser = {
4180
+ handleMessage: sinon.stub(),
4181
+ };
4182
+ locusInfo.hashTreeParser = mockHashTreeParser;
4183
+
4184
+ locusInfo.parse(mockMeeting, data);
4185
+
4186
+ assert.calledOnceWithExactly(mockHashTreeParser.handleMessage, fakeHashTreeMessage);
4187
+ });
4188
+ });
3354
4189
  });
3355
4190
  });