@webex/plugin-meetings 3.12.0-next.8 → 3.12.0-task-refactor.1

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 (201) hide show
  1. package/dist/annotation/index.js +5 -14
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/config.js +2 -8
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +6 -29
  8. package/dist/constants.js.map +1 -1
  9. package/dist/hashTree/hashTreeParser.js +29 -1563
  10. package/dist/hashTree/hashTreeParser.js.map +1 -1
  11. package/dist/hashTree/types.js +3 -13
  12. package/dist/hashTree/types.js.map +1 -1
  13. package/dist/index.js +2 -11
  14. package/dist/index.js.map +1 -1
  15. package/dist/interceptors/index.js +0 -7
  16. package/dist/interceptors/index.js.map +1 -1
  17. package/dist/interceptors/locusRouteToken.js +5 -27
  18. package/dist/interceptors/locusRouteToken.js.map +1 -1
  19. package/dist/interpretation/index.js +2 -2
  20. package/dist/interpretation/index.js.map +1 -1
  21. package/dist/interpretation/siLanguage.js +1 -1
  22. package/dist/locus-info/controlsUtils.js +3 -7
  23. package/dist/locus-info/controlsUtils.js.map +1 -1
  24. package/dist/locus-info/index.js +247 -642
  25. package/dist/locus-info/index.js.map +1 -1
  26. package/dist/locus-info/selfUtils.js +0 -1
  27. package/dist/locus-info/selfUtils.js.map +1 -1
  28. package/dist/locus-info/types.js.map +1 -1
  29. package/dist/media/MediaConnectionAwaiter.js +1 -57
  30. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  31. package/dist/media/properties.js +2 -4
  32. package/dist/media/properties.js.map +1 -1
  33. package/dist/meeting/in-meeting-actions.js +1 -7
  34. package/dist/meeting/in-meeting-actions.js.map +1 -1
  35. package/dist/meeting/index.js +1036 -1481
  36. package/dist/meeting/index.js.map +1 -1
  37. package/dist/meeting/request.js +0 -50
  38. package/dist/meeting/request.js.map +1 -1
  39. package/dist/meeting/request.type.js.map +1 -1
  40. package/dist/meeting/util.js +3 -133
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meetings/index.js +59 -142
  43. package/dist/meetings/index.js.map +1 -1
  44. package/dist/meetings/util.js +7 -11
  45. package/dist/meetings/util.js.map +1 -1
  46. package/dist/member/index.js +0 -10
  47. package/dist/member/index.js.map +1 -1
  48. package/dist/member/util.js +0 -10
  49. package/dist/member/util.js.map +1 -1
  50. package/dist/metrics/constants.js +1 -7
  51. package/dist/metrics/constants.js.map +1 -1
  52. package/dist/multistream/mediaRequestManager.js +60 -9
  53. package/dist/multistream/mediaRequestManager.js.map +1 -1
  54. package/dist/multistream/remoteMediaManager.js +0 -11
  55. package/dist/multistream/remoteMediaManager.js.map +1 -1
  56. package/dist/multistream/sendSlotManager.js +2 -116
  57. package/dist/multistream/sendSlotManager.js.map +1 -1
  58. package/dist/reachability/clusterReachability.js +18 -171
  59. package/dist/reachability/clusterReachability.js.map +1 -1
  60. package/dist/reachability/index.js +11 -21
  61. package/dist/reachability/index.js.map +1 -1
  62. package/dist/reachability/reachabilityPeerConnection.js +1 -1
  63. package/dist/reachability/reachabilityPeerConnection.js.map +1 -1
  64. package/dist/reactions/reactions.type.js.map +1 -1
  65. package/dist/reconnection-manager/index.js +1 -0
  66. package/dist/reconnection-manager/index.js.map +1 -1
  67. package/dist/types/common/browser-detection.d.ts +0 -1
  68. package/dist/types/common/events/events-scope.d.ts +0 -1
  69. package/dist/types/common/events/events.d.ts +0 -1
  70. package/dist/types/config.d.ts +0 -5
  71. package/dist/types/constants.d.ts +1 -24
  72. package/dist/types/hashTree/hashTreeParser.d.ts +11 -260
  73. package/dist/types/hashTree/types.d.ts +0 -20
  74. package/dist/types/index.d.ts +0 -1
  75. package/dist/types/interceptors/index.d.ts +1 -2
  76. package/dist/types/interceptors/locusRouteToken.d.ts +0 -2
  77. package/dist/types/locus-info/index.d.ts +47 -68
  78. package/dist/types/locus-info/types.d.ts +12 -28
  79. package/dist/types/media/MediaConnectionAwaiter.d.ts +1 -10
  80. package/dist/types/media/properties.d.ts +1 -2
  81. package/dist/types/meeting/in-meeting-actions.d.ts +0 -6
  82. package/dist/types/meeting/index.d.ts +7 -86
  83. package/dist/types/meeting/request.d.ts +1 -16
  84. package/dist/types/meeting/request.type.d.ts +0 -5
  85. package/dist/types/meeting/util.d.ts +0 -31
  86. package/dist/types/meeting-info/util.d.ts +0 -1
  87. package/dist/types/meeting-info/utilv2.d.ts +0 -1
  88. package/dist/types/meetings/index.d.ts +2 -4
  89. package/dist/types/member/index.d.ts +0 -1
  90. package/dist/types/member/types.d.ts +4 -4
  91. package/dist/types/member/util.d.ts +0 -5
  92. package/dist/types/metrics/constants.d.ts +0 -6
  93. package/dist/types/multistream/mediaRequestManager.d.ts +23 -0
  94. package/dist/types/multistream/sendSlotManager.d.ts +1 -23
  95. package/dist/types/reachability/clusterReachability.d.ts +3 -30
  96. package/dist/types/reactions/reactions.type.d.ts +0 -1
  97. package/dist/types/recording-controller/util.d.ts +5 -5
  98. package/dist/types/roap/index.d.ts +1 -1
  99. package/dist/webinar/index.js +163 -438
  100. package/dist/webinar/index.js.map +1 -1
  101. package/package.json +24 -26
  102. package/src/annotation/index.ts +7 -27
  103. package/src/config.ts +0 -5
  104. package/src/constants.ts +1 -30
  105. package/src/hashTree/hashTreeParser.ts +25 -1523
  106. package/src/hashTree/types.ts +1 -24
  107. package/src/index.ts +1 -8
  108. package/src/interceptors/index.ts +1 -2
  109. package/src/interceptors/locusRouteToken.ts +5 -22
  110. package/src/interpretation/index.ts +2 -2
  111. package/src/locus-info/controlsUtils.ts +0 -17
  112. package/src/locus-info/index.ts +213 -707
  113. package/src/locus-info/selfUtils.ts +0 -1
  114. package/src/locus-info/types.ts +12 -27
  115. package/src/media/MediaConnectionAwaiter.ts +1 -41
  116. package/src/media/properties.ts +1 -3
  117. package/src/meeting/in-meeting-actions.ts +0 -12
  118. package/src/meeting/index.ts +84 -461
  119. package/src/meeting/request.ts +0 -42
  120. package/src/meeting/request.type.ts +0 -6
  121. package/src/meeting/util.ts +2 -160
  122. package/src/meetings/index.ts +60 -180
  123. package/src/meetings/util.ts +9 -10
  124. package/src/member/index.ts +0 -10
  125. package/src/member/util.ts +0 -12
  126. package/src/metrics/constants.ts +0 -7
  127. package/src/multistream/mediaRequestManager.ts +54 -4
  128. package/src/multistream/remoteMediaManager.ts +0 -13
  129. package/src/multistream/sendSlotManager.ts +3 -97
  130. package/src/reachability/clusterReachability.ts +27 -153
  131. package/src/reachability/index.ts +1 -15
  132. package/src/reachability/reachabilityPeerConnection.ts +1 -3
  133. package/src/reactions/reactions.type.ts +0 -1
  134. package/src/reconnection-manager/index.ts +1 -0
  135. package/src/webinar/index.ts +6 -265
  136. package/test/unit/spec/annotation/index.ts +7 -69
  137. package/test/unit/spec/interceptors/locusRouteToken.ts +0 -44
  138. package/test/unit/spec/locus-info/controlsUtils.js +1 -56
  139. package/test/unit/spec/locus-info/index.js +90 -1457
  140. package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -41
  141. package/test/unit/spec/media/properties.ts +3 -12
  142. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -8
  143. package/test/unit/spec/meeting/index.js +128 -981
  144. package/test/unit/spec/meeting/request.js +0 -70
  145. package/test/unit/spec/meeting/utils.js +26 -438
  146. package/test/unit/spec/meetings/index.js +33 -845
  147. package/test/unit/spec/meetings/utils.js +1 -51
  148. package/test/unit/spec/member/index.js +4 -28
  149. package/test/unit/spec/member/util.js +27 -65
  150. package/test/unit/spec/multistream/mediaRequestManager.ts +85 -2
  151. package/test/unit/spec/multistream/remoteMediaManager.ts +0 -30
  152. package/test/unit/spec/multistream/sendSlotManager.ts +36 -135
  153. package/test/unit/spec/reachability/clusterReachability.ts +1 -125
  154. package/test/unit/spec/reachability/index.ts +3 -26
  155. package/test/unit/spec/reconnection-manager/index.js +8 -4
  156. package/test/unit/spec/webinar/index.ts +37 -534
  157. package/dist/aiEnableRequest/index.js +0 -184
  158. package/dist/aiEnableRequest/index.js.map +0 -1
  159. package/dist/aiEnableRequest/utils.js +0 -36
  160. package/dist/aiEnableRequest/utils.js.map +0 -1
  161. package/dist/hashTree/constants.js +0 -22
  162. package/dist/hashTree/constants.js.map +0 -1
  163. package/dist/hashTree/hashTree.js +0 -533
  164. package/dist/hashTree/hashTree.js.map +0 -1
  165. package/dist/hashTree/utils.js +0 -69
  166. package/dist/hashTree/utils.js.map +0 -1
  167. package/dist/interceptors/constant.js +0 -12
  168. package/dist/interceptors/constant.js.map +0 -1
  169. package/dist/interceptors/dataChannelAuthToken.js +0 -290
  170. package/dist/interceptors/dataChannelAuthToken.js.map +0 -1
  171. package/dist/interceptors/utils.js +0 -27
  172. package/dist/interceptors/utils.js.map +0 -1
  173. package/dist/types/aiEnableRequest/index.d.ts +0 -5
  174. package/dist/types/aiEnableRequest/utils.d.ts +0 -2
  175. package/dist/types/hashTree/constants.d.ts +0 -9
  176. package/dist/types/hashTree/hashTree.d.ts +0 -136
  177. package/dist/types/hashTree/utils.d.ts +0 -22
  178. package/dist/types/interceptors/constant.d.ts +0 -5
  179. package/dist/types/interceptors/dataChannelAuthToken.d.ts +0 -43
  180. package/dist/types/interceptors/utils.d.ts +0 -1
  181. package/dist/types/webinar/utils.d.ts +0 -6
  182. package/dist/webinar/utils.js +0 -25
  183. package/dist/webinar/utils.js.map +0 -1
  184. package/src/aiEnableRequest/README.md +0 -84
  185. package/src/aiEnableRequest/index.ts +0 -170
  186. package/src/aiEnableRequest/utils.ts +0 -25
  187. package/src/hashTree/constants.ts +0 -10
  188. package/src/hashTree/hashTree.ts +0 -480
  189. package/src/hashTree/utils.ts +0 -62
  190. package/src/interceptors/constant.ts +0 -6
  191. package/src/interceptors/dataChannelAuthToken.ts +0 -170
  192. package/src/interceptors/utils.ts +0 -16
  193. package/src/webinar/utils.ts +0 -16
  194. package/test/unit/spec/aiEnableRequest/index.ts +0 -981
  195. package/test/unit/spec/aiEnableRequest/utils.ts +0 -130
  196. package/test/unit/spec/hashTree/hashTree.ts +0 -721
  197. package/test/unit/spec/hashTree/hashTreeParser.ts +0 -3670
  198. package/test/unit/spec/hashTree/utils.ts +0 -140
  199. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +0 -210
  200. package/test/unit/spec/interceptors/utils.ts +0 -75
  201. package/test/unit/spec/webinar/utils.ts +0 -39
