@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
@@ -1,4 +1,4 @@
1
- import {isEqual, assignWith, cloneDeep, isEmpty, forEach} from 'lodash';
1
+ import {isEqual, assignWith, cloneDeep, isEmpty} from 'lodash';
2
2
 
3
3
  import LoggerProxy from '../common/logs/logger-proxy';
4
4
  import EventsScope from '../common/events/events-scope';
@@ -17,7 +17,8 @@ import {
17
17
  MEETING_REMOVED_REASON,
18
18
  CALL_REMOVED_REASON,
19
19
  RECORDING_STATE,
20
- BREAKOUTS,
20
+ Enum,
21
+ SELF_ROLES,
21
22
  } from '../constants';
22
23
 
23
24
  import InfoUtils from './infoUtils';
@@ -30,52 +31,54 @@ import MediaSharesUtils from './mediaSharesUtils';
30
31
  import LocusDeltaParser from './parser';
31
32
  import Metrics from '../metrics';
32
33
  import BEHAVIORAL_METRICS from '../metrics/constants';
33
-
34
- export type LocusDTO = {
35
- controls?: any;
36
- fullState?: {
37
- active: boolean;
38
- count: number;
39
- lastActive: string;
40
- locked: boolean;
41
- sessionId: string;
42
- seessionIds: string[];
43
- startTime: number;
44
- state: string;
45
- type: string;
46
- };
47
- host?: {
48
- id: string;
49
- incomingCallProtocols: any[];
50
- isExternal: boolean;
51
- name: string;
52
- orgId: string;
34
+ import HashTreeParser, {
35
+ DataSet,
36
+ HashTreeMessage,
37
+ LocusInfoUpdateType,
38
+ } from '../hashTree/hashTreeParser';
39
+ import {HashTreeObject, ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
40
+ import {isSelf} from '../hashTree/utils';
41
+ import {Links, LocusDTO, LocusFullState} from './types';
42
+
43
+ export type LocusLLMEvent = {
44
+ data: {
45
+ eventType: typeof LOCUSEVENT.HASH_TREE_DATA_UPDATED;
46
+ stateElementsMessage: HashTreeMessage;
53
47
  };
54
- info?: any;
55
- links?: any;
56
- mediaShares?: any[];
57
- meetings?: any[];
58
- participants: any[];
59
- replaces?: any[];
60
- self?: any;
61
- sequence?: {
62
- dirtyParticipants: number;
63
- entries: number[];
64
- rangeEnd: number;
65
- rangeStart: number;
66
- sequenceHash: number;
67
- sessionToken: string;
68
- since: string;
69
- totalParticipants: number;
70
- };
71
- syncUrl?: string;
72
- url?: string;
73
48
  };
74
49
 
50
+ // list of top level keys in Locus DTO relevant for Hash Tree DTOs processing
51
+ // it does not contain fields specific to classic Locus DTOs like sequence or baseSequence
52
+ const LocusDtoTopLevelKeys = [
53
+ 'controls',
54
+ 'fullState',
55
+ 'host',
56
+ 'info',
57
+ 'links',
58
+ 'mediaShares',
59
+ 'meetings',
60
+ 'participants',
61
+ 'replaces',
62
+ 'self',
63
+ 'sequence',
64
+ 'syncUrl',
65
+ 'url',
66
+ 'htMeta', // only exists when hash trees are used
67
+ ];
68
+
75
69
  export type LocusApiResponseBody = {
70
+ dataSets?: DataSet[];
76
71
  locus: LocusDTO; // this LocusDTO here might not be the full one (for example it won't have all the participants, but it should have self)
77
72
  };
78
73
 
74
+ const LocusObjectStateAfterUpdates = {
75
+ unchanged: 'unchanged',
76
+ removed: 'removed',
77
+ updated: 'updated',
78
+ } as const;
79
+
80
+ type LocusObjectStateAfterUpdates = Enum<typeof LocusObjectStateAfterUpdates>;
81
+
79
82
  /**
80
83
  * @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
81
84
  * @export
@@ -93,10 +96,7 @@ export default class LocusInfo extends EventsScope {
93
96
  aclUrl: any;
94
97
  baseSequence: any;
95
98
  created: any;
96
- identities: any;
97
- membership: any;
98
99
  participants: any;
99
- participantsUrl: any;
100
100
  replaces: any;
101
101
  scheduledMeeting: any;
102
102
  sequence: any;
@@ -108,12 +108,14 @@ export default class LocusInfo extends EventsScope {
108
108
  info: any;
109
109
  roles: any;
110
110
  mediaShares: any;
111
- replace: any;
112
111
  url: any;
113
- services: any;
114
- resources: any;
112
+ links?: Links;
115
113
  mainSessionLocusCache: any;
116
114
  self: any;
115
+ hashTreeParser?: HashTreeParser;
116
+ hashTreeObjectId2ParticipantId: Map<number, string>; // mapping of hash tree object ids to participant ids
117
+ classicVsHashTreeMismatchMetricCounter = 0;
118
+
117
119
  /**
118
120
  * Constructor
119
121
  * @param {function} updateMeeting callback to update the meeting object from an object
@@ -132,10 +134,12 @@ export default class LocusInfo extends EventsScope {
132
134
  this.meetingId = meetingId;
133
135
  this.updateMeeting = updateMeeting;
134
136
  this.locusParser = new LocusDeltaParser();
137
+ this.hashTreeObjectId2ParticipantId = new Map();
135
138
  }
136
139
 
137
140
  /**
138
141
  * Does a Locus sync. It tries to get the latest delta DTO or if it can't, it falls back to getting the full Locus DTO.
142
+ * WARNING: This function must not be used for hash tree based Locus meetings.
139
143
  *
140
144
  * @param {Meeting} meeting
141
145
  * @param {boolean} isLocusUrlChanged
@@ -319,13 +323,10 @@ export default class LocusInfo extends EventsScope {
319
323
  init(locus: any = {}) {
320
324
  this.created = locus.created || null;
321
325
  this.scheduledMeeting = locus.meeting || null;
322
- this.participantsUrl = locus.participantsUrl || null;
323
326
  this.replaces = locus.replaces || null;
324
327
  this.aclUrl = locus.aclUrl || null;
325
328
  this.baseSequence = locus.baseSequence || null;
326
329
  this.sequence = locus.sequence || null;
327
- this.membership = locus.membership || null;
328
- this.identities = locus.identities || null;
329
330
  this.participants = locus.participants || null;
330
331
 
331
332
  /**
@@ -351,19 +352,113 @@ export default class LocusInfo extends EventsScope {
351
352
  this.updateSelf(locus.self);
352
353
  this.updateHostInfo(locus.host);
353
354
  this.updateMediaShares(locus.mediaShares);
354
- this.updateServices(locus.links?.services);
355
- this.updateResources(locus.links?.resources);
355
+ this.updateLinks(locus.links);
356
356
  }
357
357
 
358
358
  /**
359
- * @param {Object} locus
359
+ * Creates the HashTreeParser instance.
360
+ * @param {Object} initial locus data
361
+ * @returns {void}
362
+ */
363
+ private createHashTreeParser({
364
+ initialLocus,
365
+ }: {
366
+ initialLocus: {
367
+ dataSets: Array<DataSet>;
368
+ locus: any;
369
+ };
370
+ }) {
371
+ return new HashTreeParser({
372
+ initialLocus,
373
+ webexRequest: this.webex.request.bind(this.webex),
374
+ locusInfoUpdateCallback: this.updateFromHashTree.bind(this),
375
+ debugId: `HT-${this.meetingId.substring(0, 4)}`,
376
+ });
377
+ }
378
+
379
+ /**
380
+ * @param {Object} data - data to initialize locus info with. It may be from a join or GET /loci response or from a Mercury event that triggers a creation of meeting object
360
381
  * @returns {undefined}
361
382
  * @memberof LocusInfo
362
383
  */
363
- initialSetup(locus: object) {
364
- this.updateLocusCache(locus);
365
- this.onFullLocus(locus);
384
+ async initialSetup(
385
+ data:
386
+ | {
387
+ trigger: 'join-response';
388
+ locus: LocusDTO;
389
+ dataSets?: DataSet[];
390
+ }
391
+ | {
392
+ trigger: 'locus-message';
393
+ locus?: LocusDTO;
394
+ hashTreeMessage?: HashTreeMessage;
395
+ }
396
+ | {
397
+ trigger: 'get-loci-response';
398
+ locus?: LocusDTO;
399
+ }
400
+ ) {
401
+ switch (data.trigger) {
402
+ case 'locus-message':
403
+ if (data.hashTreeMessage) {
404
+ // we need the SELF object to be in the received message, because it contains visibleDataSets
405
+ // and these are needed to initialize all the hash trees
406
+ const selfObject = data.hashTreeMessage.locusStateElements?.find((el) => isSelf(el));
407
+
408
+ if (!selfObject?.data?.visibleDataSets) {
409
+ LoggerProxy.logger.warn(
410
+ `Locus-info:index#initialSetup --> cannot initialize HashTreeParser, SELF object with visibleDataSets is missing in the message`
411
+ );
366
412
 
413
+ throw new Error('SELF object with visibleDataSets is missing in the message');
414
+ }
415
+
416
+ LoggerProxy.logger.info(
417
+ 'Locus-info:index#initialSetup --> creating HashTreeParser from message'
418
+ );
419
+ // first create the HashTreeParser, but don't initialize it with any data yet
420
+ // pass just a fake locus that contains only the visibleDataSets
421
+ this.hashTreeParser = this.createHashTreeParser({
422
+ initialLocus: {
423
+ locus: {self: {visibleDataSets: selfObject.data.visibleDataSets}},
424
+ dataSets: [], // empty, because they will be populated in initializeFromMessage() call // dataSets: data.hashTreeMessage.dataSets,
425
+ },
426
+ });
427
+
428
+ // now handle the message - that should populate all the visible datasets
429
+ await this.hashTreeParser.initializeFromMessage(data.hashTreeMessage);
430
+ } else {
431
+ // "classic" Locus case, no hash trees involved
432
+ this.updateLocusCache(data.locus);
433
+ this.onFullLocus(data.locus, undefined);
434
+ }
435
+ break;
436
+ case 'join-response':
437
+ this.updateLocusCache(data.locus);
438
+ this.onFullLocus(data.locus, undefined, data.dataSets);
439
+ break;
440
+ case 'get-loci-response':
441
+ if (data.locus?.links?.resources?.visibleDataSets?.url) {
442
+ LoggerProxy.logger.info(
443
+ 'Locus-info:index#initialSetup --> creating HashTreeParser from get-loci-response'
444
+ );
445
+ // first create the HashTreeParser, but don't initialize it with any data yet
446
+ // pass just a fake locus that contains only the visibleDataSets
447
+ this.hashTreeParser = this.createHashTreeParser({
448
+ initialLocus: {
449
+ locus: {self: {visibleDataSets: data.locus?.self?.visibleDataSets}},
450
+ dataSets: [], // empty, because we don't have them yet
451
+ },
452
+ });
453
+
454
+ // now initialize all the data
455
+ await this.hashTreeParser.initializeFromGetLociResponse(data.locus);
456
+ } else {
457
+ // "classic" Locus case, no hash trees involved
458
+ this.updateLocusCache(data.locus);
459
+ this.onFullLocus(data.locus, undefined);
460
+ }
461
+ }
367
462
  // Change it to true after it receives it first locus object
368
463
  this.emitChange = true;
369
464
  }
@@ -375,7 +470,335 @@ export default class LocusInfo extends EventsScope {
375
470
  * @returns {void}
376
471
  */
377
472
  handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
378
- this.handleLocusDelta(responseBody.locus, meeting);
473
+ if (this.hashTreeParser) {
474
+ if (!responseBody.dataSets) {
475
+ this.sendClassicVsHashTreeMismatchMetric(
476
+ meeting,
477
+ `expected hash tree dataSets in API response but they are missing`
478
+ );
479
+ // continuing as we can still manage without responseBody.dataSets, but this is very suspicious
480
+ }
481
+ LoggerProxy.logger.info(
482
+ 'Locus-info:index#handleLocusAPIResponse --> passing Locus API response to HashTreeParser: ',
483
+ responseBody
484
+ );
485
+ // update the data in our hash trees
486
+ this.hashTreeParser.handleLocusUpdate(responseBody);
487
+ } else {
488
+ if (responseBody.dataSets) {
489
+ this.sendClassicVsHashTreeMismatchMetric(
490
+ meeting,
491
+ `unexpected hash tree dataSets in API response`
492
+ );
493
+ }
494
+ // classic Locus delta
495
+ this.handleLocusDelta(responseBody.locus, meeting);
496
+ }
497
+ }
498
+
499
+ /**
500
+ *
501
+ * @param {HashTreeObject} object data set object
502
+ * @param {any} locus
503
+ * @returns {void}
504
+ */
505
+ updateLocusFromHashTreeObject(object: HashTreeObject, locus: LocusDTO): LocusDTO {
506
+ const type = object.htMeta.elementId.type.toLowerCase();
507
+
508
+ const addParticipantObject = (obj: HashTreeObject) => {
509
+ if (!locus.participants) {
510
+ locus.participants = [];
511
+ }
512
+ locus.participants.push(obj.data);
513
+ this.hashTreeObjectId2ParticipantId.set(obj.htMeta.elementId.id, obj.data.id);
514
+ };
515
+
516
+ switch (type) {
517
+ case ObjectType.locus: {
518
+ if (!object.data) {
519
+ // not doing anything here, as we need Locus to always be there (at least some fields)
520
+ // and that's already taken care of in updateFromHashTree()
521
+ LoggerProxy.logger.info(
522
+ `Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object removed, version=${object.htMeta.elementId.version}`
523
+ );
524
+
525
+ return locus;
526
+ }
527
+ // replace the main locus
528
+
529
+ // The Locus object we receive from backend has empty participants array,
530
+ // and may have (although it shouldn't) other fields that are managed by other ObjectTypes
531
+ // like "fullState" or "info", so we're making sure to delete them here
532
+ const locusObjectFromData = object.data;
533
+
534
+ Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
535
+ delete locusObjectFromData[locusDtoKey];
536
+ });
537
+
538
+ locus = {...locus, ...locusObjectFromData};
539
+ LoggerProxy.logger.info(
540
+ `Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object updated to version=${object.htMeta.elementId.version}`
541
+ );
542
+ break;
543
+ }
544
+ case ObjectType.mediaShare:
545
+ if (object.data) {
546
+ LoggerProxy.logger.info(
547
+ `Locus-info:index#updateLocusFromHashTreeObject --> mediaShare id=${
548
+ object.htMeta.elementId.id
549
+ } name='${object.data.name}' updated ${
550
+ object.data.name === 'content'
551
+ ? `floor=${object.data.floor?.disposition}, ${object.data.floor?.beneficiary?.id}`
552
+ : ''
553
+ } version=${object.htMeta.elementId.version}`
554
+ );
555
+ const existingMediaShare = locus.mediaShares?.find(
556
+ (ms) => ms.htMeta.elementId.id === object.htMeta.elementId.id
557
+ );
558
+
559
+ if (existingMediaShare) {
560
+ Object.assign(existingMediaShare, object.data);
561
+ } else {
562
+ locus.mediaShares = locus.mediaShares || [];
563
+ locus.mediaShares.push(object.data);
564
+ }
565
+ } else {
566
+ LoggerProxy.logger.info(
567
+ `Locus-info:index#updateLocusFromHashTreeObject --> mediaShare id=${object.htMeta.elementId.id} removed, version=${object.htMeta.elementId.version}`
568
+ );
569
+ locus.mediaShares = locus.mediaShares?.filter(
570
+ (ms) => ms.htMeta.elementId.id !== object.htMeta.elementId.id
571
+ );
572
+ }
573
+ break;
574
+ case ObjectType.participant:
575
+ LoggerProxy.logger.info(
576
+ `Locus-info:index#updateLocusFromHashTreeObject --> participant id=${
577
+ object.htMeta.elementId.id
578
+ } ${object.data ? 'updated' : 'removed'} version=${object.htMeta.elementId.version}`
579
+ );
580
+ if (object.data) {
581
+ addParticipantObject(object);
582
+ } else {
583
+ const participantId = this.hashTreeObjectId2ParticipantId.get(object.htMeta.elementId.id);
584
+
585
+ if (!locus.jsSdkMeta) {
586
+ locus.jsSdkMeta = {removedParticipantIds: []};
587
+ }
588
+ locus.jsSdkMeta.removedParticipantIds.push(participantId);
589
+ this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
590
+ }
591
+ break;
592
+ case ObjectType.control:
593
+ if (object.data) {
594
+ Object.keys(object.data).forEach((controlKey) => {
595
+ LoggerProxy.logger.info(
596
+ `Locus-info:index#updateLocusFromHashTreeObject --> control ${controlKey} updated:`,
597
+ object.data[controlKey]
598
+ );
599
+ if (!locus.controls) {
600
+ locus.controls = {};
601
+ }
602
+ locus.controls[controlKey] = object.data[controlKey];
603
+ });
604
+ } else {
605
+ LoggerProxy.logger.warn(
606
+ `Locus-info:index#updateLocusFromHashTreeObject --> control object update without data - this is not expected!`
607
+ );
608
+ }
609
+ break;
610
+ case ObjectType.links:
611
+ case ObjectType.info:
612
+ case ObjectType.fullState:
613
+ case ObjectType.self:
614
+ if (!object.data) {
615
+ // self without data is handled inside HashTreeParser and results in LocusInfoUpdateType.MEETING_ENDED, so we should never get here
616
+ // all other types info, fullstate, etc - Locus should never send them without data
617
+ LoggerProxy.logger.warn(
618
+ `Locus-info:index#updateLocusFromHashTreeObject --> received ${type} object without data, this is not expected! version=${object.htMeta.elementId.version}`
619
+ );
620
+ } else {
621
+ LoggerProxy.logger.info(
622
+ `Locus-info:index#updateLocusFromHashTreeObject --> ${type} object updated to version ${object.htMeta.elementId.version}`
623
+ );
624
+ const locusDtoKey = ObjectTypeToLocusKeyMap[type];
625
+ locus[locusDtoKey] = object.data;
626
+
627
+ /* Hash tree based webinar attendees don't receive a Participant object for themselves from Locus,
628
+ but a lot of existing code in SDK and web app expects a member object for self to exist,
629
+ so whenever SELF changes for a webinar attendee, we copy it into a participant object.
630
+ We can do it, because SELF has always all the same properties as a participant object.
631
+ */
632
+ if (
633
+ type === ObjectType.self &&
634
+ locus.info?.isWebinar &&
635
+ object.data.controls?.role?.roles?.find(
636
+ (r) => r.type === SELF_ROLES.ATTENDEE && r.hasRole
637
+ )
638
+ ) {
639
+ LoggerProxy.logger.info(
640
+ `Locus-info:index#updateLocusFromHashTreeObject --> webinar attendee: creating participant object from self`
641
+ );
642
+ addParticipantObject(object);
643
+ }
644
+ }
645
+ break;
646
+ default:
647
+ LoggerProxy.logger.warn(
648
+ `Locus-info:index#updateLocusFromHashTreeObject --> received unsupported object type ${type}`
649
+ );
650
+ break;
651
+ }
652
+
653
+ return locus;
654
+ }
655
+
656
+ /**
657
+ * Sends a metric when we receive something from Locus that uses hash trees while we
658
+ * expect classic deltas or the other way around.
659
+ * @param {Meeting} meeting
660
+ * @param {string} message
661
+ * @returns {void}
662
+ */
663
+ sendClassicVsHashTreeMismatchMetric(meeting: any, message: string) {
664
+ LoggerProxy.logger.warn(
665
+ `Locus-info:index#sendClassicVsHashTreeMismatchMetric --> classic vs hash tree mismatch! ${message}`
666
+ );
667
+
668
+ // we don't want to flood the metrics system
669
+ if (this.classicVsHashTreeMismatchMetricCounter < 5) {
670
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH, {
671
+ correlationId: meeting.correlationId,
672
+ message,
673
+ });
674
+ this.classicVsHashTreeMismatchMetricCounter += 1;
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Handles a hash tree message received from Locus.
680
+ *
681
+ * @param {Meeting} meeting - The meeting object
682
+ * @param {eventType} eventType - The event type
683
+ * @param {HashTreeMessage} message incoming hash tree message
684
+ * @returns {void}
685
+ */
686
+ private handleHashTreeMessage(meeting: any, eventType: LOCUSEVENT, message: HashTreeMessage) {
687
+ if (eventType !== LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
688
+ this.sendClassicVsHashTreeMismatchMetric(
689
+ meeting,
690
+ `got ${eventType}, expected ${LOCUSEVENT.HASH_TREE_DATA_UPDATED}`
691
+ );
692
+
693
+ return;
694
+ }
695
+
696
+ this.hashTreeParser.handleMessage(message);
697
+ }
698
+
699
+ /**
700
+ * Callback registered with HashTreeParser to receive locus info updates.
701
+ * Updates our locus info based on the data parsed by the hash tree parser.
702
+ *
703
+ * @param {LocusInfoUpdateType} updateType - The type of update received.
704
+ * @param {Object} [data] - Additional data for the update, if applicable.
705
+ * @returns {void}
706
+ */
707
+ private updateFromHashTree(
708
+ updateType: LocusInfoUpdateType,
709
+ data?: {updatedObjects: HashTreeObject[]}
710
+ ) {
711
+ switch (updateType) {
712
+ case LocusInfoUpdateType.OBJECTS_UPDATED: {
713
+ // initialize our new locus
714
+ let locus: LocusDTO = {
715
+ participants: [],
716
+ jsSdkMeta: {removedParticipantIds: []},
717
+ };
718
+
719
+ // first go over all the updates and check what happens with the main locus object
720
+ let locusObjectStateAfterUpdates: LocusObjectStateAfterUpdates =
721
+ LocusObjectStateAfterUpdates.unchanged;
722
+ data.updatedObjects.forEach((object) => {
723
+ if (object.htMeta.elementId.type.toLowerCase() === ObjectType.locus) {
724
+ if (locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.updated) {
725
+ // this code doesn't supported it right now,
726
+ // cases for "updated" followed by "removed", or multiple "updated" would need more handling
727
+ // but these should never happen
728
+ LoggerProxy.logger.warn(
729
+ `Locus-info:index#updateFromHashTree --> received multiple LOCUS objects in one update, this is unexpected!`
730
+ );
731
+ Metrics.sendBehavioralMetric(
732
+ BEHAVIORAL_METRICS.LOCUS_HASH_TREE_UNSUPPORTED_OPERATION,
733
+ {
734
+ locusUrl: object.data?.url || this.url,
735
+ message: object.data
736
+ ? 'multiple LOCUS object updates'
737
+ : 'LOCUS object update followed by removal',
738
+ }
739
+ );
740
+ }
741
+ if (object.data) {
742
+ locusObjectStateAfterUpdates = LocusObjectStateAfterUpdates.updated;
743
+ } else {
744
+ locusObjectStateAfterUpdates = LocusObjectStateAfterUpdates.removed;
745
+ }
746
+ }
747
+ });
748
+
749
+ // if Locus object is unchanged or removed, we need to keep using the existing locus
750
+ // because the rest of the locusInfo code expects locus to always be present (with at least some of the fields)
751
+ // if it gets updated, we only need to have the fields that are not part of "locus" object (like "info" or "mediaShares")
752
+ // so that when Locus object gets updated, if the new one is missing some field, that field will
753
+ // be removed from our locusInfo
754
+ if (
755
+ locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.unchanged ||
756
+ locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.removed
757
+ ) {
758
+ // copy over all of existing locus except participants
759
+ LocusDtoTopLevelKeys.forEach((key) => {
760
+ if (key !== 'participants') {
761
+ locus[key] = cloneDeep(this[key]);
762
+ }
763
+ });
764
+ } else {
765
+ // initialize only the fields that are not part of main "Locus" object
766
+ // (except participants, which need to stay empty - that means "no participant changes")
767
+ Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
768
+ if (locusDtoKey !== 'participants') {
769
+ locus[locusDtoKey] = cloneDeep(this[locusDtoKey]);
770
+ }
771
+ });
772
+ }
773
+
774
+ LoggerProxy.logger.info(
775
+ `Locus-info:index#updateFromHashTree --> LOCUS object is ${locusObjectStateAfterUpdates}, all updates: ${JSON.stringify(
776
+ data.updatedObjects.map((o) => ({
777
+ type: o.htMeta.elementId.type,
778
+ id: o.htMeta.elementId.id,
779
+ hasData: !!o.data,
780
+ }))
781
+ )}`
782
+ );
783
+ // now apply all the updates from the hash tree onto the locus
784
+ data.updatedObjects.forEach((object) => {
785
+ locus = this.updateLocusFromHashTreeObject(object, locus);
786
+ });
787
+
788
+ // update our locus info with the new locus
789
+ this.onDeltaLocus(locus);
790
+
791
+ break;
792
+ }
793
+
794
+ case LocusInfoUpdateType.MEETING_ENDED: {
795
+ LoggerProxy.logger.info(
796
+ `Locus-info:index#updateFromHashTree --> received signal that meeting ended, destroying meeting ${this.meetingId}`
797
+ );
798
+ const meeting = this.webex.meetings.meetingCollection.get(this.meetingId);
799
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.SELF_REMOVED);
800
+ }
801
+ }
379
802
  }
