@webex/plugin-meetings 3.11.0-next.2 → 3.11.0-next.21
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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +307 -139
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +2 -1
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +55 -42
- package/dist/locus-info/index.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +33 -22
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +108 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +76 -26
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +47 -12
- package/dist/types/hashTree/types.d.ts +1 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/locus-info/index.d.ts +9 -2
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +8 -5
- package/dist/types/meeting/util.d.ts +28 -0
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +294 -96
- package/src/hashTree/types.ts +1 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/locus-info/index.ts +83 -35
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/index.ts +24 -11
- package/src/meeting/util.ts +132 -1
- package/src/meetings/index.ts +93 -8
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/reactions/reactions.type.ts +1 -0
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +942 -110
- package/test/unit/spec/locus-info/index.js +88 -17
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/index.js +160 -2
- package/test/unit/spec/meeting/utils.js +294 -22
- package/test/unit/spec/meetings/index.js +594 -17
package/src/meeting/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {LocalCameraStream, LocalMicrophoneStream} from '@webex/media-helpers';
|
|
2
|
+
import url from 'url';
|
|
2
3
|
|
|
3
4
|
import {cloneDeep} from 'lodash';
|
|
4
5
|
import {MeetingNotActiveError, UserNotJoinedError} from '../common/errors/webex-errors';
|
|
@@ -32,6 +33,7 @@ const MeetingUtil = {
|
|
|
32
33
|
// First todo: add check for existance
|
|
33
34
|
parsed.locus = response.body.locus;
|
|
34
35
|
parsed.dataSets = response.body.dataSets;
|
|
36
|
+
parsed.metadata = response.body.metaData;
|
|
35
37
|
parsed.mediaConnections = response.body.mediaConnections;
|
|
36
38
|
parsed.locusUrl = parsed.locus.url;
|
|
37
39
|
parsed.locusId = parsed.locus.url.split('/').pop();
|
|
@@ -47,6 +49,124 @@ const MeetingUtil = {
|
|
|
47
49
|
return parsed;
|
|
48
50
|
},
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Sanitizes a WebSocket URL by extracting only protocol, host, and pathname
|
|
54
|
+
* Returns concatenated protocol + host + pathname for safe logging
|
|
55
|
+
* Note: This is used for logging only; URL matching uses partial matching via _urlsPartiallyMatch
|
|
56
|
+
* @param {string} urlString - The URL to sanitize
|
|
57
|
+
* @returns {string} Sanitized URL or empty string if parsing fails
|
|
58
|
+
*/
|
|
59
|
+
sanitizeWebSocketUrl: (urlString: string): string => {
|
|
60
|
+
if (!urlString || typeof urlString !== 'string') {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const parsedUrl = url.parse(urlString);
|
|
66
|
+
const protocol = parsedUrl.protocol || '';
|
|
67
|
+
const host = parsedUrl.host || '';
|
|
68
|
+
|
|
69
|
+
// If we don't have at least protocol and host, it's not a valid URL
|
|
70
|
+
if (!protocol || !host) {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const pathname = parsedUrl.pathname || '';
|
|
75
|
+
|
|
76
|
+
// Strip trailing slash if pathname is just '/'
|
|
77
|
+
const normalizedPathname = pathname === '/' ? '' : pathname;
|
|
78
|
+
|
|
79
|
+
return `${protocol}//${host}${normalizedPathname}`;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
LoggerProxy.logger.warn(
|
|
82
|
+
`Meeting:util#sanitizeWebSocketUrl --> unable to parse URL: ${error}`
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Checks if two URLs partially match using an endsWith approach
|
|
91
|
+
* Combines host and pathname, then checks if one ends with the other
|
|
92
|
+
* This handles cases where one URL goes through a proxy (e.g., /webproxy/) while the other is direct
|
|
93
|
+
* @param {string} url1 - First URL to compare
|
|
94
|
+
* @param {string} url2 - Second URL to compare
|
|
95
|
+
* @returns {boolean} True if one URL path ends with the other (partial match), false otherwise
|
|
96
|
+
*/
|
|
97
|
+
_urlsPartiallyMatch: (url1: string, url2: string): boolean => {
|
|
98
|
+
if (!url1 || !url2) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const parsedUrl1 = url.parse(url1);
|
|
104
|
+
const parsedUrl2 = url.parse(url2);
|
|
105
|
+
|
|
106
|
+
const host1 = parsedUrl1.host || '';
|
|
107
|
+
const host2 = parsedUrl2.host || '';
|
|
108
|
+
const pathname1 = parsedUrl1.pathname || '';
|
|
109
|
+
const pathname2 = parsedUrl2.pathname || '';
|
|
110
|
+
|
|
111
|
+
// If either failed to parse, they don't match
|
|
112
|
+
if (!host1 || !host2 || !pathname1 || !pathname2) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Combine host and pathname for comparison
|
|
117
|
+
const combined1 = host1 + pathname1;
|
|
118
|
+
const combined2 = host2 + pathname2;
|
|
119
|
+
|
|
120
|
+
// Check if one combined path ends with the other (handles proxy URLs)
|
|
121
|
+
return combined1.endsWith(combined2) || combined2.endsWith(combined1);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
LoggerProxy.logger.warn('Meeting:util#_urlsPartiallyMatch --> error comparing URLs', e);
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Gets socket URL information for metrics, including whether the socket URLs match
|
|
131
|
+
* Uses partial matching to handle proxy URLs (e.g., URLs with /webproxy/ prefix)
|
|
132
|
+
* @param {Object} webex - The webex instance
|
|
133
|
+
* @returns {Object} Object with hasMismatchedSocket, mercurySocketUrl, and deviceSocketUrl properties
|
|
134
|
+
*/
|
|
135
|
+
getSocketUrlInfo: (
|
|
136
|
+
webex: any
|
|
137
|
+
): {hasMismatchedSocket: boolean; mercurySocketUrl: string; deviceSocketUrl: string} => {
|
|
138
|
+
try {
|
|
139
|
+
const mercuryUrl = webex?.internal?.mercury?.socket?.url;
|
|
140
|
+
const deviceUrl = webex?.internal?.device?.webSocketUrl;
|
|
141
|
+
|
|
142
|
+
const sanitizedMercuryUrl = MeetingUtil.sanitizeWebSocketUrl(mercuryUrl);
|
|
143
|
+
const sanitizedDeviceUrl = MeetingUtil.sanitizeWebSocketUrl(deviceUrl);
|
|
144
|
+
|
|
145
|
+
// Only report a mismatch if both URLs are present and they don't match
|
|
146
|
+
// If either URL is missing, we can't determine if there's a mismatch, so return false
|
|
147
|
+
let hasMismatchedSocket = false;
|
|
148
|
+
if (sanitizedMercuryUrl && sanitizedDeviceUrl) {
|
|
149
|
+
hasMismatchedSocket = !MeetingUtil._urlsPartiallyMatch(mercuryUrl, deviceUrl);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
hasMismatchedSocket,
|
|
154
|
+
mercurySocketUrl: sanitizedMercuryUrl,
|
|
155
|
+
deviceSocketUrl: sanitizedDeviceUrl,
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
LoggerProxy.logger.warn(
|
|
159
|
+
`Meeting:util#getSocketUrlInfo --> error getting socket URL info: ${error}`
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
hasMismatchedSocket: false,
|
|
164
|
+
mercurySocketUrl: '',
|
|
165
|
+
deviceSocketUrl: '',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
50
170
|
remoteUpdateAudioVideo: (meeting, audioMuted?: boolean, videoMuted?: boolean) => {
|
|
51
171
|
if (!meeting) {
|
|
52
172
|
return Promise.reject(new ParameterError('You need a meeting object.'));
|
|
@@ -203,6 +323,7 @@ const MeetingUtil = {
|
|
|
203
323
|
const parsed = MeetingUtil.parseLocusJoin(res);
|
|
204
324
|
meeting.setLocus(parsed);
|
|
205
325
|
meeting.isoLocalClientMeetingJoinTime = res?.headers?.date; // read from header if exist, else fall back to system clock : https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-555657
|
|
326
|
+
const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
|
|
206
327
|
webex.internal.newMetrics.submitClientEvent({
|
|
207
328
|
name: 'client.locus.join.response',
|
|
208
329
|
payload: {
|
|
@@ -210,6 +331,9 @@ const MeetingUtil = {
|
|
|
210
331
|
identifiers: {
|
|
211
332
|
trackingId: res.headers.trackingid,
|
|
212
333
|
},
|
|
334
|
+
eventData: {
|
|
335
|
+
...socketUrlInfo,
|
|
336
|
+
},
|
|
213
337
|
},
|
|
214
338
|
options: {
|
|
215
339
|
meetingId: meeting.id,
|
|
@@ -220,12 +344,19 @@ const MeetingUtil = {
|
|
|
220
344
|
return parsed;
|
|
221
345
|
})
|
|
222
346
|
.catch((err) => {
|
|
347
|
+
const socketUrlInfo = MeetingUtil.getSocketUrlInfo(webex);
|
|
223
348
|
webex.internal.newMetrics.submitClientEvent({
|
|
224
349
|
name: 'client.locus.join.response',
|
|
225
350
|
payload: {
|
|
226
351
|
identifiers: {meetingLookupUrl: meeting.meetingInfo?.meetingLookupUrl},
|
|
352
|
+
eventData: {
|
|
353
|
+
...socketUrlInfo,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
options: {
|
|
357
|
+
meetingId: meeting.id,
|
|
358
|
+
rawError: err,
|
|
227
359
|
},
|
|
228
|
-
options: {meetingId: meeting.id, rawError: err},
|
|
229
360
|
});
|
|
230
361
|
|
|
231
362
|
throw err;
|
package/src/meetings/index.ts
CHANGED
|
@@ -195,6 +195,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
195
195
|
preferredWebexSite: any;
|
|
196
196
|
reachability: Reachability;
|
|
197
197
|
registered: any;
|
|
198
|
+
registrationPromise: Promise<void>;
|
|
199
|
+
unregistrationPromise: Promise<void>;
|
|
198
200
|
request: any;
|
|
199
201
|
geoHintInfo: any;
|
|
200
202
|
meetingInfo: any;
|
|
@@ -929,9 +931,20 @@ export default class Meetings extends WebexPlugin {
|
|
|
929
931
|
* @returns {Promise} A promise that resolves when the step is completed.
|
|
930
932
|
*/
|
|
931
933
|
executeRegistrationStep(step: () => Promise<any>, stepName: string) {
|
|
932
|
-
return step()
|
|
933
|
-
|
|
934
|
-
|
|
934
|
+
return step()
|
|
935
|
+
.then(() => {
|
|
936
|
+
LoggerProxy.logger.info(
|
|
937
|
+
`Meetings:index#executeRegistrationStep --> INFO, ${stepName} completed`
|
|
938
|
+
);
|
|
939
|
+
this.registrationStatus[stepName] = true;
|
|
940
|
+
})
|
|
941
|
+
.catch((error) => {
|
|
942
|
+
LoggerProxy.logger.error(
|
|
943
|
+
`Meetings:index#executeRegistrationStep --> ERROR, ${stepName} failed: ${error.message}`
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
return Promise.reject(error);
|
|
947
|
+
});
|
|
935
948
|
}
|
|
936
949
|
|
|
937
950
|
/**
|
|
@@ -944,7 +957,33 @@ export default class Meetings extends WebexPlugin {
|
|
|
944
957
|
* @memberof Meetings
|
|
945
958
|
*/
|
|
946
959
|
public register(deviceRegistrationOptions?: DeviceRegistrationOptions): Promise<any> {
|
|
947
|
-
this.
|
|
960
|
+
if (this.unregistrationPromise) {
|
|
961
|
+
LoggerProxy.logger.info(
|
|
962
|
+
'Meetings:index#register --> INFO, Meetings plugin unregistration in progress, waiting to register'
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
this.registrationPromise = this.unregistrationPromise
|
|
966
|
+
.catch(() => {}) // It doesn't matter what happened during unregistration
|
|
967
|
+
.finally(() => {
|
|
968
|
+
LoggerProxy.logger.info(
|
|
969
|
+
'Meetings:index#register --> INFO, Meetings plugin unregistration completed, proceeding to register'
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
this.registrationPromise = null;
|
|
973
|
+
|
|
974
|
+
return this.register(deviceRegistrationOptions);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
return this.registrationPromise;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (this.registrationPromise) {
|
|
981
|
+
LoggerProxy.logger.info(
|
|
982
|
+
'Meetings:index#register --> INFO, Meetings plugin registration in progress, returning existing promise'
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
return this.registrationPromise;
|
|
986
|
+
}
|
|
948
987
|
|
|
949
988
|
// @ts-ignore
|
|
950
989
|
if (!this.webex.canAuthorize) {
|
|
@@ -963,7 +1002,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
963
1002
|
return Promise.resolve();
|
|
964
1003
|
}
|
|
965
1004
|
|
|
966
|
-
|
|
1005
|
+
LoggerProxy.logger.info('Meetings:index#register --> INFO, Registering Meetings plugin');
|
|
1006
|
+
|
|
1007
|
+
this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
|
|
1008
|
+
|
|
1009
|
+
this.registrationPromise = Promise.all([
|
|
967
1010
|
this.executeRegistrationStep(() => this.fetchUserPreferredWebexSite(), 'fetchWebexSite'),
|
|
968
1011
|
this.executeRegistrationStep(() => this.getGeoHint(), 'getGeoHint'),
|
|
969
1012
|
this.executeRegistrationStep(
|
|
@@ -1022,7 +1065,12 @@ export default class Meetings extends WebexPlugin {
|
|
|
1022
1065
|
});
|
|
1023
1066
|
|
|
1024
1067
|
return Promise.reject(error);
|
|
1068
|
+
})
|
|
1069
|
+
.finally(() => {
|
|
1070
|
+
this.registrationPromise = null;
|
|
1025
1071
|
});
|
|
1072
|
+
|
|
1073
|
+
return this.registrationPromise;
|
|
1026
1074
|
}
|
|
1027
1075
|
|
|
1028
1076
|
/**
|
|
@@ -1034,6 +1082,35 @@ export default class Meetings extends WebexPlugin {
|
|
|
1034
1082
|
* @memberof Meetings
|
|
1035
1083
|
*/
|
|
1036
1084
|
unregister() {
|
|
1085
|
+
if (this.unregistrationPromise) {
|
|
1086
|
+
LoggerProxy.logger.info(
|
|
1087
|
+
'Meetings:index#unregister --> INFO, Meetings plugin unregistration in progress, returning existing promise'
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
return this.unregistrationPromise;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
if (this.registrationPromise) {
|
|
1094
|
+
LoggerProxy.logger.info(
|
|
1095
|
+
'Meetings:index#unregister --> INFO, Meetings plugin registration in progress, waiting to unregister'
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
// Wait for registration to complete (success or failure), then call unregister again
|
|
1099
|
+
this.unregistrationPromise = this.registrationPromise
|
|
1100
|
+
.catch(() => {}) // It doesn't matter what happened during registration
|
|
1101
|
+
.finally(() => {
|
|
1102
|
+
LoggerProxy.logger.info(
|
|
1103
|
+
'Meetings:index#unregister --> INFO, Meetings plugin registration completed, proceeding to unregister'
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
this.unregistrationPromise = null;
|
|
1107
|
+
|
|
1108
|
+
return this.unregister();
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
return this.unregistrationPromise;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1037
1114
|
if (!this.registered) {
|
|
1038
1115
|
LoggerProxy.logger.info(
|
|
1039
1116
|
'Meetings:index#unregister --> INFO, Meetings plugin already unregistered'
|
|
@@ -1044,10 +1121,14 @@ export default class Meetings extends WebexPlugin {
|
|
|
1044
1121
|
|
|
1045
1122
|
this.stopListeningForEvents();
|
|
1046
1123
|
|
|
1047
|
-
|
|
1124
|
+
this.unregistrationPromise =
|
|
1048
1125
|
// @ts-ignore
|
|
1049
1126
|
this.webex.internal.mercury
|
|
1050
|
-
|
|
1127
|
+
// Use code 3050 with a non-reconnecting reason to prevent Mercury auto-reconnect
|
|
1128
|
+
// during unregister. Without this, disconnect() defaults to code 1000/"Done" which
|
|
1129
|
+
// force-closes as "Done (forced)" - a normalReconnectReason that triggers auto-reconnect,
|
|
1130
|
+
// causing a race condition with device.unregister().
|
|
1131
|
+
.disconnect({code: 3050, reason: 'meetings unregister'})
|
|
1051
1132
|
// @ts-ignore
|
|
1052
1133
|
.then(() => this.webex.internal.device.unregister())
|
|
1053
1134
|
.catch((error) => {
|
|
@@ -1077,7 +1158,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
1077
1158
|
this.registered = false;
|
|
1078
1159
|
this.registrationStatus = clone(INITIAL_REGISTRATION_STATUS);
|
|
1079
1160
|
})
|
|
1080
|
-
|
|
1161
|
+
.finally(() => {
|
|
1162
|
+
this.unregistrationPromise = null;
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
return this.unregistrationPromise;
|
|
1081
1166
|
}
|
|
1082
1167
|
|
|
1083
1168
|
/**
|
package/src/metrics/constants.ts
CHANGED
|
@@ -90,6 +90,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
90
90
|
MEDIA_ISSUE_DETECTED: 'js_sdk_media_issue_detected',
|
|
91
91
|
LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH: 'js_sdk_locus_classic_vs_hash_tree_mismatch',
|
|
92
92
|
LOCUS_HASH_TREE_UNSUPPORTED_OPERATION: 'js_sdk_locus_hash_tree_unsupported_operation',
|
|
93
|
+
MEDIA_STILL_NOT_CONNECTED: 'js_sdk_media_still_not_connected',
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -356,7 +356,7 @@ export class MediaRequestManager {
|
|
|
356
356
|
mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
|
|
357
357
|
this.getMaxPayloadBitsPerSecond(mr),
|
|
358
358
|
mr.codecInfo && [
|
|
359
|
-
|
|
359
|
+
WcmeCodecInfo.fromH264(
|
|
360
360
|
0x80,
|
|
361
361
|
new H264Codec(
|
|
362
362
|
mr.codecInfo.maxFs,
|
|
@@ -652,4 +652,70 @@ describe('HashTree', () => {
|
|
|
652
652
|
expect(() => hashTree.computeLeafHash(2)).to.not.throw();
|
|
653
653
|
});
|
|
654
654
|
});
|
|
655
|
+
|
|
656
|
+
describe('getItemVersion', () => {
|
|
657
|
+
it('should return version when item exists', () => {
|
|
658
|
+
const items: LeafDataItem[] = [
|
|
659
|
+
{type: 'participant', id: 1, version: 5},
|
|
660
|
+
{type: 'self', id: 2, version: 10},
|
|
661
|
+
];
|
|
662
|
+
const hashTree = new HashTree(items, 4);
|
|
663
|
+
|
|
664
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.equal(5);
|
|
665
|
+
expect(hashTree.getItemVersion(2, 'self')).to.equal(10);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('should return undefined when item does not exist', () => {
|
|
669
|
+
const items: LeafDataItem[] = [{type: 'participant', id: 1, version: 5}];
|
|
670
|
+
const hashTree = new HashTree(items, 4);
|
|
671
|
+
|
|
672
|
+
expect(hashTree.getItemVersion(999, 'participant')).to.be.undefined;
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should return undefined when type does not match', () => {
|
|
676
|
+
const items: LeafDataItem[] = [{type: 'participant', id: 1, version: 5}];
|
|
677
|
+
const hashTree = new HashTree(items, 4);
|
|
678
|
+
|
|
679
|
+
expect(hashTree.getItemVersion(1, 'self')).to.be.undefined;
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should return undefined for tree with 0 leaves', () => {
|
|
683
|
+
const hashTree = new HashTree([], 0);
|
|
684
|
+
|
|
685
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.be.undefined;
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('should return correct version when multiple items exist in same leaf', () => {
|
|
689
|
+
const items: LeafDataItem[] = [
|
|
690
|
+
{type: 'participant', id: 1, version: 3}, // leaf 1 (1 % 2 = 1)
|
|
691
|
+
{type: 'self', id: 3, version: 7}, // leaf 1 (3 % 2 = 1)
|
|
692
|
+
{type: 'locus', id: 5, version: 12}, // leaf 1 (5 % 2 = 1)
|
|
693
|
+
];
|
|
694
|
+
const hashTree = new HashTree(items, 2);
|
|
695
|
+
|
|
696
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.equal(3);
|
|
697
|
+
expect(hashTree.getItemVersion(3, 'self')).to.equal(7);
|
|
698
|
+
expect(hashTree.getItemVersion(5, 'locus')).to.equal(12);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('should return updated version after item is updated', () => {
|
|
702
|
+
const hashTree = new HashTree([{type: 'participant', id: 1, version: 5}], 4);
|
|
703
|
+
|
|
704
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.equal(5);
|
|
705
|
+
|
|
706
|
+
hashTree.putItem({type: 'participant', id: 1, version: 10});
|
|
707
|
+
|
|
708
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.equal(10);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('should return undefined after item is removed', () => {
|
|
712
|
+
const hashTree = new HashTree([{type: 'participant', id: 1, version: 5}], 4);
|
|
713
|
+
|
|
714
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.equal(5);
|
|
715
|
+
|
|
716
|
+
hashTree.removeItem({type: 'participant', id: 1, version: 5});
|
|
717
|
+
|
|
718
|
+
expect(hashTree.getItemVersion(1, 'participant')).to.be.undefined;
|
|
719
|
+
});
|
|
720
|
+
});
|
|
655
721
|
});
|