@webex/plugin-meetings 3.12.0-next.20 → 3.12.0-next.22
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/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/hashTreeParser.js +480 -320
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +170 -36
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meetings/index.js +130 -45
- package/dist/meetings/index.js.map +1 -1
- package/dist/types/hashTree/hashTreeParser.d.ts +40 -12
- package/dist/types/locus-info/index.d.ts +29 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +14 -14
- package/src/hashTree/hashTreeParser.ts +182 -97
- package/src/locus-info/index.ts +176 -48
- package/src/meetings/index.ts +42 -17
- package/test/unit/spec/hashTree/hashTreeParser.ts +372 -7
- package/test/unit/spec/locus-info/index.js +179 -4
- package/test/unit/spec/meetings/index.js +127 -5
package/src/locus-info/index.ts
CHANGED
|
@@ -684,11 +684,31 @@ export default class LocusInfo extends EventsScope {
|
|
|
684
684
|
* @param {LocusApiResponseBody} responseBody body of the http response from Locus API call
|
|
685
685
|
* @returns {void}
|
|
686
686
|
*/
|
|
687
|
-
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
|
|
687
|
+
handleLocusAPIResponse(meeting: any, responseBody: LocusApiResponseBody): void {
|
|
688
688
|
const isWrapped = 'locus' in responseBody;
|
|
689
689
|
const locusUrl = isWrapped ? responseBody.locus?.url : responseBody.url;
|
|
690
690
|
const hashTreeParserEntry = locusUrl && this.hashTreeParsers.get(locusUrl);
|
|
691
|
-
|
|
691
|
+
const locus = isWrapped
|
|
692
|
+
? (responseBody as {locus: LocusDTO}).locus
|
|
693
|
+
: (responseBody as LocusDTO);
|
|
694
|
+
|
|
695
|
+
if (this.hashTreeParsers.size > 0) {
|
|
696
|
+
// We are in hash tree mode. Check if we need to create/reactivate a parser for this locusUrl.
|
|
697
|
+
if (!hashTreeParserEntry || hashTreeParserEntry.parser.state === 'stopped') {
|
|
698
|
+
if (!locusUrl) {
|
|
699
|
+
LoggerProxy.logger.warn(
|
|
700
|
+
'Locus-info:index#handleLocusAPIResponse --> API response has no locusUrl, cannot handle hash tree parser switch'
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
this.handleHashTreeParserSwitchForAPIResponse(locusUrl, locus);
|
|
707
|
+
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Active parser found - pass the API response to it
|
|
692
712
|
if (isWrapped) {
|
|
693
713
|
if (!responseBody.dataSets) {
|
|
694
714
|
this.sendClassicVsHashTreeMismatchMetric(
|
|
@@ -704,20 +724,23 @@ export default class LocusInfo extends EventsScope {
|
|
|
704
724
|
// update the data in our hash trees
|
|
705
725
|
hashTreeParserEntry.parser.handleLocusUpdate(responseBody);
|
|
706
726
|
} else {
|
|
707
|
-
// LocusDTO without wrapper - pass it through as if it had no dataSets
|
|
727
|
+
// LocusDTO without wrapper - pass it through as if it had no dataSets nor metadata
|
|
708
728
|
hashTreeParserEntry.parser.handleLocusUpdate({locus: responseBody});
|
|
709
729
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
730
|
+
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// No hash tree parsers - classic Locus mode
|
|
735
|
+
if (isWrapped && responseBody.dataSets) {
|
|
736
|
+
this.sendClassicVsHashTreeMismatchMetric(
|
|
737
|
+
meeting,
|
|
738
|
+
`unexpected hash tree dataSets in API response`
|
|
739
|
+
);
|
|
720
740
|
}
|
|
741
|
+
|
|
742
|
+
// classic Locus delta
|
|
743
|
+
this.handleLocusDelta(locus, meeting);
|
|
721
744
|
}
|
|
722
745
|
|
|
723
746
|
/**
|
|
@@ -946,6 +969,114 @@ export default class LocusInfo extends EventsScope {
|
|
|
946
969
|
}
|
|
947
970
|
}
|
|
948
971
|
|
|
972
|
+
/**
|
|
973
|
+
* Helper that handles the common logic for reactivating a stopped HashTreeParser when
|
|
974
|
+
* a newer "replaces" is detected. Used by both the message and API response parser switch methods.
|
|
975
|
+
*
|
|
976
|
+
* @param {string} callerName - name of the calling method, used in log messages
|
|
977
|
+
* @param {string} locusUrl - the locus URL of the stopped parser
|
|
978
|
+
* @param {HashTreeParserEntry} stoppedEntry - the stopped parser entry
|
|
979
|
+
* @param {ReplacesInfo} replaces - replacement info extracted from self
|
|
980
|
+
* @param {Function} resumeCallback - callback to invoke after reactivation to resume the parser
|
|
981
|
+
* @returns {void}
|
|
982
|
+
*/
|
|
983
|
+
private resumeStoppedParser(
|
|
984
|
+
callerName: string,
|
|
985
|
+
locusUrl: string,
|
|
986
|
+
stoppedEntry: HashTreeParserEntry,
|
|
987
|
+
replaces: ReplacesInfo | undefined,
|
|
988
|
+
resumeCallback: () => void
|
|
989
|
+
): void {
|
|
990
|
+
// this check is just for typescript, it should never happen, replaces should always be defined
|
|
991
|
+
if (!replaces) {
|
|
992
|
+
LoggerProxy.logger.info(
|
|
993
|
+
`Locus-info:index#${callerName} --> received data for stopped HashTreeParser with locusUrl ${locusUrl}, but no replaces info provided, so not re-activating the parser`
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (replaces.replacedAt <= (stoppedEntry.replacedAt || '')) {
|
|
1000
|
+
LoggerProxy.logger.info(
|
|
1001
|
+
`Locus-info:index#${callerName} --> received data for stopped HashTreeParser with locusUrl ${locusUrl}, but replaces info provided is not newer, so not re-activating the parser`
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
LoggerProxy.logger.info(
|
|
1008
|
+
`Locus-info:index#${callerName} --> reactivating HashTreeParser for locusUrl=${locusUrl}, which replaces ${replaces.locusUrl}`
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
const replacedEntry = this.hashTreeParsers.get(replaces.locusUrl);
|
|
1012
|
+
|
|
1013
|
+
if (replacedEntry) {
|
|
1014
|
+
replacedEntry.replacedAt = replaces.replacedAt;
|
|
1015
|
+
replacedEntry.parser.stop();
|
|
1016
|
+
} else {
|
|
1017
|
+
LoggerProxy.logger.warn(
|
|
1018
|
+
`Locus-info:index#${callerName} --> the parser that is supposed to be replaced with the currently reactivated parser is not found, locusUrl=${replaces.locusUrl}`
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
stoppedEntry.initializedFromHashTree = false;
|
|
1023
|
+
this.hashTreeObjectId2ParticipantId.clear();
|
|
1024
|
+
|
|
1025
|
+
resumeCallback();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Handles an API response whose locusUrl doesn't match any active HashTreeParser
|
|
1030
|
+
* (either no entry exists, or the existing entry is stopped).
|
|
1031
|
+
* Creates a new parser or reactivates a stopped one using initializeFromGetLociResponse.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {string} locusUrl - the locus URL from the API response
|
|
1034
|
+
* @param {LocusDTO} locus - the locus DTO from the API response
|
|
1035
|
+
* @returns {void}
|
|
1036
|
+
*/
|
|
1037
|
+
private handleHashTreeParserSwitchForAPIResponse(locusUrl: string, locus: LocusDTO): void {
|
|
1038
|
+
const entry = this.hashTreeParsers.get(locusUrl);
|
|
1039
|
+
|
|
1040
|
+
const replaces = getReplaceInfoFromSelf(
|
|
1041
|
+
locus.self,
|
|
1042
|
+
// @ts-ignore
|
|
1043
|
+
this.webex.internal.device.url
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
if (!entry) {
|
|
1047
|
+
LoggerProxy.logger.info(
|
|
1048
|
+
`Locus-info:index#handleHashTreeParserSwitchForAPIResponse --> no parser for locusUrl ${locusUrl}, creating a new one`
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
const parser = this.createHashTreeParser({
|
|
1052
|
+
locusUrl,
|
|
1053
|
+
initialLocus: {locus: null, dataSets: []},
|
|
1054
|
+
metadata: null,
|
|
1055
|
+
replacedAt: replaces?.replacedAt,
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
parser.initializeFromGetLociResponse(locus);
|
|
1059
|
+
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (entry.parser.state !== 'stopped') {
|
|
1064
|
+
LoggerProxy.logger.warn(
|
|
1065
|
+
`Locus-info:index#handleHashTreeParserSwitchForAPIResponse --> unexpected parser state "${entry.parser.state}" for locusUrl ${locusUrl}`
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
this.resumeStoppedParser(
|
|
1072
|
+
'handleHashTreeParserSwitchForAPIResponse',
|
|
1073
|
+
locusUrl,
|
|
1074
|
+
entry,
|
|
1075
|
+
replaces,
|
|
1076
|
+
() => entry.parser.resumeFromApiResponse(locus)
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
949
1080
|
/**
|
|
950
1081
|
* Checks if the hash tree message should trigger a switch to a different HashTreeParser
|
|
951
1082
|
*
|
|
@@ -994,36 +1125,12 @@ export default class LocusInfo extends EventsScope {
|
|
|
994
1125
|
if (entry.parser.state === 'stopped') {
|
|
995
1126
|
// the message matches a stopped parser, we need to check if maybe this is a new "replacement" and we need to re-activate the parser
|
|
996
1127
|
// this happens when you move from breakout A -> breakout B -> back to breakout A
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
if (replacedEntry) {
|
|
1005
|
-
replacedEntry.replacedAt = replaces.replacedAt;
|
|
1006
|
-
entry.initializedFromHashTree = false;
|
|
1007
|
-
this.hashTreeObjectId2ParticipantId.clear();
|
|
1008
|
-
|
|
1009
|
-
replacedEntry.parser.stop();
|
|
1010
|
-
entry.parser.resume(message);
|
|
1011
|
-
} else {
|
|
1012
|
-
LoggerProxy.logger.warn(
|
|
1013
|
-
`Locus-info:index#handleHashTreeParserSwitch --> the parser that is supposed to be replaced with the currently resumed parser is not found, locusUrl=${replaces.locusUrl}`
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
} else {
|
|
1017
|
-
LoggerProxy.logger.info(
|
|
1018
|
-
`Locus-info:index#handleHashTreeParserSwitch --> received message for stopped HashTreeParser with locusUrl ${message.locusUrl}, but replaces info provided is not newer, so not re-activating the parser`
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
return true;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
LoggerProxy.logger.info(
|
|
1026
|
-
`Locus-info:index#handleHashTreeParserSwitch --> received message for stopped HashTreeParser with locusUrl ${message.locusUrl}, but no replaces info provided, so not re-activating the parser`
|
|
1128
|
+
this.resumeStoppedParser(
|
|
1129
|
+
'handleHashTreeParserSwitch',
|
|
1130
|
+
message.locusUrl,
|
|
1131
|
+
entry,
|
|
1132
|
+
replaces,
|
|
1133
|
+
() => entry.parser.resumeFromMessage(message)
|
|
1027
1134
|
);
|
|
1028
1135
|
|
|
1029
1136
|
return true;
|
|
@@ -1064,6 +1171,21 @@ export default class LocusInfo extends EventsScope {
|
|
|
1064
1171
|
}
|
|
1065
1172
|
}
|
|
1066
1173
|
|
|
1174
|
+
/**
|
|
1175
|
+
* Triggers a sync of all hash tree datasets for all hash tree parsers associated with this meeting.
|
|
1176
|
+
* The syncs are executed sequentially within each parser.
|
|
1177
|
+
*
|
|
1178
|
+
* @returns {Promise<void>}
|
|
1179
|
+
*/
|
|
1180
|
+
async syncAllHashTreeDatasets(): Promise<void> {
|
|
1181
|
+
for (const [, entry] of this.hashTreeParsers) {
|
|
1182
|
+
if (entry.parser) {
|
|
1183
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1184
|
+
await entry.parser.syncAllDatasets();
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1067
1189
|
/**
|
|
1068
1190
|
* Callback registered with HashTreeParser to receive locus info updates.
|
|
1069
1191
|
* Updates our locus info based on the data parsed by the hash tree parser.
|
|
@@ -1202,11 +1324,17 @@ export default class LocusInfo extends EventsScope {
|
|
|
1202
1324
|
*/
|
|
1203
1325
|
parse(meeting: any, data: any) {
|
|
1204
1326
|
if (this.hashTreeParsers.size > 0) {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
data.
|
|
1209
|
-
|
|
1327
|
+
if (data.eventType === LOCUSEVENT.SDK_LOCUS_FROM_SYNC_MEETINGS) {
|
|
1328
|
+
// sync meetings response follows the format of "not wrapped" locus API responses,
|
|
1329
|
+
// so has no dataSets nor Metadata
|
|
1330
|
+
this.handleLocusAPIResponse(meeting, {...data.locus});
|
|
1331
|
+
} else {
|
|
1332
|
+
this.handleHashTreeMessage(
|
|
1333
|
+
meeting,
|
|
1334
|
+
data.eventType,
|
|
1335
|
+
data.stateElementsMessage as HashTreeMessage
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1210
1338
|
} else {
|
|
1211
1339
|
const {eventType} = data;
|
|
1212
1340
|
|
package/src/meetings/index.ts
CHANGED
|
@@ -71,6 +71,7 @@ import {HashTreeObject} from '../hashTree/types';
|
|
|
71
71
|
import {isSelf} from '../hashTree/utils';
|
|
72
72
|
|
|
73
73
|
import {createLocusFromHashTreeMessage, findMeetingForHashTreeMessage} from '../locus-info';
|
|
74
|
+
import {LocusDTO} from '../locus-info/types';
|
|
74
75
|
|
|
75
76
|
let mediaLogger;
|
|
76
77
|
|
|
@@ -1886,23 +1887,20 @@ export default class Meetings extends WebexPlugin {
|
|
|
1886
1887
|
* @public
|
|
1887
1888
|
* @memberof Meetings
|
|
1888
1889
|
*/
|
|
1889
|
-
public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1890
|
+
public async syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1890
1891
|
// @ts-ignore
|
|
1891
1892
|
if (this.webex.credentials.isUnverifiedGuest) {
|
|
1892
1893
|
LoggerProxy.logger.info(
|
|
1893
|
-
'Meetings:index#syncMeetings --> skipping
|
|
1894
|
+
'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
|
|
1894
1895
|
);
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return this.request
|
|
1900
|
-
.getActiveMeetings()
|
|
1901
|
-
.then((locusArray: any) => {
|
|
1896
|
+
} else {
|
|
1897
|
+
try {
|
|
1898
|
+
const locusArray = await this.request.getActiveMeetings();
|
|
1902
1899
|
const activeLocusUrl: string[] = [];
|
|
1903
1900
|
|
|
1904
1901
|
if (locusArray?.loci && locusArray.loci.length > 0) {
|
|
1905
1902
|
const lociToUpdate = this.sortLocusArrayToUpdate(locusArray.loci);
|
|
1903
|
+
|
|
1906
1904
|
lociToUpdate.forEach((locus) => {
|
|
1907
1905
|
activeLocusUrl.push(locus.url);
|
|
1908
1906
|
this.handleLocusEvent({
|
|
@@ -1920,21 +1918,48 @@ export default class Meetings extends WebexPlugin {
|
|
|
1920
1918
|
// (they had a locusUrl previously but are no longer active) in the sync
|
|
1921
1919
|
for (const meeting of Object.values(meetingsCollection)) {
|
|
1922
1920
|
// @ts-ignore
|
|
1923
|
-
const {locusUrl} = meeting;
|
|
1921
|
+
const {locusUrl, locusInfo} = meeting;
|
|
1924
1922
|
if ((keepOnlyLocusMeetings || locusUrl) && !activeLocusUrl.includes(locusUrl)) {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1923
|
+
const globalMeetingId = locusInfo?.info?.globalMeetingId;
|
|
1924
|
+
|
|
1925
|
+
if (
|
|
1926
|
+
globalMeetingId &&
|
|
1927
|
+
locusArray?.loci?.some(
|
|
1928
|
+
(locus: LocusDTO) => locus.info?.globalMeetingId === globalMeetingId
|
|
1929
|
+
)
|
|
1930
|
+
) {
|
|
1931
|
+
// don't destroy the meeting as Locus API still returned some Locus that shares
|
|
1932
|
+
// the same globalMeetingId - that happens for example if a webinar user (who hasn't scheduled it)
|
|
1933
|
+
// is in a breakout and gets moved to a different breakout while we were offline
|
|
1934
|
+
} else {
|
|
1935
|
+
// destroy function also uploads logs
|
|
1936
|
+
// @ts-ignore
|
|
1937
|
+
this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
|
|
1938
|
+
}
|
|
1928
1939
|
}
|
|
1929
1940
|
}
|
|
1930
1941
|
}
|
|
1931
|
-
})
|
|
1932
|
-
.catch((error) => {
|
|
1942
|
+
} catch (error) {
|
|
1933
1943
|
LoggerProxy.logger.error(
|
|
1934
1944
|
`Meetings:index#syncMeetings --> failed to sync meetings, ${error}`
|
|
1935
1945
|
);
|
|
1936
|
-
throw
|
|
1937
|
-
}
|
|
1946
|
+
throw error;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// Trigger hash tree syncs for all remaining meetings
|
|
1951
|
+
const remainingMeetings = this.meetingCollection.getAll();
|
|
1952
|
+
const syncPromises = [];
|
|
1953
|
+
|
|
1954
|
+
for (const meeting of Object.values(remainingMeetings) as any[]) {
|
|
1955
|
+
if (meeting.locusInfo) {
|
|
1956
|
+
syncPromises.push(meeting.locusInfo.syncAllHashTreeDatasets());
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (syncPromises.length > 0) {
|
|
1961
|
+
await Promise.all(syncPromises);
|
|
1962
|
+
}
|
|
1938
1963
|
}
|
|
1939
1964
|
|
|
1940
1965
|
/**
|