380
803
 
381
804
  /**
@@ -385,38 +808,52 @@ export default class LocusInfo extends EventsScope {
385
808
  * @memberof LocusInfo
386
809
  */
387
810
  parse(meeting: any, data: any) {
388
- // eslint-disable-next-line @typescript-eslint/no-shadow
389
- const {eventType} = data;
390
- const locus = this.getTheLocusToUpdate(data.locus);
391
- LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
392
-
393
- locus.jsSdkMeta = {removedParticipantIds: []};
394
-
395
- switch (eventType) {
396
- case LOCUSEVENT.PARTICIPANT_JOIN:
397
- case LOCUSEVENT.PARTICIPANT_LEFT:
398
- case LOCUSEVENT.CONTROLS_UPDATED:
399
- case LOCUSEVENT.PARTICIPANT_AUDIO_MUTED:
400
- case LOCUSEVENT.PARTICIPANT_AUDIO_UNMUTED:
401
- case LOCUSEVENT.PARTICIPANT_VIDEO_MUTED:
402
- case LOCUSEVENT.PARTICIPANT_VIDEO_UNMUTED:
403
- case LOCUSEVENT.SELF_CHANGED:
404
- case LOCUSEVENT.PARTICIPANT_UPDATED:
405
- case LOCUSEVENT.PARTICIPANT_CONTROLS_UPDATED:
406
- case LOCUSEVENT.PARTICIPANT_ROLES_UPDATED:
407
- case LOCUSEVENT.PARTICIPANT_DECLINED:
408
- case LOCUSEVENT.FLOOR_GRANTED:
409
- case LOCUSEVENT.FLOOR_RELEASED:
410
- this.onFullLocus(locus, eventType);
411
- break;
412
- case LOCUSEVENT.DIFFERENCE:
413
- this.handleLocusDelta(locus, meeting);
414
- break;
811
+ if (this.hashTreeParser) {
812
+ this.handleHashTreeMessage(
813
+ meeting,
814
+ data.eventType,
815
+ data.stateElementsMessage as HashTreeMessage
816
+ );
817
+ } else {
818
+ // eslint-disable-next-line @typescript-eslint/no-shadow
819
+ const {eventType} = data;
820
+ const locus = this.getTheLocusToUpdate(data.locus);
821
+ LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
822
+
823
+ locus.jsSdkMeta = {removedParticipantIds: []};
824
+
825
+ switch (eventType) {
826
+ case LOCUSEVENT.PARTICIPANT_JOIN:
827
+ case LOCUSEVENT.PARTICIPANT_LEFT:
828
+ case LOCUSEVENT.CONTROLS_UPDATED:
829
+ case LOCUSEVENT.PARTICIPANT_AUDIO_MUTED:
830
+ case LOCUSEVENT.PARTICIPANT_AUDIO_UNMUTED:
831
+ case LOCUSEVENT.PARTICIPANT_VIDEO_MUTED:
832
+ case LOCUSEVENT.PARTICIPANT_VIDEO_UNMUTED:
833
+ case LOCUSEVENT.SELF_CHANGED:
834
+ case LOCUSEVENT.PARTICIPANT_UPDATED:
835
+ case LOCUSEVENT.PARTICIPANT_CONTROLS_UPDATED:
836
+ case LOCUSEVENT.PARTICIPANT_ROLES_UPDATED:
837
+ case LOCUSEVENT.PARTICIPANT_DECLINED:
838
+ case LOCUSEVENT.FLOOR_GRANTED:
839
+ case LOCUSEVENT.FLOOR_RELEASED:
840
+ this.onFullLocus(locus, eventType);
841
+ break;
842
+ case LOCUSEVENT.DIFFERENCE:
843
+ this.handleLocusDelta(locus, meeting);
844
+ break;
845
+ case LOCUSEVENT.HASH_TREE_DATA_UPDATED:
846
+ this.sendClassicVsHashTreeMismatchMetric(
847
+ meeting,
848
+ `got ${eventType}, expected classic events`
849
+ );
850
+ break;
415
851
 
416
- default:
417
- // Why will there be a event with no eventType ????
418
- // we may not need this, we can get full locus
419
- this.handleLocusDelta(locus, meeting);
852
+ default:
853
+ // Why will there be a event with no eventType ????
854
+ // we may not need this, we can get full locus
855
+ this.handleLocusDelta(locus, meeting);
856
+ }
420
857
  }
421
858
  }
422
859
 
@@ -432,19 +869,45 @@ export default class LocusInfo extends EventsScope {
432
869
  }
433
870
 
434
871
  /**
435
- * updates the locus with full locus object
872
+ * Function for handling full locus when it's using hash trees (so not the "classic" one).
873
+ *
436
874
  * @param {object} locus locus object
437
- * @param {string} eventType particulat locus event
438
- * @returns {object} null
439
- * @memberof LocusInfo
875
+ * @param {string} eventType locus event
876
+ * @param {DataSet[]} dataSets
877
+ * @returns {void}
440
878
  */
441
- onFullLocus(locus: any, eventType?: string) {
442
- if (!locus) {
443
- LoggerProxy.logger.error(
444
- 'Locus-info:index#onFullLocus --> object passed as argument was invalid, continuing.'
879
+ private onFullLocusWithHashTrees(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
880
+ if (!this.hashTreeParser) {
881
+ LoggerProxy.logger.info(`Locus-info:index#onFullLocus --> creating hash tree parser`);
882
+ LoggerProxy.logger.info(
883
+ 'Locus-info:index#onFullLocus --> dataSets:',
884
+ dataSets,
885
+ ' and locus:',
886
+ locus
445
887
  );
888
+ this.hashTreeParser = this.createHashTreeParser({
889
+ initialLocus: {locus, dataSets},
890
+ });
891
+ this.onFullLocusCommon(locus, eventType);
892
+ } else {
893
+ // in this case the Locus we're getting is not necessarily the full one
894
+ // so treat it like if we just got it in any api response
895
+
896
+ LoggerProxy.logger.info(
897
+ 'Locus-info:index#onFullLocus --> hash tree parser already exists, handling it like a normal API response'
898
+ );
899
+ this.handleLocusAPIResponse(undefined, {dataSets, locus});
446
900
  }
901
+ }
447
902
 
903
+ /**
904
+ * Function for handling full locus when it's the "classic" one (not hash trees)
905
+ *
906
+ * @param {object} locus locus object
907
+ * @param {string} eventType locus event
908
+ * @returns {void}
909
+ */
910
+ private onFullLocusClassic(locus: any, eventType?: string) {
448
911
  if (!this.locusParser.isNewFullLocus(locus)) {
449
912
  LoggerProxy.logger.info(
450
913
  `Locus-info:index#onFullLocus --> ignoring old full locus DTO, eventType=${eventType}`
@@ -452,9 +915,47 @@ export default class LocusInfo extends EventsScope {
452
915
 
453
916
  return;
454
917
  }
918
+ this.onFullLocusCommon(locus, eventType);
919
+ }
920
+
921
+ /**
922
+ * updates the locus with full locus object
923
+ * @param {object} locus locus object
924
+ * @param {string} eventType locus event
925
+ * @param {DataSet[]} dataSets
926
+ * @returns {object} null
927
+ * @memberof LocusInfo
928
+ */
929
+ onFullLocus(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
930
+ if (!locus) {
931
+ LoggerProxy.logger.error(
932
+ 'Locus-info:index#onFullLocus --> object passed as argument was invalid, continuing.'
933
+ );
934
+ }
455
935
 
936
+ if (dataSets) {
937
+ // this is the new hashmap Locus DTO format (only applicable to webinars for now)
938
+ this.onFullLocusWithHashTrees(locus, eventType, dataSets);
939
+ } else {
940
+ this.onFullLocusClassic(locus, eventType);
941
+ }
942
+ }
943
+
944
+ /**
945
+ * Common part of handling full locus, used by both classic and hash tree based locus handling
946
+ * @param {object} locus locus object
947
+ * @param {string} eventType locus event
948
+ * @returns {void}
949
+ */
950
+ private onFullLocusCommon(locus: any, eventType?: string) {
456
951
  this.scheduledMeeting = locus.meeting || null;
457
952
  this.participants = locus.participants;
953
+ this.participants?.forEach((participant) => {
954
+ // participant.htMeta is set only for hash tree based locus
955
+ if (participant.htMeta?.elementId.id) {
956
+ this.hashTreeObjectId2ParticipantId.set(participant.htMeta.elementId.id, participant.id);
957
+ }
958
+ });
458
959
  const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
459
960
  this.updateLocusInfo(locus);
460
961
  this.updateParticipants(
@@ -478,8 +979,8 @@ export default class LocusInfo extends EventsScope {
478
979
  // eslint-disable-next-line @typescript-eslint/no-shadow
479
980
  handleOneOnOneEvent(eventType: string) {
480
981
  if (
481
- this.parsedLocus.fullState.type === _CALL_ ||
482
- this.parsedLocus.fullState.type === _SIP_BRIDGE_
982
+ this.parsedLocus.fullState?.type === _CALL_ ||
983
+ this.parsedLocus.fullState?.type === _SIP_BRIDGE_
483
984
  ) {
484
985
  // for 1:1 bob calls alice and alice declines, notify the meeting state
485
986
  if (eventType === LOCUSEVENT.PARTICIPANT_DECLINED) {
@@ -552,17 +1053,13 @@ export default class LocusInfo extends EventsScope {
552
1053
  this.updateLocusUrl(locus.url, ControlsUtils.isMainSessionDTO(locus));
553
1054
  this.updateMeetingInfo(locus.info, locus.self);
554
1055
  this.updateMediaShares(locus.mediaShares);
555
- this.updateParticipantsUrl(locus.participantsUrl);
556
- this.updateReplace(locus.replace);
1056
+ this.updateReplaces(locus.replaces);
557
1057
  this.updateSelf(locus.self);
558
1058
  this.updateAclUrl(locus.aclUrl);
559
1059
  this.updateBasequence(locus.baseSequence);
560
1060
  this.updateSequence(locus.sequence);
561
- this.updateMemberShip(locus.membership);
562
- this.updateIdentifiers(locus.identities);
563
1061
  this.updateEmbeddedApps(locus.embeddedApps);
564
- this.updateServices(locus.links?.services);
565
- this.updateResources(locus.links?.resources);
1062
+ this.updateLinks(locus.links);
566
1063
  this.compareAndUpdate();
567
1064
  // update which required to compare different objects from locus
568
1065
  }
@@ -596,9 +1093,9 @@ export default class LocusInfo extends EventsScope {
596
1093
  */
597
1094
  isMeetingActive() {
598
1095
  if (
599
- this.parsedLocus.fullState.type === _CALL_ ||
600
- this.parsedLocus.fullState.type === _SIP_BRIDGE_ ||
601
- this.parsedLocus.fullState.type === _SPACE_SHARE_
1096
+ this.parsedLocus.fullState?.type === _CALL_ ||
1097
+ this.parsedLocus.fullState?.type === _SIP_BRIDGE_ ||
1098
+ this.parsedLocus.fullState?.type === _SPACE_SHARE_
602
1099
  ) {
603
1100
  // @ts-ignore
604
1101
  const partner = this.getLocusPartner(this.participants, this.self);
@@ -691,7 +1188,7 @@ export default class LocusInfo extends EventsScope {
691
1188
  }
692
1189
  );
693
1190
  }
694
- } else if (this.parsedLocus.fullState.type === _MEETING_) {
1191
+ } else if (this.parsedLocus.fullState?.type === _MEETING_) {
695
1192
  if (
696
1193
  this.fullState &&
697
1194
  (this.fullState.state === LOCUS.STATE.INACTIVE ||
@@ -788,6 +1285,7 @@ export default class LocusInfo extends EventsScope {
788
1285
  compareSelfAndHost() {
789
1286
  // In some cases the host info is not present but the moderator values changes from null to false so it triggers an update
790
1287
  if (
1288
+ this.parsedLocus.self &&
791
1289
  this.parsedLocus.self.selfIdentity === this.parsedLocus.host?.hostId &&
792
1290
  this.parsedLocus.self.moderator
793
1291
  ) {
@@ -1214,17 +1712,19 @@ export default class LocusInfo extends EventsScope {
1214
1712
  }
1215
1713
 
1216
1714
  /**
1217
- * @param {Object} services
1715
+ * Updates links and emits appropriate events if services or resources have changed
1716
+ * @param {Object} links
1218
1717
  * @returns {undefined}
1219
1718
  * @memberof LocusInfo
1220
1719
  */
1221
- updateServices(services: Record<'breakout' | 'record', {url: string}>) {
1222
- if (services && !isEqual(this.services, services)) {
1223
- this.services = services;
1720
+ updateLinks(links?: Links) {
1721
+ const {services, resources} = links || {};
1722
+
1723
+ if (services && !isEqual(this.links?.services, services)) {
1224
1724
  this.emitScoped(
1225
1725
  {
1226
1726
  file: 'locus-info',
1227
- function: 'updateServices',
1727
+ function: 'updateLinks',
1228
1728
  },
1229
1729
  LOCUSINFO.EVENTS.LINKS_SERVICES,
1230
1730
  {
@@ -1232,20 +1732,12 @@ export default class LocusInfo extends EventsScope {
1232
1732
  }
1233
1733
  );
1234
1734
  }
1235
- }
1236
1735
 
1237
- /**
1238
- * @param {Object} resources
1239
- * @returns {undefined}
1240
- * @memberof LocusInfo
1241
- */
1242
- updateResources(resources: Record<'webcastInstance', {url: string}>) {
1243
- if (resources && !isEqual(this.resources, resources)) {
1244
- this.resources = resources;
1736
+ if (resources && !isEqual(this.links?.resources, resources)) {
1245
1737
  this.emitScoped(
1246
1738
  {
1247
1739
  file: 'locus-info',
1248
- function: 'updateResources',
1740
+ function: 'updateLinks',
1249
1741
  },
1250
1742
  LOCUSINFO.EVENTS.LINKS_RESOURCES,
1251
1743
  {
@@ -1253,6 +1745,8 @@ export default class LocusInfo extends EventsScope {
1253
1745
  }
1254
1746
  );
1255
1747
  }
1748
+
1749
+ this.links = links;
1256
1750
  }
1257
1751
 
1258
1752
  /**
@@ -1439,24 +1933,13 @@ export default class LocusInfo extends EventsScope {
1439
1933
  }
1440
1934
 
1441
1935
  /**
1442
- * @param {String} participantsUrl
1443
- * @returns {undefined}
1444
- * @memberof LocusInfo
1445
- */
1446
- updateParticipantsUrl(participantsUrl: string) {
1447
- if (participantsUrl && !isEqual(this.participantsUrl, participantsUrl)) {
1448
- this.participantsUrl = participantsUrl;
1449
- }
1450
- }
1451
-
1452
- /**
1453
- * @param {Object} replace
1936
+ * @param {Object} replaces
1454
1937
  * @returns {undefined}
1455
1938
  * @memberof LocusInfo
1456
1939
  */
1457
- updateReplace(replace: object) {
1458
- if (replace && !isEqual(this.replace, replace)) {
1459
- this.replace = replace;
1940
+ updateReplaces(replaces: object) {
1941
+ if (replaces && !isEqual(this.replaces, replaces)) {
1942
+ this.replaces = replaces;
1460
1943
  }
1461
1944
  }
1462
1945
 
@@ -1487,14 +1970,14 @@ export default class LocusInfo extends EventsScope {
1487
1970
  }
1488
1971
 
1489
1972
  // TODO: check if we need to save the sipUri here as well
1490
- // this.emit(LOCUSINFO.EVENTS.MEETING_UPDATE, SelfUtils.getSipUrl(this.getLocusPartner(participants, self), this.parsedLocus.fullState.type, this.parsedLocus.info.sipUri));
1973
+ // this.emit(LOCUSINFO.EVENTS.MEETING_UPDATE, SelfUtils.getSipUrl(this.getLocusPartner(participants, self), this.parsedLocus.fullState?.type, this.parsedLocus.info?.sipUri));
1491
1974
  const result = SelfUtils.getSipUrl(
1492
1975
  this.getLocusPartner(this.participants, self),
1493
- this.parsedLocus.fullState.type,
1494
- this.parsedLocus.info.sipUri
1976
+ this.parsedLocus.fullState?.type,
1977
+ this.parsedLocus.info?.sipUri
1495
1978
  );
1496
1979
 
1497
- if (result.sipUri) {
1980
+ if (result?.sipUri) {
1498
1981
  this.updateMeeting(result);
1499
1982
  }
1500
1983
 
@@ -1793,28 +2276,6 @@ export default class LocusInfo extends EventsScope {
1793
2276
  }
1794
2277
  }
1795
2278
 
1796
- /**
1797
- * @param {Object} membership
1798
- * @returns {undefined}
1799
- * @memberof LocusInfo
1800
- */
1801
- updateMemberShip(membership: object) {
1802
- if (membership && !isEqual(this.membership, membership)) {
1803
- this.membership = membership;
1804
- }
1805
- }
1806
-
1807
- /**
1808
- * @param {Array} identities
1809
- * @returns {undefined}
1810
- * @memberof LocusInfo
1811
- */
1812
- updateIdentifiers(identities: Array<any>) {
1813
- if (identities && !isEqual(this.identities, identities)) {
1814
- this.identities = identities;
1815
- }
1816
- }
1817
-
1818
2279
  /**
1819
2280
  * check the locus is main session's one or not, if is main session's, update main session cache
1820
2281
  * @param {Object} locus