@@ -1,11 +1,6 @@
1
- import {cloneDeep, isEmpty, zip} from 'lodash';
2
- import HashTree, {LeafDataItem} from './hashTree';
3
- import LoggerProxy from '../common/logs/logger-proxy';
4
- import {Enum, HTTP_VERBS} from '../constants';
5
- import {DataSetNames, EMPTY_HASH} from './constants';
6
- import {ObjectType, HtMeta, HashTreeObject} from './types';
1
+ import {Enum} from '../constants';
2
+ import {ObjectType, HtMeta} from './types';
7
3
  import {LocusDTO} from '../locus-info/types';
8
- import {deleteNestedObjectsWithHtMeta, isMetadata} from './utils';
9
4
 
10
5
  export interface DataSet {
11
6
  url: string;
@@ -20,6 +15,11 @@ export interface DataSet {
20
15
  };
21
16
  }
22
17
 
18
+ export interface HashTreeObject {
19
+ htMeta: HtMeta;
20
+ data: Record<string, any>;
21
+ }
22
+
23
23
  export interface RootHashMessage {
24
24
  dataSets: Array<DataSet>;
25
25
  }
@@ -29,24 +29,11 @@ export interface HashTreeMessage {
29
29
  locusStateElements?: Array<HashTreeObject>;
30
30
  locusSessionId?: string;
31
31
  locusUrl: string;
32
- heartbeatIntervalMs?: number;
33
- }
34
-
35
- export interface VisibleDataSetInfo {
36
- name: string;
37
- url: string;
38
- dataChannelUrl?: string;
39
- }
40
-
41
- export interface Metadata {
42
- htMeta: HtMeta;
43
- visibleDataSets: VisibleDataSetInfo[];
44
32
  }
45
33
 
46
34
  interface InternalDataSet extends DataSet {
47
- hashTree?: HashTree; // set only for visible data sets
35
+ // hashTree?: HashTree; // set only for visible data sets
48
36
  timer?: ReturnType<typeof setTimeout>;
49
- heartbeatWatchdogTimer?: ReturnType<typeof setTimeout>;
50
37
  }
51
38
 
52
39
  type WebexRequestMethod = (options: Record<string, any>) => Promise<any>;
@@ -62,29 +49,20 @@ export type LocusInfoUpdateCallback = (
62
49
  data?: {updatedObjects: HashTreeObject[]}
63
50
  ) => void;
64
51
 
65
- interface LeafInfo {
66
- type: ObjectType;
67
- id: number;
68
- version: number;
69
- data?: any;
70
- }
71
-
72
52
  /**
73
53
  * This error is thrown if we receive information that the meeting has ended while we're processing some hash messages.
74
54
  * It's handled internally by HashTreeParser and results in MEETING_ENDED being sent up.
75
55
  */
76
- export class MeetingEndedError extends Error {}
56
+ class MeetingEndedError extends Error {}
77
57
 
78
- /* Currently Locus always sends Metadata objects only in the "self" dataset.
79
- * If this ever changes, update all the code that relies on this constant.
58
+ /**
59
+ * Checks if the given hash tree object is of type "self"
60
+ * @param {HashTreeObject} object object to check
61
+ * @returns {boolean} True if the object is of type "self", false otherwise
80
62
  */
81
- const MetadataDataSetName = DataSetNames.SELF;
82
-
83
- const PossibleSentinelMessageDataSetNames = [
84
- DataSetNames.MAIN,
85
- DataSetNames.SELF,
86
- DataSetNames.UNJOINED,
87
- ];
63
+ export function isSelf(object: HashTreeObject) {
64
+ return object.htMeta.elementId.type.toLowerCase() === ObjectType.self;
65
+ }
88
66
 
