@webex/plugin-meetings 3.12.0-next.3 → 3.12.0-next.31
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +550 -346
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +222 -61
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.js +372 -292
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +146 -62
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +39 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +116 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +53 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +38 -5
- package/dist/types/meeting/index.d.ts +11 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/multistream/sendSlotManager.d.ts +23 -1
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +16 -16
- package/src/constants.ts +1 -0
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +273 -154
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +233 -79
- package/src/meeting/index.ts +98 -11
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +58 -34
- package/src/meetings/util.ts +44 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +5 -0
- package/src/multistream/sendSlotManager.ts +97 -3
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +839 -37
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/locus-info/index.js +262 -64
- package/test/unit/spec/meeting/index.js +54 -36
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +190 -8
- package/test/unit/spec/meetings/utils.js +124 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
- package/test/unit/spec/webinar/index.ts +60 -0
package/src/hashTree/utils.ts
CHANGED
|
@@ -60,3 +60,20 @@ export const deleteNestedObjectsWithHtMeta = (
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reorders items so that those matching the given priority list come first (in priority order),
|
|
66
|
+
* followed by everything else in their original order.
|
|
67
|
+
*
|
|
68
|
+
* @param {Array<T>} items - The items to reorder
|
|
69
|
+
* @param {string[]} priority - Ordered list of names that should come first
|
|
70
|
+
* @returns {Array<T>} A new array with prioritized items first
|
|
71
|
+
*/
|
|
72
|
+
export function sortByInitPriority<T extends {name: string}>(items: T[], priority: string[]): T[] {
|
|
73
|
+
const prioritized = priority
|
|
74
|
+
.map((name) => items.find((item) => item.name === name))
|
|
75
|
+
.filter(Boolean) as T[];
|
|
76
|
+
const rest = items.filter((item) => !priority.includes(item.name));
|
|
77
|
+
|
|
78
|
+
return [...prioritized, ...rest];
|
|
79
|
+
}
|
|
@@ -18,12 +18,33 @@ export default class LocusRetryStatusInterceptor extends Interceptor {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* @param {
|
|
23
|
-
* @
|
|
24
|
-
* @returns {Promise<WebexHttpError>}
|
|
21
|
+
* Check whether a URI is a Locus /hashtree or /sync endpoint.
|
|
22
|
+
* @param {string} uri
|
|
23
|
+
* @returns {boolean}
|
|
25
24
|
*/
|
|
25
|
+
private static isLocusHashtreeOrSync(uri: string): boolean {
|
|
26
|
+
try {
|
|
27
|
+
const {pathname} = new URL(uri);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
pathname.includes('/locus/') &&
|
|
31
|
+
(pathname.endsWith('/hashtree') || pathname.endsWith('/sync'))
|
|
32
|
+
);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
onResponseError(options, reason) {
|
|
39
|
+
// Don't retry /hashtree or /sync calls for 429 or any 5xx — during a sync storm retries
|
|
40
|
+
// make things worse. The normal sync timers will handle recovery for these endpoints.
|
|
41
|
+
if (
|
|
42
|
+
(reason.statusCode === 429 || reason.statusCode >= 500) &&
|
|
43
|
+
LocusRetryStatusInterceptor.isLocusHashtreeOrSync(options.uri)
|
|
44
|
+
) {
|
|
45
|
+
return Promise.reject(reason);
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
if ((reason.statusCode === 503 || reason.statusCode === 429) && options.uri.includes('locus')) {
|
|
28
49
|
const hasRetriedLocusRequest = rateLimitExpiryTime.get(this);
|
|
29
50
|
const retryAfterTime = options.headers['retry-after'] || 2000;
|
package/src/locus-info/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
|
34
34
|
import HashTreeParser, {
|
|
35
35
|
DataSet,
|
|
36
36
|
HashTreeMessage,
|
|
37
|
+
LocusInfoUpdate,
|
|
37
38
|
LocusInfoUpdateType,
|
|
38
39
|
Metadata,
|
|
39
40
|
} from '../hashTree/hashTreeParser';
|
|
@@ -97,7 +98,7 @@ export type HashTreeParserEntry = {
|
|
|
97
98
|
* Gets the replacement information
|
|
98
99
|
*
|
|
99
100
|
* @param {any} self - "self" object from Locus DTO
|
|
100
|
-
* @param {string} deviceUrl - The URL of the
|
|
101
|
+
* @param {string} deviceUrl - The URL of the specified device
|
|
101
102
|
* @returns {any} The replace information if available, otherwise undefined
|
|
102
103
|
*/
|
|
103
104
|
function getReplaceInfoFromSelf(self: any, deviceUrl: string): ReplacesInfo | undefined {
|
|
@@ -137,14 +138,15 @@ function findLocusUrlInAnyHashTreeParser(
|
|
|
137
138
|
*
|
|
138
139
|
* @param {HashTreeMessage} message - The hash tree message to find the meeting for
|
|
139
140
|
* @param {MeetingCollection} meetingCollection - The collection of meetings to search
|
|
140
|
-
* @param {string} deviceUrl - The URL of the user's device
|
|
141
141
|
* @returns {any} The meeting if found, otherwise undefined
|
|
142
142
|
*/
|
|
143
143
|
export function findMeetingForHashTreeMessage(
|
|
144
|
-
message: HashTreeMessage,
|
|
145
|
-
meetingCollection: MeetingCollection
|
|
146
|
-
deviceUrl: string
|
|
144
|
+
message: HashTreeMessage | undefined,
|
|
145
|
+
meetingCollection: MeetingCollection
|
|
147
146
|
): any {
|
|
147
|
+
if (!message) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
148
150
|
let foundMeeting = findLocusUrlInAnyHashTreeParser(meetingCollection, message.locusUrl);
|
|
149
151
|
|
|
150
152
|
if (foundMeeting) {
|
|
@@ -154,7 +156,7 @@ export function findMeetingForHashTreeMessage(
|
|
|
154
156
|
// if we haven't found anything, it may mean that message has a new locusUrl
|
|
155
157
|
// check if it indicates that it replaces some existing current locusUrl (this is indicated in "self")
|
|
156
158
|
const self = message.locusStateElements?.find((el) => isSelf(el))?.data;
|
|
157
|
-
const replaces = getReplaceInfoFromSelf(self, deviceUrl);
|
|
159
|
+
const replaces = getReplaceInfoFromSelf(self, self?.deviceUrl);
|
|
158
160
|
|
|
159
161
|
if (replaces?.locusUrl) {
|
|
160
162
|
foundMeeting = findLocusUrlInAnyHashTreeParser(meetingCollection, replaces.locusUrl);
|
|
@@ -545,7 +547,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
545
547
|
dataSets: Array<DataSet>;
|
|
546
548
|
locus: any;
|
|
547
549
|
};
|
|
548
|
-
metadata: Metadata;
|
|
550
|
+
metadata: Metadata | null;
|
|
549
551
|
replacedAt?: string;
|
|
550
552
|
}): HashTreeParser {
|
|
551
553
|
const parser = new HashTreeParser({
|
|
@@ -553,7 +555,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
553
555
|
metadata,
|
|
554
556
|
webexRequest: this.webex.request.bind(this.webex),
|
|
555
557
|
locusInfoUpdateCallback: this.updateFromHashTree.bind(this, locusUrl),
|
|
556
|
-
debugId: `HT-${locusUrl.split('/')
|
|
558
|
+
debugId: `HT-${locusUrl.split('/')?.pop()?.substring(0, 4)}`,
|
|
557
559
|
excludedDataSets: this.webex.config.meetings.locus?.excludedDataSets,
|
|
558
560
|
});
|
|
559
561
|
|
|
@@ -656,7 +658,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
656
658
|
);
|
|
657
659
|
// first create the HashTreeParser, but don't initialize it with any data yet
|
|
658
660
|
const hashTreeParser = this.createHashTreeParser({
|
|
659
|
-
locusUrl: data.locus.url,
|
|
661
|
+
locusUrl: data.locus.url as string,
|
|
660
662
|
initialLocus: {
|
|
661
663
|
locus: null,
|
|
662
664
|
dataSets: [], // empty, because we don't have them yet
|
|
@@ -682,11 +684,31 @@ export default class LocusInfo extends EventsScope {
|
|
|
682
684
|
* @param {LocusApiResponseBody} responseBody body of the http response from Locus API call
|
|
683
685
|
* @returns {void}
|
|
684
686
|
*/
|
|
685
|
-
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
|
|
687
|
+
handleLocusAPIResponse(meeting: any, responseBody: LocusApiResponseBody): void {
|
|
686
688
|
const isWrapped = 'locus' in responseBody;
|
|
687
689
|
const locusUrl = isWrapped ? responseBody.locus?.url : responseBody.url;
|
|
688
690
|
const hashTreeParserEntry = locusUrl && this.hashTreeParsers.get(locusUrl);
|
|
689
|
-
|
|
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
|
|
690
712
|
if (isWrapped) {
|
|
691
713
|
if (!responseBody.dataSets) {
|
|
692
714
|
this.sendClassicVsHashTreeMismatchMetric(
|
|
@@ -702,20 +724,23 @@ export default class LocusInfo extends EventsScope {
|
|
|
702
724
|
// update the data in our hash trees
|
|
703
725
|
hashTreeParserEntry.parser.handleLocusUpdate(responseBody);
|
|
704
726
|
} else {
|
|
705
|
-
// 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
|
|
706
728
|
hashTreeParserEntry.parser.handleLocusUpdate({locus: responseBody});
|
|
707
729
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
this.sendClassicVsHashTreeMismatchMetric(
|
|
711
|
-
meeting,
|
|
712
|
-
`unexpected hash tree dataSets in API response`
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
// classic Locus delta
|
|
716
|
-
const locus = isWrapped ? responseBody.locus : responseBody;
|
|
717
|
-
this.handleLocusDelta(locus, meeting);
|
|
730
|
+
|
|
731
|
+
return;
|
|
718
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
|
+
);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// classic Locus delta
|
|
743
|
+
this.handleLocusDelta(locus, meeting);
|
|
719
744
|
}
|
|
720
745
|
|
|
721
746
|
/**
|
|
@@ -944,6 +969,114 @@ export default class LocusInfo extends EventsScope {
|
|
|
944
969
|
}
|
|
945
970
|
}
|
|
946
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
|
+
|
|
947
1080
|
/**
|
|
948
1081
|
* Checks if the hash tree message should trigger a switch to a different HashTreeParser
|
|
949
1082
|
*
|
|
@@ -965,7 +1098,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
965
1098
|
// but it's buried inside the message, we need to find it and pass it to HashTreeParser constructor
|
|
966
1099
|
const metadata = message.locusStateElements?.find((el) => isMetadata(el));
|
|
967
1100
|
|
|
968
|
-
if (metadata
|
|
1101
|
+
if (metadata && metadata.data?.visibleDataSets?.length > 0) {
|
|
969
1102
|
LoggerProxy.logger.info(
|
|
970
1103
|
`Locus-info:index#handleHashTreeParserSwitch --> no hash tree parser found for locusUrl ${message.locusUrl}, creating a new one`
|
|
971
1104
|
);
|
|
@@ -992,36 +1125,12 @@ export default class LocusInfo extends EventsScope {
|
|
|
992
1125
|
if (entry.parser.state === 'stopped') {
|
|
993
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
|
|
994
1127
|
// this happens when you move from breakout A -> breakout B -> back to breakout A
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
if (replacedEntry) {
|
|
1003
|
-
replacedEntry.replacedAt = replaces.replacedAt;
|
|
1004
|
-
entry.initializedFromHashTree = false;
|
|
1005
|
-
this.hashTreeObjectId2ParticipantId.clear();
|
|
1006
|
-
|
|
1007
|
-
replacedEntry.parser.stop();
|
|
1008
|
-
entry.parser.resume(message);
|
|
1009
|
-
} else {
|
|
1010
|
-
LoggerProxy.logger.warn(
|
|
1011
|
-
`Locus-info:index#handleHashTreeParserSwitch --> the parser that is supposed to be replaced with the currently resumed parser is not found, locusUrl=${replaces.locusUrl}`
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
} else {
|
|
1015
|
-
LoggerProxy.logger.info(
|
|
1016
|
-
`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`
|
|
1017
|
-
);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
return true;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
LoggerProxy.logger.info(
|
|
1024
|
-
`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)
|
|
1025
1134
|
);
|
|
1026
1135
|
|
|
1027
1136
|
return true;
|
|
@@ -1056,7 +1165,25 @@ export default class LocusInfo extends EventsScope {
|
|
|
1056
1165
|
|
|
1057
1166
|
const entry = this.hashTreeParsers.get(message.locusUrl);
|
|
1058
1167
|
|
|
1059
|
-
entry
|
|
1168
|
+
// the check is just for typescript, the case of no entry in hashTreeParsers is handled in handleHashTreeParserSwitch() above
|
|
1169
|
+
if (entry) {
|
|
1170
|
+
entry.parser.handleMessage(message);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
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
|
+
}
|
|
1060
1187
|
}
|
|
1061
1188
|
|
|
1062
1189
|
/**
|
|
@@ -1064,16 +1191,11 @@ export default class LocusInfo extends EventsScope {
|
|
|
1064
1191
|
* Updates our locus info based on the data parsed by the hash tree parser.
|
|
1065
1192
|
*
|
|
1066
1193
|
* @param {string} locusUrl - the locus URL for which the update is received
|
|
1067
|
-
* @param {
|
|
1068
|
-
* @param {Object} [data] - Additional data for the update, if applicable.
|
|
1194
|
+
* @param {LocusInfoUpdate} update - Details about the update.
|
|
1069
1195
|
* @returns {void}
|
|
1070
1196
|
*/
|
|
1071
|
-
private updateFromHashTree(
|
|
1072
|
-
|
|
1073
|
-
updateType: LocusInfoUpdateType,
|
|
1074
|
-
data?: {updatedObjects: HashTreeObject[]}
|
|
1075
|
-
) {
|
|
1076
|
-
switch (updateType) {
|
|
1197
|
+
private updateFromHashTree(locusUrl: string, update: LocusInfoUpdate) {
|
|
1198
|
+
switch (update.updateType) {
|
|
1077
1199
|
case LocusInfoUpdateType.OBJECTS_UPDATED: {
|
|
1078
1200
|
// initialize our new locus
|
|
1079
1201
|
let locus: LocusDTO = {
|
|
@@ -1087,7 +1209,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1087
1209
|
// first go over all the updates and check what happens with the main locus object
|
|
1088
1210
|
let locusObjectStateAfterUpdates: LocusObjectStateAfterUpdates =
|
|
1089
1211
|
LocusObjectStateAfterUpdates.unchanged;
|
|
1090
|
-
|
|
1212
|
+
update.updatedObjects.forEach((object) => {
|
|
1091
1213
|
if (object.htMeta.elementId.type.toLowerCase() === ObjectType.locus) {
|
|
1092
1214
|
if (locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.updated) {
|
|
1093
1215
|
// this code doesn't supported it right now,
|
|
@@ -1116,6 +1238,14 @@ export default class LocusInfo extends EventsScope {
|
|
|
1116
1238
|
|
|
1117
1239
|
const hashTreeParserEntry = this.hashTreeParsers.get(locusUrl);
|
|
1118
1240
|
|
|
1241
|
+
if (!hashTreeParserEntry) {
|
|
1242
|
+
LoggerProxy.logger.warn(
|
|
1243
|
+
`Locus-info:index#updateFromHashTree --> no HashTreeParser found for locusUrl ${locusUrl} when trying to apply updates from hash tree`
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1119
1249
|
if (!hashTreeParserEntry.initializedFromHashTree) {
|
|
1120
1250
|
// this is the first time we're getting an update for this locusUrl,
|
|
1121
1251
|
// so it's probably a move to/from breakout. We need to start from a clean state,
|
|
@@ -1124,7 +1254,8 @@ export default class LocusInfo extends EventsScope {
|
|
|
1124
1254
|
`Locus-info:index#updateFromHashTree --> first INITIAL update for locusUrl ${locusUrl}, starting from empty state`
|
|
1125
1255
|
);
|
|
1126
1256
|
hashTreeParserEntry.initializedFromHashTree = true;
|
|
1127
|
-
|
|
1257
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1258
|
+
locus.jsSdkMeta!.forceReplaceMembers = true;
|
|
1128
1259
|
} else if (
|
|
1129
1260
|
// if Locus object is unchanged or removed, we need to keep using the existing locus
|
|
1130
1261
|
// because the rest of the locusInfo code expects locus to always be present (with at least some of the fields)
|
|
@@ -1137,7 +1268,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1137
1268
|
// copy over all of existing locus except participants
|
|
1138
1269
|
LocusDtoTopLevelKeys.forEach((key) => {
|
|
1139
1270
|
if (key !== 'participants') {
|
|
1140
|
-
locus[key] = cloneDeep(this[key]);
|
|
1271
|
+
(locus as Record<string, any>)[key] = cloneDeep((this as Record<string, any>)[key]);
|
|
1141
1272
|
}
|
|
1142
1273
|
});
|
|
1143
1274
|
} else {
|
|
@@ -1145,14 +1276,16 @@ export default class LocusInfo extends EventsScope {
|
|
|
1145
1276
|
// (except participants, which need to stay empty - that means "no participant changes")
|
|
1146
1277
|
Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
|
|
1147
1278
|
if (locusDtoKey !== 'participants') {
|
|
1148
|
-
locus[locusDtoKey] = cloneDeep(
|
|
1279
|
+
(locus as Record<string, any>)[locusDtoKey] = cloneDeep(
|
|
1280
|
+
(this as Record<string, any>)[locusDtoKey]
|
|
1281
|
+
);
|
|
1149
1282
|
}
|
|
1150
1283
|
});
|
|
1151
1284
|
}
|
|
1152
1285
|
|
|
1153
1286
|
LoggerProxy.logger.info(
|
|
1154
1287
|
`Locus-info:index#updateFromHashTree --> LOCUS object is ${locusObjectStateAfterUpdates}, all updates: ${JSON.stringify(
|
|
1155
|
-
|
|
1288
|
+
update.updatedObjects.map((o) => ({
|
|
1156
1289
|
type: o.htMeta.elementId.type,
|
|
1157
1290
|
id: o.htMeta.elementId.id,
|
|
1158
1291
|
hasData: !!o.data,
|
|
@@ -1160,7 +1293,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1160
1293
|
)}`
|
|
1161
1294
|
);
|
|
1162
1295
|
// now apply all the updates from the hash tree onto the locus
|
|
1163
|
-
|
|
1296
|
+
update.updatedObjects.forEach((object) => {
|
|
1164
1297
|
locus = this.updateLocusFromHashTreeObject(object, locus);
|
|
1165
1298
|
});
|
|
1166
1299
|
|
|
@@ -1191,11 +1324,17 @@ export default class LocusInfo extends EventsScope {
|
|
|
1191
1324
|
*/
|
|
1192
1325
|
parse(meeting: any, data: any) {
|
|
1193
1326
|
if (this.hashTreeParsers.size > 0) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
data.
|
|
1198
|
-
|
|
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
|
+
}
|
|
1199
1338
|
} else {
|
|
1200
1339
|
const {eventType} = data;
|
|
1201
1340
|
|
|
@@ -1260,16 +1399,16 @@ export default class LocusInfo extends EventsScope {
|
|
|
1260
1399
|
* @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
|
|
1261
1400
|
* @param {object} locus locus object
|
|
1262
1401
|
* @param {object} metadata locus hash trees metadata
|
|
1263
|
-
* @param {string} eventType locus event
|
|
1264
1402
|
* @param {DataSet[]} dataSets
|
|
1403
|
+
* @param {string} eventType locus event
|
|
1265
1404
|
* @returns {void}
|
|
1266
1405
|
*/
|
|
1267
1406
|
private onFullLocusWithHashTrees(
|
|
1268
1407
|
debugText: string,
|
|
1269
1408
|
locus: any,
|
|
1270
1409
|
metadata: Metadata,
|
|
1271
|
-
|
|
1272
|
-
|
|
1410
|
+
dataSets: Array<DataSet>,
|
|
1411
|
+
eventType?: string
|
|
1273
1412
|
) {
|
|
1274
1413
|
if (!this.hashTreeParsers.has(locus.url)) {
|
|
1275
1414
|
LoggerProxy.logger.info(
|
|
@@ -1289,7 +1428,8 @@ export default class LocusInfo extends EventsScope {
|
|
|
1289
1428
|
metadata,
|
|
1290
1429
|
});
|
|
1291
1430
|
// we have a full locus to start with, so we consider Locus info to be "initialized"
|
|
1292
|
-
|
|
1431
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1432
|
+
this.hashTreeParsers.get(locus.url)!.initializedFromHashTree = true;
|
|
1293
1433
|
this.onFullLocusCommon(locus, eventType);
|
|
1294
1434
|
} else {
|
|
1295
1435
|
// in this case the Locus we're getting is not necessarily the full one
|
|
@@ -1351,7 +1491,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1351
1491
|
);
|
|
1352
1492
|
}
|
|
1353
1493
|
// this is the new hashmap Locus DTO format (only applicable to webinars for now)
|
|
1354
|
-
this.onFullLocusWithHashTrees(debugText, locus, metadata,
|
|
1494
|
+
this.onFullLocusWithHashTrees(debugText, locus, metadata, dataSets, eventType);
|
|
1355
1495
|
} else {
|
|
1356
1496
|
this.onFullLocusClassic(debugText, locus, eventType);
|
|
1357
1497
|
}
|
|
@@ -1495,7 +1635,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1495
1635
|
* @memberof LocusInfo
|
|
1496
1636
|
*/
|
|
1497
1637
|
updateLocusInfo(locus) {
|
|
1498
|
-
if (
|
|
1638
|
+
if (MeetingsUtil.isSelfMovedOrBreakoutEnded(locus)) {
|
|
1499
1639
|
// When moved to a breakout session locus sends a message for the previous locus
|
|
1500
1640
|
// indicating that we have been moved. It isn't helpful to continue parsing this
|
|
1501
1641
|
// as it gets interpreted as if we have left the call
|
|
@@ -2587,6 +2727,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
2587
2727
|
{
|
|
2588
2728
|
muted: parsedSelves.current.remoteMuted,
|
|
2589
2729
|
unmuteAllowed: parsedSelves.current.unmuteAllowed,
|
|
2730
|
+
modifiedBy: parsedSelves.current.modifiedBy ?? null,
|
|
2590
2731
|
}
|
|
2591
2732
|
);
|
|
2592
2733
|
}
|
|
@@ -2859,4 +3000,17 @@ export default class LocusInfo extends EventsScope {
|
|
|
2859
3000
|
clearMainSessionLocusCache() {
|
|
2860
3001
|
this.mainSessionLocusCache = null;
|
|
2861
3002
|
}
|
|
3003
|
+
|
|
3004
|
+
/**
|
|
3005
|
+
* Cleans up all hash tree parsers and clears internal maps.
|
|
3006
|
+
* @returns {void}
|
|
3007
|
+
* @memberof LocusInfo
|
|
3008
|
+
*/
|
|
3009
|
+
cleanUp() {
|
|
3010
|
+
this.hashTreeParsers.forEach((entry) => {
|
|
3011
|
+
entry.parser.cleanUp();
|
|
3012
|
+
});
|
|
3013
|
+
this.hashTreeParsers.clear();
|
|
3014
|
+
this.hashTreeObjectId2ParticipantId.clear();
|
|
3015
|
+
}
|
|
2862
3016
|
}
|