89
67
  /**
90
68
  * Parses hash tree eventing locus data
@@ -94,11 +72,8 @@ class HashTreeParser {
94
72
  visibleDataSetsUrl: string; // url from which we can get info about all data sets
95
73
  webexRequest: WebexRequestMethod;
96
74
  locusInfoUpdateCallback: LocusInfoUpdateCallback;
97
- visibleDataSets: VisibleDataSetInfo[];
75
+ visibleDataSets: string[];
98
76
  debugId: string;
99
- heartbeatIntervalMs?: number;
100
- private excludedDataSets: string[];
101
- state: 'active' | 'stopped';
102
77
 
103
78
  /**
104
79
  * Constructor for HashTreeParser
@@ -110,220 +85,16 @@ class HashTreeParser {
110
85
  dataSets: Array<DataSet>;
111
86
  locus: any;
112
87
  };
113
- metadata: Metadata | null;
114
88
  webexRequest: WebexRequestMethod;
115
89
  locusInfoUpdateCallback: LocusInfoUpdateCallback;
116
90
  debugId: string;
117
- excludedDataSets?: string[];
118
91
  }) {
119
- const {dataSets, locus} = options.initialLocus; // extract dataSets from initialLocus
92
+ const {locus} = options.initialLocus;
120
93
 
121
94
  this.debugId = options.debugId;
122
95
  this.webexRequest = options.webexRequest;
123
96
  this.locusInfoUpdateCallback = options.locusInfoUpdateCallback;
124
- this.excludedDataSets = options.excludedDataSets || [];
125
- this.visibleDataSetsUrl = locus?.links?.resources?.visibleDataSets?.url;
126
- this.setVisibleDataSets(options.metadata?.visibleDataSets || [], dataSets);
127
-
128
- this.state = 'active';
129
-
130
- if (options.metadata?.visibleDataSets?.length === 0) {
131
- LoggerProxy.logger.warn(
132
- `HashTreeParser#constructor --> ${this.debugId} No visibleDataSets found in Metadata`
133
- );
134
- }
135
- // object mapping dataset names to arrays of leaf data
136
- const leafData = this.analyzeLocusHtMeta(locus);
137
-
138
- if (options.metadata) {
139
- // add also the metadata that's outside of locus object itself
140
- this.analyzeMetadata(leafData, options.metadata);
141
- }
142
-
143
- LoggerProxy.logger.info(
144
- `HashTreeParser#constructor --> ${
145
- this.debugId
146
- } creating HashTreeParser for datasets: ${JSON.stringify(
147
- dataSets.map((ds) => ds.name)
148
- )} with visible datasets: ${JSON.stringify(this.visibleDataSets.map((vds) => vds.name))}`
149
- );
150
-
151
- for (const dataSet of dataSets) {
152
- const {name, leafCount} = dataSet;
153
-
154
- this.dataSets[name] = {
155
- ...dataSet,
156
- hashTree: this.isVisibleDataSet(name)
157
- ? new HashTree(leafData[name] || [], leafCount)
158
- : undefined,
159
- };
160
- }
161
- }
162
-
163
- /**
164
- * Sets the visible data sets list for the HashTreeParser. This method should be called only at the start, to initialize
165
- * the visible data sets, before any message processsing, so for example from the constructor or when resuming the parser.
166
- *
167
- * @param {Array<VisibleDataSetInfo>} visibleDataSets - The visible data sets to set
168
- * @param {Array<DataSet>} dataSets - The "dataSets" list from Locus (yes, Locus sends visibleDataSets and dataSets as separate lists and they can differ)
169
- * @returns {void}
170
- */
171
- private setVisibleDataSets(visibleDataSets: VisibleDataSetInfo[], dataSets: Array<DataSet>) {
172
- this.visibleDataSets = cloneDeep(visibleDataSets).filter(
173
- (vds) =>
174
- // exclude data sets we will never care about
175
- !this.isExcludedDataSet(vds.name) &&
176
- // and make sure that visibleDataSets list is consistent with dataSets list
177
- dataSets.some((ds) => ds.name === vds.name)
178
- );
179
- }
180
-
181
- /**
182
- * Checks if the given data set name is in the list of visible data sets
183
- * @param {string} dataSetName data set name to check
184
- * @returns {Boolean} True if the data set is visible, false otherwise
185
- */
186
- private isVisibleDataSet(dataSetName: string): boolean {
187
- return this.visibleDataSets.some((vds) => vds.name === dataSetName);
188
- }
189
-
190
- /**
191
- * Checks if the given data set name is in the excluded list
192
- * @param {string} dataSetName data set name to check
193
- * @returns {boolean} True if the data set is excluded, false otherwise
194
- */
195
- private isExcludedDataSet(dataSetName: string): boolean {
196
- return this.excludedDataSets.some((name) => name === dataSetName);
197
- }
198
-
199
- /**
200
- * Adds a data set to the visible data sets list, unless it is in the excluded list.
201
- * @param {VisibleDataSetInfo} dataSetInfo data set info to add
202
- * @returns {boolean} True if the data set was added, false if it was excluded
203
- */
204
- private addToVisibleDataSetsList(dataSetInfo: VisibleDataSetInfo): boolean {
205
- if (this.isExcludedDataSet(dataSetInfo.name)) {
206
- LoggerProxy.logger.info(
207
- `HashTreeParser#addToVisibleDataSetsList --> ${this.debugId} Data set "${dataSetInfo.name}" is in the excluded list, ignoring`
208
- );
209
-
210
- return false;
211
- }
212
-
213
- this.visibleDataSets.push(dataSetInfo);
214
-
215
- return true;
216
- }
217
-
218
- /**
219
- * Initializes a new visible data set by creating a hash tree for it, adding it to all the internal structures,
220
- * and sending an initial sync request to Locus with empty leaf data - that will trigger Locus to gives us all the data
221
- * from that dataset (in the response or via messages).
222
- *
223
- * @param {VisibleDataSetInfo} visibleDataSetInfo Information about the new visible data set
224
- * @param {DataSet} dataSetInfo The new data set to be added
225
- * @returns {Promise}
226
- */
227
- private initializeNewVisibleDataSet(
228
- visibleDataSetInfo: VisibleDataSetInfo,
229
- dataSetInfo: DataSet
230
- ): Promise<{updateType: LocusInfoUpdateType; updatedObjects?: HashTreeObject[]}> {
231
- if (this.isVisibleDataSet(dataSetInfo.name)) {
232
- LoggerProxy.logger.info(
233
- `HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Data set "${dataSetInfo.name}" already exists, skipping init`
234
- );
235
-
236
- return Promise.resolve({updateType: LocusInfoUpdateType.OBJECTS_UPDATED, updatedObjects: []});
237
- }
238
-
239
- LoggerProxy.logger.info(
240
- `HashTreeParser#initializeNewVisibleDataSet --> ${this.debugId} Adding visible data set "${dataSetInfo.name}"`
241
- );
242
-
243
- if (!this.addToVisibleDataSetsList(visibleDataSetInfo)) {
244
- return Promise.resolve({updateType: LocusInfoUpdateType.OBJECTS_UPDATED, updatedObjects: []});
245
- }
246
-
247
- const hashTree = new HashTree([], dataSetInfo.leafCount);
248
-
249
- this.dataSets[dataSetInfo.name] = {
250
- ...dataSetInfo,
251
- hashTree,
252
- };
253
-
254
- return this.sendInitializationSyncRequestToLocus(dataSetInfo.name, 'new visible data set');
255
- }
256
-
257
- /**
258
- * Sends a special sync request to Locus with all leaves empty - this is a way to get all the data for a given dataset.
259
- *
260
- * @param {string} datasetName - name of the dataset for which to send the request
261
- * @param {string} debugText - text to include in logs
262
- * @returns {Promise}
263
- */
264
- private sendInitializationSyncRequestToLocus(
265
- datasetName: string,
266
- debugText: string
267
- ): Promise<{updateType: LocusInfoUpdateType; updatedObjects?: HashTreeObject[]}> {
268
- const dataset = this.dataSets[datasetName];
269
-
270
- if (!dataset) {
271
- LoggerProxy.logger.warn(
272
- `HashTreeParser#sendInitializationSyncRequestToLocus --> ${this.debugId} No data set found for ${datasetName}, cannot send the request for leaf data`
273
- );
274
-
275
- return Promise.resolve(null);
276
- }
277
-
278
- const emptyLeavesData = new Array(dataset.leafCount).fill([]);
279
-
280
- LoggerProxy.logger.info(
281
- `HashTreeParser#sendInitializationSyncRequestToLocus --> ${this.debugId} Sending initial sync request to Locus for data set "${datasetName}" with empty leaf data`
282
- );
283
-
284
- return this.sendSyncRequestToLocus(this.dataSets[datasetName], emptyLeavesData).then(
285
- (syncResponse) => {
286
- if (syncResponse) {
287
- return {
288
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
289
- updatedObjects: this.parseMessage(
290
- syncResponse,
291
- `via empty leaves /sync API call for ${debugText}`
292
- ),
293
- };
294
- }
295
-
296
- return {updateType: LocusInfoUpdateType.OBJECTS_UPDATED, updatedObjects: []};
297
- }
298
- );
299
- }
300
-
301
- /**
302
- * Queries Locus for all up-to-date information about all visible data sets
303
- *
304
- * @returns {Promise}
305
- */
306
- private getAllVisibleDataSetsFromLocus() {
307
- if (!this.visibleDataSetsUrl) {
308
- LoggerProxy.logger.warn(
309
- `HashTreeParser#getAllVisibleDataSetsFromLocus --> ${this.debugId} No visibleDataSetsUrl, cannot get data sets information`
310
- );
311
-
312
- return Promise.resolve([]);
313
- }
314
-
315
- return this.webexRequest({
316
- method: HTTP_VERBS.GET,
317
- uri: this.visibleDataSetsUrl,
318
- })
319
- .then((response) => {
320
- return response.body.dataSets as Array<DataSet>;
321
- })
322
- .catch((error) => {
323
- this.checkForSentinelHttpResponse(error);
324
-
325
- throw error;
326
- });
97
+ this.visibleDataSets = locus?.self?.visibleDataSets || [];
327
98
  }
328
99
 
329
100
  /**
@@ -333,14 +104,7 @@ class HashTreeParser {
333
104
  * @returns {Promise}
334
105
  */
335
106
  async initializeFromMessage(message: HashTreeMessage) {
336
- this.visibleDataSetsUrl = message.visibleDataSetsUrl;
337
-
338
- LoggerProxy.logger.info(
339
- `HashTreeParser#initializeFromMessage --> ${this.debugId} visibleDataSetsUrl=${this.visibleDataSetsUrl}`
340
- );
341
- const visibleDataSets = await this.getAllVisibleDataSetsFromLocus();
342
-
343
- await this.initializeDataSets(visibleDataSets, 'initialization from message');
107
+ // todo
344
108
  }
345
109
 
346
110
  /**
@@ -353,327 +117,7 @@ class HashTreeParser {
353
117
  * @returns {Promise}
354
118
  */
355
119
  async initializeFromGetLociResponse(locus: LocusDTO) {
356
- if (!locus?.links?.resources?.visibleDataSets?.url) {
357
- LoggerProxy.logger.warn(
358
- `HashTreeParser#initializeFromGetLociResponse --> ${this.debugId} missing visibleDataSets url in GET Loci response, cannot initialize hash trees`
359
- );
360
-
361
- return;
362
- }
363
- this.visibleDataSetsUrl = locus.links.resources.visibleDataSets.url;
364
-
365
- LoggerProxy.logger.info(
366
- `HashTreeParser#initializeFromGetLociResponse --> ${this.debugId} visibleDataSets url: ${this.visibleDataSetsUrl}`
367
- );
368
-
369
- const visibleDataSets = await this.getAllVisibleDataSetsFromLocus();
370
-
371
- await this.initializeDataSets(visibleDataSets, 'initialization from GET /loci response');
372
- }
373
-
374
- /**
375
- * Initializes data sets by doing an initialization sync on each visible data set that doesn't have a hash tree yet.
376
- *
377
- * @param {DataSet[]} visibleDataSets Array of visible DataSet objects to initialize
378
- * @param {string} debugText Text to include in logs for debugging purposes
379
- * @returns {Promise}
380
- */
381
- private async initializeDataSets(visibleDataSets: Array<DataSet>, debugText: string) {
382
- if (this.state === 'stopped') {
383
- return;
384
- }
385
- const updatedObjects: HashTreeObject[] = [];
386
-
387
- for (const dataSet of visibleDataSets) {
388
- const {name, leafCount, url} = dataSet;
389
-
390
- if (!this.dataSets[name]) {
391
- LoggerProxy.logger.info(
392
- `HashTreeParser#initializeDataSets --> ${this.debugId} initializing dataset "${name}" (${debugText})`
393
- );
394
-
395
- this.dataSets[name] = {
396
- ...dataSet,
397
- };
398
- } else {
399
- LoggerProxy.logger.info(
400
- `HashTreeParser#initializeDataSets --> ${this.debugId} dataset "${name}" already exists (${debugText})`
401
- );
402
- }
403
-
404
- if (!this.isVisibleDataSet(name)) {
405
- if (
406
- !this.addToVisibleDataSetsList({
407
- name,
408
- url,
409
- })
410
- ) {
411
- // dataset is excluded, skip it
412
- // eslint-disable-next-line no-continue
413
- continue;
414
- }
415
- }
416
-
417
- if (!this.dataSets[name].hashTree) {
418
- LoggerProxy.logger.info(
419
- `HashTreeParser#initializeDataSets --> ${this.debugId} creating hash tree for visible dataset "${name}" (${debugText})`
420
- );
421
- this.dataSets[name].hashTree = new HashTree([], leafCount);
422
-
423
- // eslint-disable-next-line no-await-in-loop
424
- const data = await this.sendInitializationSyncRequestToLocus(name, debugText);
425
-
426
- if (data.updateType === LocusInfoUpdateType.OBJECTS_UPDATED) {
427
- updatedObjects.push(...(data.updatedObjects || []));
428
- }
429
- }
430
- }
431
-
432
- this.callLocusInfoUpdateCallback({
433
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
434
- updatedObjects,
435
- });
436
- }
437
-
438
- /**
439
- * Each dataset exists at a different place in the dto
440
- * iterate recursively over the locus and if it has a htMeta key,
441
- * create an object with the type, id and version and add it to the appropriate leafData array
442
- *
443
- * @param {any} locus - The current part of the locus being processed
444
- * @param {Object} [options]
445
- * @param {boolean} [options.copyData=false] - Whether to copy the data for each leaf into returned result
446
- * @returns {any} - An object mapping dataset names to arrays of leaf data
447
- */
448
- private analyzeLocusHtMeta(locus: any, options?: {copyData?: boolean}) {
449
- const {copyData = false} = options || {};
450
- // object mapping dataset names to arrays of leaf data
451
- const leafInfo: Record<string, Array<LeafInfo>> = {};
452
-
453
- const findAndStoreMetaData = (currentLocusPart: any, currentLocusPartName: string) => {
454
- if (typeof currentLocusPart !== 'object' || currentLocusPart === null) {
455
- return;
456
- }
457
-
458
- if (currentLocusPart.htMeta && currentLocusPart.htMeta.dataSetNames) {
459
- const {type, id, version} = currentLocusPart.htMeta.elementId;
460
- const {dataSetNames} = currentLocusPart.htMeta;
461
- const newLeafInfo: LeafInfo = {
462
- type,
463
- id,
464
- version,
465
- };
466
-
467
- if (copyData) {
468
- if ((type as string).toLowerCase() === ObjectType.control) {
469
- // control entries require special handling, because they are signalled by Locus
470
- // differently when coming in messages vs API responses
471
- newLeafInfo.data = {
472
- [currentLocusPartName]: cloneDeep(currentLocusPart),
473
- };
474
- } else {
475
- newLeafInfo.data = cloneDeep(currentLocusPart);
476
-
477
- // remove any nested other objects that have their own htMeta
478
- deleteNestedObjectsWithHtMeta(newLeafInfo.data);
479
- }
480
- }
481
-
482
- for (const dataSetName of dataSetNames) {
483
- if (!leafInfo[dataSetName]) {
484
- leafInfo[dataSetName] = [];
485
- }
486
- leafInfo[dataSetName].push(newLeafInfo);
487
- }
488
- }
489
-
490
- if (Array.isArray(currentLocusPart)) {
491
- for (const [index, item] of currentLocusPart.entries()) {
492
- findAndStoreMetaData(item, index.toString());
493
- }
494
- } else {
495
- for (const key of Object.keys(currentLocusPart)) {
496
- if (Object.prototype.hasOwnProperty.call(currentLocusPart, key)) {
497
- findAndStoreMetaData(currentLocusPart[key], key);
498
- }
499
- }
500
- }
501
- };
502
-
503
- findAndStoreMetaData(locus, 'locus');
504
-
505
- return leafInfo;
506
- }
507
-
508
- /**
509
- * Analyzes the Metadata object that is sent outside of Locus object, and appends its data to passed in leafInfo
510
- * structure.
511
- *
512
- * @param {Record<string, LeafInfo[]>} leafInfo the structure to which the Metadata info will be appended
513
- * @param {Metadata} metadata Metadata object
514
- * @returns {void}
515
- */
516
- private analyzeMetadata(leafInfo: Record<string, LeafInfo[]>, metadata: Metadata) {
517
- const {htMeta} = metadata;
518
-
519
- if (
520
- htMeta?.dataSetNames?.length === 1 &&
521
- htMeta.dataSetNames[0].toLowerCase() === MetadataDataSetName
522
- ) {
523
- const {type, id, version} = metadata.htMeta.elementId;
524
-
525
- const dataSetName = htMeta.dataSetNames[0];
526
-
527
- if (!leafInfo[dataSetName]) {
528
- leafInfo[dataSetName] = [];
529
- }
530
-
531
- leafInfo[dataSetName].push({
532
- type,
533
- id,
534
- version,
535
- });
536
- } else {
537
- throw new Error(
538
- `${this.debugId} Metadata htMeta has unexpected dataSetNames: ${
539
- htMeta && htMeta.dataSetNames.join(',')
540
- }`
541
- );
542
- }
543
- }
544
-
545
- /**
546
- * Checks if the provided hash tree message indicates the end of the meeting and that there won't be any more updates.
547
- *
548
- * @param {HashTreeMessage} message - The hash tree message to check
549
- * @returns {boolean} - Returns true if the message indicates the end of the meeting, false otherwise
550
- */
551
- private isEndMessage(message: HashTreeMessage) {
552
- return message.dataSets.some((dataSet) => {
553
- if (
554
- dataSet.leafCount === 1 &&
555
- dataSet.root === EMPTY_HASH &&
556
- (!this.dataSets[dataSet.name] || this.dataSets[dataSet.name].version < dataSet.version) &&
557
- PossibleSentinelMessageDataSetNames.includes(dataSet.name.toLowerCase())
558
- ) {
559
- // this is a special way for Locus to indicate that this meeting has ended
560
- return true;
561
- }
562
-
563
- return false;
564
- });
565
- }
566
-
567
- /**
568
- * Handles the root hash heartbeat message
569
- *
570
- * @param {RootHashMessage} message - The root hash heartbeat message
571
- * @returns {void}
572
- */
573
- private handleRootHashHeartBeatMessage(message: RootHashMessage): void {
574
- const {dataSets} = message;
575
-
576
- LoggerProxy.logger.info(
577
- `HashTreeParser#handleRootHashMessage --> ${
578
- this.debugId
579
- } Received heartbeat root hash message with data sets: ${JSON.stringify(
580
- dataSets.map(({name, root, leafCount, version}) => ({
581
- name,
582
- root,
583
- leafCount,
584
- version,
585
- }))
586
- )}`
587
- );
588
-
589
- dataSets.forEach((dataSet) => {
590
- this.updateDataSetInfo(dataSet);
591
- this.runSyncAlgorithm(dataSet);
592
- });
593
- }
594
-
595
- /**
596
- * Asynchronously initializes new visible data sets
597
- *
598
- * @param {VisibleDataSetInfo[]} dataSetsRequiringInitialization list of datasets to initialize
599
- * @returns {void}
600
- */
601
- private queueInitForNewVisibleDataSets(dataSetsRequiringInitialization: VisibleDataSetInfo[]) {
602
- LoggerProxy.logger.info(
603
- `HashTreeParser#queueInitForNewVisibleDataSets --> ${
604
- this.debugId
605
- } queuing initialization of new visible datasets: ${dataSetsRequiringInitialization
606
- .map((ds) => ds.name)
607
- .join(', ')}`
608
- );
609
- queueMicrotask(() => {
610
- this.initializeNewVisibleDataSets(dataSetsRequiringInitialization).catch((error) => {
611
- if (error instanceof MeetingEndedError) {
612
- this.callLocusInfoUpdateCallback({
613
- updateType: LocusInfoUpdateType.MEETING_ENDED,
614
- });
615
- } else {
616
- LoggerProxy.logger.warn(
617
- `HashTreeParser#queueInitForNewVisibleDataSets --> ${
618
- this.debugId
619
- } error while initializing new visible datasets: ${dataSetsRequiringInitialization
620
- .map((ds) => ds.name)
621
- .join(', ')}: `,
622
- error
623
- );
624
- }
625
- });
626
- });
627
- }
628
-
629
- /**
630
- * Handles updates to Metadata object that we receive from Locus via other means than messages. Right now
631
- * that means only in the API response alongside locus object.
632
- *
633
- * @param {Metadata} metadata received in Locus update other than a message (for example in an API response)
634
- * @param {HashTreeObject[]} updatedObjects a list of updated hash tree objects to which any updates resulting from new Metadata will be added
635
- * @returns {void}
636
- */
637
- handleMetadataUpdate(metadata: Metadata, updatedObjects: HashTreeObject[]): void {
638
- let dataSetsRequiringInitialization: VisibleDataSetInfo[] = [];
639
-
640
- // current assumption based on Locus docs is that Metadata object lives always in "self" data set
641
- const hashTree = this.dataSets[MetadataDataSetName]?.hashTree;
642
-
643
- if (!hashTree) {
644
- LoggerProxy.logger.warn(
645
- `HashTreeParser#handleLocusUpdate --> ${this.debugId} received Metadata object but no hash tree for "${MetadataDataSetName}" data set exists`
646
- );
647
- } else {
648
- const metadataUpdated = hashTree.putItem(metadata.htMeta.elementId);
649
-
650
- if (metadataUpdated) {
651
- // metadata in Locus API response is in a slightly different format than the objects in messages, so need to adapt it
652
- const metadataObject: HashTreeObject = {
653
- htMeta: metadata.htMeta,
654
- data: metadata,
655
- };
656
-
657
- updatedObjects.push(metadataObject);
658
-
659
- const {changeDetected, removedDataSets, addedDataSets} = this.checkForVisibleDataSetChanges(
660
- [metadataObject]
661
- );
662
-
663
- if (changeDetected) {
664
- dataSetsRequiringInitialization = this.processVisibleDataSetChanges(
665
- removedDataSets,
666
- addedDataSets,
667
- updatedObjects
668
- );
669
- }
670
-
671
- if (dataSetsRequiringInitialization.length > 0) {
672
- // there are some data sets that we need to initialize asynchronously
673
- this.queueInitForNewVisibleDataSets(dataSetsRequiringInitialization);
674
- }
675
- }
676
- }
120
+ // todo
677
121
  }
678
122
 
679
123
  /**
@@ -683,427 +127,8 @@ class HashTreeParser {
683
127
  * @param {Object} update - The locus update containing data sets and locus information
684
128
  * @returns {void}
685
129
  */
686
- handleLocusUpdate(update: {dataSets?: Array<DataSet>; locus: any; metadata?: Metadata}): void {
687
- if (this.state === 'stopped') {
688
- return;
689
- }
690
-
691
- const {dataSets, locus, metadata} = update;
692
-
693
- if (!dataSets) {
694
- LoggerProxy.logger.info(
695
- `HashTreeParser#handleLocusUpdate --> ${this.debugId} received hash tree update without dataSets`
696
- );
697
- } else {
698
- for (const dataSet of dataSets) {
699
- this.updateDataSetInfo(dataSet);
700
- }
701
- }
702
- const updatedObjects: HashTreeObject[] = [];
703
-
704
- // first, analyze the locus object to extract the hash tree objects' htMeta and data from it
705
- const leafInfo = this.analyzeLocusHtMeta(locus, {copyData: true});
706
-
707
- // if we got metadata, process it (currently that means only potential visible data set list changes)
708
- if (metadata) {
709
- this.handleMetadataUpdate(metadata, updatedObjects);
710
- }
711
-
712
- // then process the data in hash trees, if it is a new version, then add it to updatedObjects
713
- Object.keys(leafInfo).forEach((dataSetName) => {
714
- if (this.dataSets[dataSetName]) {
715
- if (this.dataSets[dataSetName].hashTree) {
716
- const appliedChangesList = this.dataSets[dataSetName].hashTree.putItems(
717
- leafInfo[dataSetName].map((leaf) => ({
718
- id: leaf.id,
719
- type: leaf.type,
720
- version: leaf.version,
721
- }))
722
- );
723
-
724
- zip(appliedChangesList, leafInfo[dataSetName]).forEach(([changeApplied, leaf]) => {
725
- if (changeApplied) {
726
- updatedObjects.push({
727
- htMeta: {
728
- elementId: {
729
- type: leaf.type,
730
- id: leaf.id,
731
- version: leaf.version,
732
- },
733
- dataSetNames: [dataSetName],
734
- },
735
- data: leaf.data,
736
- });
737
- }
738
- });
739
- } else {
740
- // no hash tree means that the data set is not visible
741
- LoggerProxy.logger.warn(
742
- `HashTreeParser#handleLocusUpdate --> ${this.debugId} received leaf data for data set "${dataSetName}" that has no hash tree created, ignoring`
743
- );
744
- }
745
- } else {
746
- LoggerProxy.logger.info(
747
- `HashTreeParser#handleLocusUpdate --> ${this.debugId} received leaf data for unknown data set "${dataSetName}", ignoring`
748
- );
749
- }
750
- });
751
-
752
- if (updatedObjects.length === 0) {
753
- LoggerProxy.logger.info(
754
- `HashTreeParser#handleLocusUpdate --> ${this.debugId} No objects updated as a result of received API response`
755
- );
756
- } else {
757
- this.callLocusInfoUpdateCallback({
758
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
759
- updatedObjects,
760
- });
761
- }
762
- }
763
-
764
- /**
765
- * Updates the internal data set information based on the received data set from Locus.
766
- *
767
- * @param {DataSet} receivedDataSet - The latest data set information received from Locus to update the internal state.
768
- * @returns {void}
769
- */
770
- private updateDataSetInfo(receivedDataSet: DataSet) {
771
- if (!this.dataSets[receivedDataSet.name]) {
772
- this.dataSets[receivedDataSet.name] = {
773
- ...receivedDataSet,
774
- };
775
-
776
- LoggerProxy.logger.info(
777
- `HashTreeParser#updateDataSetInfo --> ${this.debugId} created entry for "${receivedDataSet.name}" dataset: version=${receivedDataSet.version}, root=${receivedDataSet.root}`
778
- );
779
-
780
- return;
781
- }
782
- // update our version of the dataSet
783
- if (this.dataSets[receivedDataSet.name].version < receivedDataSet.version) {
784
- this.dataSets[receivedDataSet.name].version = receivedDataSet.version;
785
- this.dataSets[receivedDataSet.name].root = receivedDataSet.root;
786
- this.dataSets[receivedDataSet.name].idleMs = receivedDataSet.idleMs;
787
- this.dataSets[receivedDataSet.name].backoff = {
788
- maxMs: receivedDataSet.backoff.maxMs,
789
- exponent: receivedDataSet.backoff.exponent,
790
- };
791
- LoggerProxy.logger.info(
792
- `HashTreeParser#updateDataSetInfo --> ${this.debugId} updated "${receivedDataSet.name}" dataset to version=${receivedDataSet.version}, root=${receivedDataSet.root}`
793
- );
794
- }
795
- }
796
-
797
- /**
798
- * Checks for changes in the visible data sets based on the updated objects.
799
- * @param {HashTreeObject[]} updatedObjects - The list of updated hash tree objects.
800
- * @returns {Object} An object containing the removed and added visible data sets.
801
- */
802
- private checkForVisibleDataSetChanges(updatedObjects: HashTreeObject[]) {
803
- let removedDataSets: VisibleDataSetInfo[] = [];
804
- let addedDataSets: VisibleDataSetInfo[] = [];
805
-
806
- // visibleDataSets can only be changed by Metadata object updates
807
- updatedObjects.forEach((object) => {
808
- if (isMetadata(object) && object.data?.visibleDataSets) {
809
- const newVisibleDataSets = object.data.visibleDataSets.filter(
810
- (vds) => !this.isExcludedDataSet(vds.name)
811
- );
812
-
813
- removedDataSets = this.visibleDataSets.filter(
814
- (ds) => !newVisibleDataSets.some((nvs) => nvs.name === ds.name)
815
- );
816
- addedDataSets = newVisibleDataSets.filter((nvs) =>
817
- this.visibleDataSets.every((ds) => ds.name !== nvs.name)
818
- );
819
-
820
- if (removedDataSets.length > 0 || addedDataSets.length > 0) {
821
- LoggerProxy.logger.info(
822
- `HashTreeParser#checkForVisibleDataSetChanges --> ${
823
- this.debugId
824
- } visible data sets change: removed: ${removedDataSets
825
- .map((ds) => ds.name)
826
- .join(', ')}, added: ${addedDataSets.map((ds) => ds.name).join(', ')}`
827
- );
828
- }
829
- }
830
- });
831
-
832
- return {
833
- changeDetected: removedDataSets.length > 0 || addedDataSets.length > 0,
834
- removedDataSets,
835
- addedDataSets,
836
- };
837
- }
838
-
839
- /**
840
- * Deletes the hash tree for the specified data set.
841
- *
842
- * @param {string} dataSetName name of the data set to delete
843
- * @returns {void}
844
- */
845
- private deleteHashTree(dataSetName: string) {
846
- this.dataSets[dataSetName].hashTree = undefined;
847
-
848
- // we also need to stop the timers as there is no hash tree anymore to sync
849
- if (this.dataSets[dataSetName].timer) {
850
- clearTimeout(this.dataSets[dataSetName].timer);
851
- this.dataSets[dataSetName].timer = undefined;
852
- }
853
- if (this.dataSets[dataSetName].heartbeatWatchdogTimer) {
854
- clearTimeout(this.dataSets[dataSetName].heartbeatWatchdogTimer);
855
- this.dataSets[dataSetName].heartbeatWatchdogTimer = undefined;
856
- }
857
- }
858
-
859
- /**
860
- * Adds entries to the passed in updateObjects array
861
- * for the changes that result from removing visible data sets and creates hash
862
- * trees for the new visible data sets, but without populating the hash trees.
863
- *
864
- * This function is synchronous. If we are missing information about some new
865
- * visible data sets and they require async initialization, the names of these data sets
866
- * are returned in an array.
867
- *
868
- * @param {VisibleDataSetInfo[]} removedDataSets - The list of removed data sets.
869
- * @param {VisibleDataSetInfo[]} addedDataSets - The list of added data sets.
870
- * @param {HashTreeObject[]} updatedObjects - The list of updated hash tree objects to which changes will be added.
871
- * @returns {VisibleDataSetInfo[]} list of data sets that couldn't be initialized synchronously
872
- */
873
- private processVisibleDataSetChanges(
874
- removedDataSets: VisibleDataSetInfo[],
875
- addedDataSets: VisibleDataSetInfo[],
876
- updatedObjects: HashTreeObject[]
877
- ): VisibleDataSetInfo[] {
878
- const dataSetsRequiringInitialization: VisibleDataSetInfo[] = [];
879
-
880
- // if a visible data set was removed, we need to tell our client that all objects from it are removed
881
- const removedObjects: HashTreeObject[] = [];
882
-
883
- removedDataSets.forEach((ds) => {
884
- if (this.dataSets[ds.name]?.hashTree) {
885
- for (let i = 0; i < this.dataSets[ds.name].hashTree.numLeaves; i += 1) {
886
- removedObjects.push(
887
- ...this.dataSets[ds.name].hashTree.getLeafData(i).map((elementId) => ({
888
- htMeta: {
889
- elementId,
890
- dataSetNames: [ds.name],
891
- },
892
- data: null,
893
- }))
894
- );
895
- }
896
-
897
- this.deleteHashTree(ds.name);
898
- }
899
- });
900
- this.visibleDataSets = this.visibleDataSets.filter(
901
- (vds) => !removedDataSets.some((rds) => rds.name === vds.name)
902
- );
903
- updatedObjects.push(...removedObjects);
904
-
905
- // now setup the new visible data sets
906
- for (const ds of addedDataSets) {
907
- const dataSetInfo = this.dataSets[ds.name];
908
-
909
- if (dataSetInfo) {
910
- if (this.isVisibleDataSet(dataSetInfo.name)) {
911
- LoggerProxy.logger.info(
912
- `HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} Data set "${ds.name}" is already visible, skipping`
913
- );
914
-
915
- // eslint-disable-next-line no-continue
916
- continue;
917
- }
918
-
919
- LoggerProxy.logger.info(
920
- `HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} Adding visible data set "${ds.name}"`
921
- );
922
-
923
- if (!this.addToVisibleDataSetsList(ds)) {
924
- // eslint-disable-next-line no-continue
925
- continue;
926
- }
927
-
928
- const hashTree = new HashTree([], dataSetInfo.leafCount);
929
-
930
- this.dataSets[dataSetInfo.name] = {
931
- ...dataSetInfo,
932
- hashTree,
933
- };
934
-
935
- // this call is needed here for the edge case where we receive a message with new visible data sets
936
- // and there are no objects belonging to these data sets in the message but we already have the info about them in this.dataSets
937
- this.runSyncAlgorithm(this.dataSets[dataSetInfo.name]);
938
- } else {
939
- LoggerProxy.logger.info(
940
- `HashTreeParser#processVisibleDataSetChanges --> ${this.debugId} visible data set "${ds.name}" added but no info about it in our dataSets structures`
941
- );
942
- // todo: add a metric here
943
- dataSetsRequiringInitialization.push(ds);
944
- }
945
- }
946
-
947
- return dataSetsRequiringInitialization;
948
- }
949
-
950
- /**
951
- * Adds entries to the passed in updateObjects array
952
- * for the changes that result from adding and removing visible data sets.
953
- *
954
- * @param {VisibleDataSetInfo[]} addedDataSets - The list of added data sets.
955
- * @returns {Promise<void>}
956
- */
957
- private async initializeNewVisibleDataSets(addedDataSets: VisibleDataSetInfo[]): Promise<void> {
958
- if (this.state === 'stopped') {
959
- return;
960
- }
961
- const allDataSets = await this.getAllVisibleDataSetsFromLocus();
962
-
963
- for (const ds of addedDataSets) {
964
- const dataSetInfo = allDataSets.find((d) => d.name === ds.name);
965
-
966
- LoggerProxy.logger.info(
967
- `HashTreeParser#initializeNewVisibleDataSets --> ${this.debugId} initializing data set "${ds.name}"`
968
- );
969
-
970
- if (!dataSetInfo) {
971
- LoggerProxy.logger.warn(
972
- `HashTreeParser#initializeNewVisibleDataSets --> ${this.debugId} missing info about data set "${ds.name}" in Locus response from visibleDataSetsUrl`
973
- );
974
- } else {
975
- // we're awaiting in a loop, because in practice there will be only one new data set at a time,
976
- // so no point in trying to parallelize this
977
- // eslint-disable-next-line no-await-in-loop
978
- const updates = await this.initializeNewVisibleDataSet(ds, dataSetInfo);
979
-
980
- this.callLocusInfoUpdateCallback(updates);
981
- }
982
- }
983
- }
984
-
985
- /**
986
- * Parses incoming hash tree messages, updates the hash trees and returns information about the changes
987
- *
988
- * @param {HashTreeMessage} message - The hash tree message containing data sets and objects to be processed
989
- * @param {string} [debugText] - Optional debug text to include in logs
990
- * @returns {HashTreeObject[]} list of hash tree objects that were updated as a result of processing the message
991
- */
992
- private parseMessage(message: HashTreeMessage, debugText?: string): HashTreeObject[] {
993
- if (this.state === 'stopped') {
994
- return [];
995
- }
996
-
997
- const {dataSets, visibleDataSetsUrl} = message;
998
-
999
- LoggerProxy.logger.info(
1000
- `HashTreeParser#parseMessage --> ${this.debugId} received message ${debugText || ''}:`,
1001
- message
1002
- );
1003
- if (message.locusStateElements?.length === 0) {
1004
- LoggerProxy.logger.warn(
1005
- `HashTreeParser#parseMessage --> ${this.debugId} got empty locusStateElements!!!`
1006
- );
1007
- // todo: send a metric
1008
- }
1009
-
1010
- // first, update our metadata about the datasets with info from the message
1011
- this.visibleDataSetsUrl = visibleDataSetsUrl;
1012
- dataSets.forEach((dataSet) => this.updateDataSetInfo(dataSet));
1013
-
1014
- const updatedObjects: HashTreeObject[] = [];
1015
-
1016
- // when we detect new visible datasets, it may be that the metadata about them is not
1017
- // available in the message, they will require separate async initialization
1018
- let dataSetsRequiringInitialization = [];
1019
-
1020
- // first find out if there are any visible data set changes - they're signalled in Metadata object updates
1021
- const metadataUpdates = (message.locusStateElements || []).filter((object) =>
1022
- isMetadata(object)
1023
- );
1024
-
1025
- if (metadataUpdates.length > 0) {
1026
- const updatedMetadataObjects = [];
1027
-
1028
- metadataUpdates.forEach((object) => {
1029
- // todo: once Locus supports it, we will use the "view" field here instead of dataSetNames
1030
- for (const dataSetName of object.htMeta.dataSetNames) {
1031
- const hashTree = this.dataSets[dataSetName]?.hashTree;
1032
-
1033
- if (hashTree && object.data) {
1034
- if (hashTree.putItem(object.htMeta.elementId)) {
1035
- updatedMetadataObjects.push(object);
1036
- }
1037
- }
1038
- }
1039
- });
1040
-
1041
- updatedObjects.push(...updatedMetadataObjects);
1042
-
1043
- const {changeDetected, removedDataSets, addedDataSets} =
1044
- this.checkForVisibleDataSetChanges(updatedMetadataObjects);
1045
-
1046
- if (changeDetected) {
1047
- dataSetsRequiringInitialization = this.processVisibleDataSetChanges(
1048
- removedDataSets,
1049
- addedDataSets,
1050
- updatedObjects
1051
- );
1052
- }
1053
- }
1054
-
1055
- if (message.locusStateElements?.length > 0) {
1056
- // by this point we now have this.dataSets setup for data sets from this message
1057
- // and hash trees created for the new visible data sets,
1058
- // so we can now process all the updates from the message
1059
- dataSets.forEach((dataSet) => {
1060
- if (this.dataSets[dataSet.name]) {
1061
- const {hashTree} = this.dataSets[dataSet.name];
1062
-
1063
- if (hashTree) {
1064
- const locusStateElementsForThisSet = message.locusStateElements.filter((object) =>
1065
- object.htMeta.dataSetNames.includes(dataSet.name)
1066
- );
1067
-
1068
- const appliedChangesList = hashTree.updateItems(
1069
- locusStateElementsForThisSet.map((object) =>
1070
- object.data
1071
- ? {operation: 'update', item: object.htMeta.elementId}
1072
- : {operation: 'remove', item: object.htMeta.elementId}
1073
- )
1074
- );
1075
-
1076
- zip(appliedChangesList, locusStateElementsForThisSet).forEach(
1077
- ([changeApplied, object]) => {
1078
- if (changeApplied) {
1079
- // add to updatedObjects so that our locus DTO will get updated with the new object
1080
- updatedObjects.push(object);
1081
- }
1082
- }
1083
- );
1084
- } else {
1085
- LoggerProxy.logger.info(
1086
- `Locus-info:index#parseMessage --> ${this.debugId} unexpected (not visible) dataSet ${dataSet.name} received in hash tree message`
1087
- );
1088
- }
1089
- }
1090
-
1091
- this.runSyncAlgorithm(dataSet);
1092
- });
1093
- }
1094
-
1095
- if (dataSetsRequiringInitialization.length > 0) {
1096
- // there are some data sets that we need to initialize asynchronously
1097
- this.queueInitForNewVisibleDataSets(dataSetsRequiringInitialization);
1098
- }
1099
-
1100
- if (updatedObjects.length === 0) {
1101
- LoggerProxy.logger.info(
1102
- `HashTreeParser#parseMessage --> ${this.debugId} No objects updated as a result of received message`
1103
- );
1104
- }
1105
-
1106
- return updatedObjects;
130
+ handleLocusUpdate(update: {dataSets?: Array<DataSet>; locus: any}): void {
131
+ // todo
1107
132
  }
1108
133
 
1109
134
  /**
@@ -1113,531 +138,8 @@ class HashTreeParser {
1113
138
  * @param {string} [debugText] - Optional debug text to include in logs
1114
139
  * @returns {void}
1115
140
  */
1116
- handleMessage(message: HashTreeMessage, debugText?: string) {
1117
- if (this.state === 'stopped') {
1118
- return;
1119
- }
1120
-
1121
- if (message.heartbeatIntervalMs) {
1122
- this.heartbeatIntervalMs = message.heartbeatIntervalMs;
1123
- }
1124
- if (this.isEndMessage(message)) {
1125
- LoggerProxy.logger.info(
1126
- `HashTreeParser#handleMessage --> ${this.debugId} received sentinel END MEETING message`
1127
- );
1128
- this.stopAllTimers();
1129
-
1130
- this.callLocusInfoUpdateCallback({updateType: LocusInfoUpdateType.MEETING_ENDED});
1131
- } else if (message.locusStateElements === undefined) {
1132
- this.handleRootHashHeartBeatMessage(message);
1133
- this.resetHeartbeatWatchdogs(message.dataSets);
1134
- } else {
1135
- const updatedObjects = this.parseMessage(message, debugText);
1136
-
1137
- this.resetHeartbeatWatchdogs(message.dataSets);
1138
- this.callLocusInfoUpdateCallback({
1139
- updateType: LocusInfoUpdateType.OBJECTS_UPDATED,
1140
- updatedObjects,
1141
- });
1142
- }
1143
- }
1144
-
1145
- /**
1146
- * Calls the updateInfo callback if there are any updates to report
1147
- *
1148
- * @param {Object} updates parsed from a Locus message
1149
- * @returns {void}
1150
- */
1151
- private callLocusInfoUpdateCallback(updates: {
1152
- updateType: LocusInfoUpdateType;
1153
- updatedObjects?: HashTreeObject[];
1154
- }) {
1155
- if (this.state === 'stopped') {
1156
- return;
1157
- }
1158
-
1159
- const {updateType, updatedObjects} = updates;
1160
-
1161
- if (updateType === LocusInfoUpdateType.OBJECTS_UPDATED && updatedObjects?.length > 0) {
1162
- // Filter out updates for objects that already have a higher version in their datasets,
1163
- // or removals for objects that still exist in any of their datasets
1164
- const filteredUpdates = updatedObjects.filter((object) => {
1165
- const {elementId} = object.htMeta;
1166
- const {type, id, version} = elementId;
1167
-
1168
- // Check all datasets
1169
- for (const dataSetName of Object.keys(this.dataSets)) {
1170
- const dataSet = this.dataSets[dataSetName];
1171
-
1172
- // only visible datasets have hash trees set
1173
- if (dataSet?.hashTree) {
1174
- const existingVersion = dataSet.hashTree.getItemVersion(id, type);
1175
- if (existingVersion !== undefined) {
1176
- if (object.data) {
1177
- // For updates: filter out if any dataset has a higher version
1178
- if (existingVersion > version) {
1179
- LoggerProxy.logger.info(
1180
- `HashTreeParser#callLocusInfoUpdateCallback --> ${this.debugId} Filtering out update for ${type}:${id} v${version} because dataset "${dataSetName}" has v${existingVersion}`
1181
- );
1182
-
1183
- return false;
1184
- }
1185
- } else if (existingVersion >= version) {
1186
- // For removals: filter out if the object still exists in any dataset
1187
- LoggerProxy.logger.info(
1188
- `HashTreeParser#callLocusInfoUpdateCallback --> ${this.debugId} Filtering out removal for ${type}:${id} v${version} because dataset "${dataSetName}" still has v${existingVersion}`
1189
- );
1190
-
1191
- return false;
1192
- }
1193
- }
1194
- }
1195
- }
1196
-
1197
- return true;
1198
- });
1199
-
1200
- if (filteredUpdates.length > 0) {
1201
- this.locusInfoUpdateCallback(updateType, {updatedObjects: filteredUpdates});
1202
- }
1203
- } else if (updateType !== LocusInfoUpdateType.OBJECTS_UPDATED) {
1204
- this.locusInfoUpdateCallback(updateType, {updatedObjects});
1205
- }
1206
- }
1207
-
1208
- /**
1209
- * Calculates a weighted backoff time that should be used for syncs
1210
- *
1211
- * @param {Object} backoff - The backoff configuration containing maxMs and exponent
1212
- * @returns {number} - A weighted backoff time based on the provided configuration, using algorithm supplied by Locus team
1213
- */
1214
- private getWeightedBackoffTime(backoff: {maxMs: number; exponent: number}): number {
1215
- const {maxMs, exponent} = backoff;
1216
-
1217
- const randomValue = Math.random();
1218
-
1219
- return Math.round(randomValue ** exponent * maxMs);
1220
- }
1221
-
1222
- /**
1223
- * Performs a sync for the given data set.
1224
- *
1225
- * @param {InternalDataSet} dataSet - The data set to sync
1226
- * @param {string} rootHash - Our current root hash for this data set
1227
- * @param {string} reason - The reason for the sync (used for logging)
1228
- * @returns {Promise<void>}
1229
- */
1230
- private async performSync(
1231
- dataSet: InternalDataSet,
1232
- rootHash: string,
1233
- reason: string
1234
- ): Promise<void> {
1235
- if (!dataSet.hashTree) {
1236
- return;
1237
- }
1238
-
1239
- try {
1240
- LoggerProxy.logger.info(
1241
- `HashTreeParser#performSync --> ${this.debugId} ${reason}, syncing data set "${dataSet.name}"`
1242
- );
1243
-
1244
- const mismatchedLeavesData: Record<number, LeafDataItem[]> = {};
1245
-
1246
- if (dataSet.leafCount !== 1) {
1247
- let receivedHashes;
1248
-
1249
- try {
1250
- // request hashes from sender
1251
- const {hashes, dataSet: latestDataSetInfo} = await this.getHashesFromLocus(
1252
- dataSet.name,
1253
- rootHash
1254
- );
1255
-
1256
- receivedHashes = hashes;
1257
-
1258
- dataSet.hashTree.resize(latestDataSetInfo.leafCount);
1259
- } catch (error) {
1260
- if (error.statusCode === 409) {
1261
- // this is a leaf count mismatch, we should do nothing, just wait for another heartbeat message from Locus
1262
- LoggerProxy.logger.info(
1263
- `HashTreeParser#getHashesFromLocus --> ${this.debugId} Got 409 when fetching hashes for data set "${dataSet.name}": ${error.message}`
1264
- );
1265
-
1266
- return;
1267
- }
1268
- throw error;
1269
- }
1270
-
1271
- // identify mismatched leaves
1272
- const mismatchedLeaveIndexes = dataSet.hashTree.diffHashes(receivedHashes);
1273
-
1274
- mismatchedLeaveIndexes.forEach((index) => {
1275
- mismatchedLeavesData[index] = dataSet.hashTree.getLeafData(index);
1276
- });
1277
- } else {
1278
- mismatchedLeavesData[0] = dataSet.hashTree.getLeafData(0);
1279
- }
1280
- // request sync for mismatched leaves
1281
- if (Object.keys(mismatchedLeavesData).length > 0) {
1282
- const syncResponse = await this.sendSyncRequestToLocus(dataSet, mismatchedLeavesData);
1283
-
1284
- // sync API may return nothing (in that case data will arrive via messages)
1285
- // or it may return a response in the same format as messages
1286
- if (syncResponse) {
1287
- this.handleMessage(syncResponse, 'via sync API');
1288
- }
1289
- }
1290
- } catch (error) {
1291
- if (error instanceof MeetingEndedError) {
1292
- this.callLocusInfoUpdateCallback({
1293
- updateType: LocusInfoUpdateType.MEETING_ENDED,
1294
- });
1295
- } else {
1296
- LoggerProxy.logger.warn(
1297
- `HashTreeParser#performSync --> ${this.debugId} error during sync for data set "${dataSet.name}":`,
1298
- error
1299
- );
1300
- }
1301
- }
1302
- }
1303
-
1304
- /**
1305
- * Runs the sync algorithm for the given data set.
1306
- *
1307
- * @param {DataSet} receivedDataSet - The data set to run the sync algorithm for.
1308
- * @returns {void}
1309
- */
1310
- private runSyncAlgorithm(receivedDataSet: DataSet) {
1311
- const dataSet = this.dataSets[receivedDataSet.name];
1312
-
1313
- if (!dataSet) {
1314
- LoggerProxy.logger.warn(
1315
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} No data set found for ${receivedDataSet.name}, skipping sync algorithm`
1316
- );
1317
-
1318
- return;
1319
- }
1320
-
1321
- if (!dataSet.hashTree) {
1322
- LoggerProxy.logger.info(
1323
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} Data set "${dataSet.name}" has no hash tree, skipping sync algorithm`
1324
- );
1325
-
1326
- return;
1327
- }
1328
-
1329
- dataSet.hashTree.resize(receivedDataSet.leafCount);
1330
-
1331
- // temporary log for the workshop // todo: remove
1332
- const ourCurrentRootHash = dataSet.hashTree.getRootHash();
1333
- LoggerProxy.logger.info(
1334
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} dataSet="${dataSet.name}" version=${dataSet.version} hashes before starting timer: ours=${ourCurrentRootHash} Locus=${dataSet.root}`
1335
- );
1336
-
1337
- const delay = dataSet.idleMs + this.getWeightedBackoffTime(dataSet.backoff);
1338
-
1339
- if (delay > 0) {
1340
- if (dataSet.timer) {
1341
- clearTimeout(dataSet.timer);
1342
- }
1343
-
1344
- LoggerProxy.logger.info(
1345
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} setting "${dataSet.name}" sync timer for ${delay}`
1346
- );
1347
-
1348
- dataSet.timer = setTimeout(async () => {
1349
- dataSet.timer = undefined;
1350
-
1351
- if (!dataSet.hashTree) {
1352
- LoggerProxy.logger.warn(
1353
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} Data set "${dataSet.name}" no longer has a hash tree, cannot run sync algorithm`
1354
- );
1355
-
1356
- return;
1357
- }
1358
-
1359
- const rootHash = dataSet.hashTree.getRootHash();
1360
-
1361
- if (dataSet.root !== rootHash) {
1362
- await this.performSync(
1363
- dataSet,
1364
- rootHash,
1365
- `Root hash mismatch: received=${dataSet.root}, ours=${rootHash}`
1366
- );
1367
- } else {
1368
- LoggerProxy.logger.info(
1369
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} "${dataSet.name}" root hash matching: ${rootHash}, version=${dataSet.version}`
1370
- );
1371
- }
1372
- }, delay);
1373
- } else {
1374
- LoggerProxy.logger.info(
1375
- `HashTreeParser#runSyncAlgorithm --> ${this.debugId} No delay for "${dataSet.name}" data set, skipping sync timer reset/setup`
1376
- );
1377
- }
1378
- }
1379
-
1380
- /**
1381
- * Resets the heartbeat watchdog timers for the specified data sets. Each data set has its own
1382
- * watchdog timer that monitors whether heartbeats are being received within the expected interval.
1383
- * If a heartbeat is not received for a specific data set within heartbeatIntervalMs plus
1384
- * a backoff-calculated time, the sync algorithm is initiated for that data set
1385
- *
1386
- * @param {Array<DataSet>} receivedDataSets - The data sets from the received message for which watchdog timers should be reset
1387
- * @returns {void}
1388
- */
1389
- private resetHeartbeatWatchdogs(receivedDataSets: Array<DataSet>): void {
1390
- if (!this.heartbeatIntervalMs) {
1391
- return;
1392
- }
1393
-
1394
- for (const receivedDataSet of receivedDataSets) {
1395
- const dataSet = this.dataSets[receivedDataSet.name];
1396
-
1397
- if (!dataSet?.hashTree) {
1398
- // eslint-disable-next-line no-continue
1399
- continue;
1400
- }
1401
-
1402
- if (dataSet.heartbeatWatchdogTimer) {
1403
- clearTimeout(dataSet.heartbeatWatchdogTimer);
1404
- dataSet.heartbeatWatchdogTimer = undefined;
1405
- }
1406
-
1407
- const backoffTime = this.getWeightedBackoffTime(dataSet.backoff);
1408
- const delay = this.heartbeatIntervalMs + backoffTime;
1409
-
1410
- dataSet.heartbeatWatchdogTimer = setTimeout(async () => {
1411
- dataSet.heartbeatWatchdogTimer = undefined;
1412
-
1413
- LoggerProxy.logger.warn(
1414
- `HashTreeParser#resetHeartbeatWatchdogs --> ${this.debugId} Heartbeat watchdog fired for data set "${dataSet.name}" - no heartbeat received within expected interval, initiating sync`
1415
- );
1416
-
1417
- await this.performSync(
1418
- dataSet,
1419
- dataSet.hashTree.getRootHash(),
1420
- `heartbeat watchdog expired`
1421
- );
1422
- }, delay);
1423
- }
1424
- }
1425
-
1426
- /**
1427
- * Stops all timers for the data sets to prevent any further sync attempts.
1428
- * @returns {void}
1429
- */
1430
- private stopAllTimers() {
1431
- Object.values(this.dataSets).forEach((dataSet) => {
1432
- if (dataSet.timer) {
1433
- clearTimeout(dataSet.timer);
1434
- dataSet.timer = undefined;
1435
- }
1436
- if (dataSet.heartbeatWatchdogTimer) {
1437
- clearTimeout(dataSet.heartbeatWatchdogTimer);
1438
- dataSet.heartbeatWatchdogTimer = undefined;
1439
- }
1440
- });
1441
- }
1442
-
1443
- /**
1444
- * Stops the HashTreeParser, preventing it from processing any further messages and clearing all timers.
1445
- * It also clears all the hash trees, so if the parser is resumed later, it will need to do a sync
1446
- * to be up-to-date.
1447
- * @returns {void}
1448
- */
1449
- public stop() {
1450
- LoggerProxy.logger.info(
1451
- `HashTreeParser#stop --> ${this.debugId} Stopping HashTreeParser, clearing timers and hash trees`
1452
- );
1453
- this.stopAllTimers();
1454
- Object.values(this.dataSets).forEach((dataSet) => {
1455
- dataSet.hashTree = undefined;
1456
- });
1457
- this.visibleDataSets = [];
1458
- this.state = 'stopped';
1459
- }
1460
-
1461
- /**
1462
- * Resumes the HashTreeParser that was previously stopped.
1463
- * @param {HashTreeMessage} message - The message to resume with, it must contain metadata with visible data sets info
1464
- * @returns {void}
1465
- */
1466
- public resume(message: HashTreeMessage) {
1467
- // check that message contains metadata with visible data sets - this is essential to be able to resume
1468
- const metadataObject = message.locusStateElements?.find((el) => isMetadata(el));
1469
-
1470
- if (!metadataObject?.data?.visibleDataSets) {
1471
- LoggerProxy.logger.warn(
1472
- `HashTreeParser#resume --> ${this.debugId} Cannot resume HashTreeParser because the message is missing metadata with visible data sets info`
1473
- );
1474
-
1475
- return;
1476
- }
1477
- this.setVisibleDataSets(
1478
- metadataObject.data.visibleDataSets as VisibleDataSetInfo[],
1479
- message.dataSets
1480
- );
1481
-
1482
- this.dataSets = {};
1483
-
1484
- for (const dataSet of message.dataSets) {
1485
- const {name, leafCount} = dataSet;
1486
-
1487
- this.dataSets[name] = {
1488
- ...dataSet,
1489
- hashTree: this.isVisibleDataSet(name) ? new HashTree([], leafCount) : undefined,
1490
- };
1491
- }
1492
- LoggerProxy.logger.info(
1493
- `HashTreeParser#resume --> ${
1494
- this.debugId
1495
- } Resuming HashTreeParser with data sets: ${Object.keys(this.dataSets).join(
1496
- ', '
1497
- )}, visible data sets: ${this.visibleDataSets.map((ds) => ds.name).join(', ')}`
1498
- );
1499
- this.state = 'active';
1500
-
1501
- this.handleMessage(message, 'on resume');
1502
- }
1503
-
1504
- private checkForSentinelHttpResponse(error: any, dataSetName?: string) {
1505
- const isValidDataSetForSentinel =
1506
- dataSetName === undefined ||
1507
- PossibleSentinelMessageDataSetNames.includes(dataSetName.toLowerCase());
1508
-
1509
- if (
1510
- ((error.statusCode === 409 && error.body?.errorCode === 2403004) ||
1511
- error.statusCode === 404) &&
1512
- isValidDataSetForSentinel
1513
- ) {
1514
- LoggerProxy.logger.info(
1515
- `HashTreeParser#checkForSentinelHttpResponse --> ${this.debugId} Received ${error.statusCode} for data set "${dataSetName}", indicating that the meeting has ended`
1516
- );
1517
- this.stopAllTimers();
1518
-
1519
- throw new MeetingEndedError();
1520
- }
1521
- }
1522
-
1523
- /**
1524
- * Gets the current hashes from the locus for a specific data set.
1525
- * @param {string} dataSetName
1526
- * @param {string} currentRootHash
1527
- * @returns {string[]}
1528
- */
1529
- private getHashesFromLocus(dataSetName: string, currentRootHash: string) {
1530
- LoggerProxy.logger.info(
1531
- `HashTreeParser#getHashesFromLocus --> ${this.debugId} Requesting hashes for data set "${dataSetName}"`
1532
- );
1533
-
1534
- const dataSet = this.dataSets[dataSetName];
1535
-
1536
- const url = `${dataSet.url}/hashtree`;
1537
-
1538
- return this.webexRequest({
1539
- method: HTTP_VERBS.GET,
1540
- uri: url,
1541
- qs: {
1542
- rootHash: currentRootHash,
1543
- },
1544
- })
1545
- .then((response) => {
1546
- const hashes = response.body?.hashes as string[] | undefined;
1547
- const dataSetFromResponse = response.body?.dataSet;
1548
-
1549
- if (!hashes || !Array.isArray(hashes)) {
1550
- LoggerProxy.logger.warn(
1551
- `HashTreeParser#getHashesFromLocus --> ${this.debugId} Locus returned invalid hashes, response body=`,
1552
- response.body
1553
- );
1554
- throw new Error(`Locus returned invalid hashes: ${hashes}`);
1555
- }
1556
-
1557
- LoggerProxy.logger.info(
1558
- `HashTreeParser#getHashesFromLocus --> ${
1559
- this.debugId
1560
- } Received hashes for data set "${dataSetName}": ${JSON.stringify(hashes)}`
1561
- );
1562
-
1563
- return {
1564
- hashes,
1565
- dataSet: dataSetFromResponse as DataSet,
1566
- };
1567
- })
1568
- .catch((error) => {
1569
- LoggerProxy.logger.error(
1570
- `HashTreeParser#getHashesFromLocus --> ${this.debugId} Error ${error.statusCode} fetching hashes for data set "${dataSetName}":`,
1571
- error
1572
- );
1573
- this.checkForSentinelHttpResponse(error, dataSet.name);
1574
-
1575
- throw error;
1576
- });
1577
- }
1578
-
1579
- /**
1580
- * Sends a sync request to Locus for the specified data set.
1581
- *
1582
- * @param {InternalDataSet} dataSet The data set to sync.
1583
- * @param {Record<number, LeafDataItem[]>} mismatchedLeavesData The mismatched leaves data to include in the sync request.
1584
- * @returns {Promise<HashTreeMessage|null>}
1585
- */
1586
- private sendSyncRequestToLocus(
1587
- dataSet: InternalDataSet,
1588
- mismatchedLeavesData: Record<number, LeafDataItem[]>
1589
- ): Promise<HashTreeMessage | null> {
1590
- LoggerProxy.logger.info(
1591
- `HashTreeParser#sendSyncRequestToLocus --> ${this.debugId} Sending sync request for data set "${dataSet.name}"`
1592
- );
1593
-
1594
- const url = `${dataSet.url}/sync`;
1595
- const body = {
1596
- leafCount: dataSet.leafCount,
1597
- leafDataEntries: [],
1598
- };
1599
-
1600
- Object.keys(mismatchedLeavesData).forEach((index) => {
1601
- body.leafDataEntries.push({
1602
- leafIndex: parseInt(index, 10),
1603
- elementIds: mismatchedLeavesData[index],
1604
- });
1605
- });
1606
-
1607
- const ourCurrentRootHash = dataSet.hashTree ? dataSet.hashTree.getRootHash() : EMPTY_HASH;
1608
-
1609
- return this.webexRequest({
1610
- method: HTTP_VERBS.POST,
1611
- uri: url,
1612
- qs: {
1613
- rootHash: ourCurrentRootHash,
1614
- },
1615
- body,
1616
- })
1617
- .then((resp) => {
1618
- LoggerProxy.logger.info(
1619
- `HashTreeParser#sendSyncRequestToLocus --> ${this.debugId} Sync request succeeded for "${dataSet.name}"`
1620
- );
1621
-
1622
- if (!resp.body || isEmpty(resp.body)) {
1623
- LoggerProxy.logger.info(
1624
- `HashTreeParser#sendSyncRequestToLocus --> ${this.debugId} Got ${resp.statusCode} with empty body for sync request for data set "${dataSet.name}", data should arrive via messages`
1625
- );
1626
-
1627
- return null;
1628
- }
1629
-
1630
- return resp.body as HashTreeMessage;
1631
- })
1632
- .catch((error) => {
1633
- LoggerProxy.logger.error(
1634
- `HashTreeParser#sendSyncRequestToLocus --> ${this.debugId} Error ${error.statusCode} sending sync request for data set "${dataSet.name}":`,
1635
- error
1636
- );
1637
- this.checkForSentinelHttpResponse(error, dataSet.name);
1638
-
1639
- throw error;
1640
- });
141
+ async handleMessage(message: HashTreeMessage, debugText?: string): Promise<void> {
142
+ // todo
1641
143
  }
1642
144
  }
1643
145