@webex/plugin-meetings 3.10.0-next.14 → 3.10.0-next.16
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/constants.js +20 -0
- package/dist/hashTree/constants.js.map +1 -0
- package/dist/hashTree/hashTree.js +515 -0
- package/dist/hashTree/hashTree.js.map +1 -0
- package/dist/hashTree/hashTreeParser.js +1115 -14
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +10 -3
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +48 -0
- package/dist/hashTree/utils.js.map +1 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +59 -49
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/types/hashTree/constants.d.ts +8 -0
- package/dist/types/hashTree/hashTree.d.ts +129 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +151 -0
- package/dist/types/hashTree/types.d.ts +11 -0
- package/dist/types/hashTree/utils.d.ts +9 -0
- package/dist/types/locus-info/index.d.ts +5 -15
- package/dist/types/locus-info/types.d.ts +21 -12
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -21
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTree.ts +463 -0
- package/src/hashTree/hashTreeParser.ts +1022 -7
- package/src/hashTree/types.ts +13 -1
- package/src/hashTree/utils.ts +42 -0
- package/src/locus-info/index.ts +64 -48
- package/src/locus-info/types.ts +19 -12
- package/test/unit/spec/hashTree/hashTree.ts +655 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1532 -0
- package/test/unit/spec/hashTree/utils.ts +103 -0
- package/test/unit/spec/locus-info/index.js +150 -7
package/src/hashTree/types.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import {Enum} from '../constants';
|
|
2
2
|
|
|
3
|
-
// todo: Locus docs have now more types like CONTROL_ENTRY, EMBEDDED_APP
|
|
3
|
+
// todo: Locus docs have now more types like CONTROL_ENTRY, EMBEDDED_APP - need to add support for them once Locus implements them
|
|
4
4
|
export const ObjectType = {
|
|
5
5
|
participant: 'participant',
|
|
6
6
|
self: 'self',
|
|
7
7
|
locus: 'locus',
|
|
8
8
|
mediaShare: 'mediashare',
|
|
9
|
+
info: 'info',
|
|
10
|
+
fullState: 'fullstate',
|
|
11
|
+
links: 'links',
|
|
9
12
|
} as const;
|
|
10
13
|
|
|
11
14
|
export type ObjectType = Enum<typeof ObjectType>;
|
|
12
15
|
|
|
16
|
+
// mapping from ObjectType to top level LocusDTO keys
|
|
17
|
+
export const ObjectTypeToLocusKeyMap = {
|
|
18
|
+
[ObjectType.links]: 'links',
|
|
19
|
+
[ObjectType.info]: 'info',
|
|
20
|
+
[ObjectType.fullState]: 'fullState',
|
|
21
|
+
[ObjectType.self]: 'self',
|
|
22
|
+
[ObjectType.participant]: 'participants', // note: each object is a single participant in participants array
|
|
23
|
+
[ObjectType.mediaShare]: 'mediaShares', // note: each object is a single mediaShare in mediaShares array
|
|
24
|
+
};
|
|
13
25
|
export interface HtMeta {
|
|
14
26
|
elementId: {
|
|
15
27
|
type: ObjectType;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Analyzes given part of Locus DTO recursively and delete any nested objects that have their own htMeta
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} currentLocusPart part of locus DTO to analyze
|
|
7
|
+
* @param {Object} parent parent object
|
|
8
|
+
* @param {string|number} currentKey key of the parent object that currentLocusPart is
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
11
|
+
export const deleteNestedObjectsWithHtMeta = (
|
|
12
|
+
currentLocusPart: any,
|
|
13
|
+
parent?: any,
|
|
14
|
+
currentKey?: string | number
|
|
15
|
+
) => {
|
|
16
|
+
if (typeof currentLocusPart !== 'object' || currentLocusPart === null) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (parent && currentKey !== undefined && currentLocusPart.htMeta) {
|
|
21
|
+
if (Array.isArray(parent)) {
|
|
22
|
+
parent.splice(Number(currentKey), 1);
|
|
23
|
+
} else {
|
|
24
|
+
delete parent[currentKey];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (Array.isArray(currentLocusPart)) {
|
|
31
|
+
// iterate array in reverse, so that indexes remain valid when deleting elements
|
|
32
|
+
for (let i = currentLocusPart.length - 1; i >= 0; i -= 1) {
|
|
33
|
+
deleteNestedObjectsWithHtMeta(currentLocusPart[i], currentLocusPart, i);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
for (const key of Object.keys(currentLocusPart)) {
|
|
37
|
+
if (Object.prototype.hasOwnProperty.call(currentLocusPart, key)) {
|
|
38
|
+
deleteNestedObjectsWithHtMeta(currentLocusPart[key], currentLocusPart, key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
package/src/locus-info/index.ts
CHANGED
|
@@ -37,8 +37,8 @@ import HashTreeParser, {
|
|
|
37
37
|
isSelf,
|
|
38
38
|
LocusInfoUpdateType,
|
|
39
39
|
} from '../hashTree/hashTreeParser';
|
|
40
|
-
import {ObjectType} from '../hashTree/types';
|
|
41
|
-
import {LocusDTO} from './types';
|
|
40
|
+
import {ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
|
|
41
|
+
import {Links, LocusDTO, LocusFullState} from './types';
|
|
42
42
|
|
|
43
43
|
export type LocusLLMEvent = {
|
|
44
44
|
data: {
|
|
@@ -47,6 +47,8 @@ export type LocusLLMEvent = {
|
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
// list of top level keys in Locus DTO relevant for Hash Tree DTOs processing
|
|
51
|
+
// it does not contain fields specific to classic Locus DTOs like sequence or baseSequence
|
|
50
52
|
const LocusDtoTopLevelKeys = [
|
|
51
53
|
'controls',
|
|
52
54
|
'fullState',
|
|
@@ -111,8 +113,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
111
113
|
mediaShares: any;
|
|
112
114
|
replace: any;
|
|
113
115
|
url: any;
|
|
114
|
-
|
|
115
|
-
resources: any;
|
|
116
|
+
links?: Links;
|
|
116
117
|
mainSessionLocusCache: any;
|
|
117
118
|
self: any;
|
|
118
119
|
hashTreeParser?: HashTreeParser;
|
|
@@ -358,8 +359,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
358
359
|
this.updateSelf(locus.self);
|
|
359
360
|
this.updateHostInfo(locus.host);
|
|
360
361
|
this.updateMediaShares(locus.mediaShares);
|
|
361
|
-
this.
|
|
362
|
-
this.updateResources(locus.links?.resources);
|
|
362
|
+
this.updateLinks(locus.links);
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
/**
|
|
@@ -478,12 +478,19 @@ export default class LocusInfo extends EventsScope {
|
|
|
478
478
|
*/
|
|
479
479
|
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
|
|
480
480
|
if (this.hashTreeParser) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
481
|
+
if (!responseBody.dataSets) {
|
|
482
|
+
this.sendClassicVsHashTreeMismatchMetric(
|
|
483
|
+
meeting,
|
|
484
|
+
`expected hash tree dataSets in API response but they are missing`
|
|
485
|
+
);
|
|
486
|
+
// continuing as we can still manage without responseBody.dataSets, but this is very suspicious
|
|
487
|
+
}
|
|
484
488
|
LoggerProxy.logger.info(
|
|
485
|
-
'Locus-info:index#handleLocusAPIResponse
|
|
489
|
+
'Locus-info:index#handleLocusAPIResponse --> passing Locus API response to HashTreeParser: ',
|
|
490
|
+
responseBody
|
|
486
491
|
);
|
|
492
|
+
// update the data in our hash trees
|
|
493
|
+
this.hashTreeParser.handleLocusUpdate(responseBody);
|
|
487
494
|
} else {
|
|
488
495
|
if (responseBody.dataSets) {
|
|
489
496
|
this.sendClassicVsHashTreeMismatchMetric(
|
|
@@ -511,23 +518,25 @@ export default class LocusInfo extends EventsScope {
|
|
|
511
518
|
// not doing anything here, as we need Locus to always be there (at least some fields)
|
|
512
519
|
// and that's already taken care of in updateFromHashTree()
|
|
513
520
|
LoggerProxy.logger.info(
|
|
514
|
-
`Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object removed`
|
|
521
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object removed, version=${object.htMeta.elementId.version}`
|
|
515
522
|
);
|
|
516
523
|
|
|
517
524
|
return locus;
|
|
518
525
|
}
|
|
519
526
|
// replace the main locus
|
|
520
527
|
|
|
521
|
-
// The Locus object we receive from backend has empty participants,
|
|
522
|
-
//
|
|
523
|
-
//
|
|
528
|
+
// The Locus object we receive from backend has empty participants array,
|
|
529
|
+
// and may have (although it shouldn't) other fields that are managed by other ObjectTypes
|
|
530
|
+
// like "fullState" or "info", so we're making sure to delete them here
|
|
524
531
|
const locusObjectFromData = object.data;
|
|
525
|
-
|
|
526
|
-
|
|
532
|
+
|
|
533
|
+
Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
|
|
534
|
+
delete locusObjectFromData[locusDtoKey];
|
|
535
|
+
});
|
|
527
536
|
|
|
528
537
|
locus = {...locus, ...locusObjectFromData};
|
|
529
538
|
LoggerProxy.logger.info(
|
|
530
|
-
`Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object updated`
|
|
539
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object updated to version=${object.htMeta.elementId.version}`
|
|
531
540
|
);
|
|
532
541
|
break;
|
|
533
542
|
}
|
|
@@ -540,7 +549,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
540
549
|
object.data.name === 'content'
|
|
541
550
|
? `floor=${object.data.floor?.disposition}, ${object.data.floor?.beneficiary?.id}`
|
|
542
551
|
: ''
|
|
543
|
-
}`
|
|
552
|
+
} version=${object.htMeta.elementId.version}`
|
|
544
553
|
);
|
|
545
554
|
const existingMediaShare = locus.mediaShares?.find(
|
|
546
555
|
(ms) => ms.htMeta.elementId.id === object.htMeta.elementId.id
|
|
@@ -554,7 +563,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
554
563
|
}
|
|
555
564
|
} else {
|
|
556
565
|
LoggerProxy.logger.info(
|
|
557
|
-
`Locus-info:index#updateLocusFromHashTreeObject --> mediaShare id=${object.htMeta.elementId.id} removed`
|
|
566
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> mediaShare id=${object.htMeta.elementId.id} removed, version=${object.htMeta.elementId.version}`
|
|
558
567
|
);
|
|
559
568
|
locus.mediaShares = locus.mediaShares?.filter(
|
|
560
569
|
(ms) => ms.htMeta.elementId.id !== object.htMeta.elementId.id
|
|
@@ -565,7 +574,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
565
574
|
LoggerProxy.logger.info(
|
|
566
575
|
`Locus-info:index#updateLocusFromHashTreeObject --> participant id=${
|
|
567
576
|
object.htMeta.elementId.id
|
|
568
|
-
} ${object.data ? 'updated' : 'removed'}`
|
|
577
|
+
} ${object.data ? 'updated' : 'removed'} version=${object.htMeta.elementId.version}`
|
|
569
578
|
);
|
|
570
579
|
if (object.data) {
|
|
571
580
|
if (!locus.participants) {
|
|
@@ -585,19 +594,23 @@ export default class LocusInfo extends EventsScope {
|
|
|
585
594
|
this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
|
|
586
595
|
}
|
|
587
596
|
break;
|
|
597
|
+
case ObjectType.links:
|
|
598
|
+
case ObjectType.info:
|
|
599
|
+
case ObjectType.fullState:
|
|
588
600
|
case ObjectType.self:
|
|
589
601
|
if (!object.data) {
|
|
590
602
|
// self without data is handled inside HashTreeParser and results in LocusInfoUpdateType.MEETING_ENDED, so we should never get here
|
|
603
|
+
// all other types info, fullstate, etc - Locus should never send them without data
|
|
591
604
|
LoggerProxy.logger.warn(
|
|
592
|
-
`Locus-info:index#updateLocusFromHashTreeObject --> received
|
|
605
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> received ${type} object without data, this is not expected! version=${object.htMeta.elementId.version}`
|
|
593
606
|
);
|
|
594
|
-
|
|
595
|
-
|
|
607
|
+
} else {
|
|
608
|
+
LoggerProxy.logger.info(
|
|
609
|
+
`Locus-info:index#updateLocusFromHashTreeObject --> ${type} object updated to version ${object.htMeta.elementId.version}`
|
|
610
|
+
);
|
|
611
|
+
const locusDtoKey = ObjectTypeToLocusKeyMap[type];
|
|
612
|
+
locus[locusDtoKey] = object.data;
|
|
596
613
|
}
|
|
597
|
-
LoggerProxy.logger.info(
|
|
598
|
-
`Locus-info:index#updateLocusFromHashTreeObject --> SELF object updated`
|
|
599
|
-
);
|
|
600
|
-
locus.self = object.data;
|
|
601
614
|
break;
|
|
602
615
|
default:
|
|
603
616
|
LoggerProxy.logger.warn(
|
|
@@ -704,19 +717,27 @@ export default class LocusInfo extends EventsScope {
|
|
|
704
717
|
|
|
705
718
|
// if Locus object is unchanged or removed, we need to keep using the existing locus
|
|
706
719
|
// because the rest of the locusInfo code expects locus to always be present (with at least some of the fields)
|
|
707
|
-
// if it gets updated, we
|
|
708
|
-
// so that when
|
|
720
|
+
// if it gets updated, we only need to have the fields that are not part of "locus" object (like "info" or "mediaShares")
|
|
721
|
+
// so that when Locus object gets updated, if the new one is missing some field, that field will
|
|
709
722
|
// be removed from our locusInfo
|
|
710
723
|
if (
|
|
711
724
|
locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.unchanged ||
|
|
712
725
|
locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.removed
|
|
713
726
|
) {
|
|
714
|
-
// copy over existing locus
|
|
727
|
+
// copy over all of existing locus except participants
|
|
715
728
|
LocusDtoTopLevelKeys.forEach((key) => {
|
|
716
729
|
if (key !== 'participants') {
|
|
717
730
|
locus[key] = cloneDeep(this[key]);
|
|
718
731
|
}
|
|
719
732
|
});
|
|
733
|
+
} else {
|
|
734
|
+
// initialize only the fields that are not part of main "Locus" object
|
|
735
|
+
// (except participants, which need to stay empty - that means "no participant changes")
|
|
736
|
+
Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
|
|
737
|
+
if (locusDtoKey !== 'participants') {
|
|
738
|
+
locus[locusDtoKey] = cloneDeep(this[locusDtoKey]);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
720
741
|
}
|
|
721
742
|
|
|
722
743
|
LoggerProxy.logger.info(
|
|
@@ -724,7 +745,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
724
745
|
data.updatedObjects.map((o) => ({
|
|
725
746
|
type: o.htMeta.elementId.type,
|
|
726
747
|
id: o.htMeta.elementId.id,
|
|
727
|
-
hasData: o.data
|
|
748
|
+
hasData: !!o.data,
|
|
728
749
|
}))
|
|
729
750
|
)}`
|
|
730
751
|
);
|
|
@@ -1010,8 +1031,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1010
1031
|
this.updateMemberShip(locus.membership);
|
|
1011
1032
|
this.updateIdentifiers(locus.identities);
|
|
1012
1033
|
this.updateEmbeddedApps(locus.embeddedApps);
|
|
1013
|
-
this.
|
|
1014
|
-
this.updateResources(locus.links?.resources);
|
|
1034
|
+
this.updateLinks(locus.links);
|
|
1015
1035
|
this.compareAndUpdate();
|
|
1016
1036
|
// update which required to compare different objects from locus
|
|
1017
1037
|
}
|
|
@@ -1663,17 +1683,19 @@ export default class LocusInfo extends EventsScope {
|
|
|
1663
1683
|
}
|
|
1664
1684
|
|
|
1665
1685
|
/**
|
|
1666
|
-
*
|
|
1686
|
+
* Updates links and emits appropriate events if services or resources have changed
|
|
1687
|
+
* @param {Object} links
|
|
1667
1688
|
* @returns {undefined}
|
|
1668
1689
|
* @memberof LocusInfo
|
|
1669
1690
|
*/
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1691
|
+
updateLinks(links?: Links) {
|
|
1692
|
+
const {services, resources} = links || {};
|
|
1693
|
+
|
|
1694
|
+
if (services && !isEqual(this.links?.services, services)) {
|
|
1673
1695
|
this.emitScoped(
|
|
1674
1696
|
{
|
|
1675
1697
|
file: 'locus-info',
|
|
1676
|
-
function: '
|
|
1698
|
+
function: 'updateLinks',
|
|
1677
1699
|
},
|
|
1678
1700
|
LOCUSINFO.EVENTS.LINKS_SERVICES,
|
|
1679
1701
|
{
|
|
@@ -1681,20 +1703,12 @@ export default class LocusInfo extends EventsScope {
|
|
|
1681
1703
|
}
|
|
1682
1704
|
);
|
|
1683
1705
|
}
|
|
1684
|
-
}
|
|
1685
1706
|
|
|
1686
|
-
|
|
1687
|
-
* @param {Object} resources
|
|
1688
|
-
* @returns {undefined}
|
|
1689
|
-
* @memberof LocusInfo
|
|
1690
|
-
*/
|
|
1691
|
-
updateResources(resources: Record<'webcastInstance', {url: string}>) {
|
|
1692
|
-
if (resources && !isEqual(this.resources, resources)) {
|
|
1693
|
-
this.resources = resources;
|
|
1707
|
+
if (resources && !isEqual(this.links?.resources, resources)) {
|
|
1694
1708
|
this.emitScoped(
|
|
1695
1709
|
{
|
|
1696
1710
|
file: 'locus-info',
|
|
1697
|
-
function: '
|
|
1711
|
+
function: 'updateLinks',
|
|
1698
1712
|
},
|
|
1699
1713
|
LOCUSINFO.EVENTS.LINKS_RESOURCES,
|
|
1700
1714
|
{
|
|
@@ -1702,6 +1716,8 @@ export default class LocusInfo extends EventsScope {
|
|
|
1702
1716
|
}
|
|
1703
1717
|
);
|
|
1704
1718
|
}
|
|
1719
|
+
|
|
1720
|
+
this.links = links;
|
|
1705
1721
|
}
|
|
1706
1722
|
|
|
1707
1723
|
/**
|
package/src/locus-info/types.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import {HtMeta} from '../hashTree/types';
|
|
2
2
|
|
|
3
|
+
export type LocusFullState = {
|
|
4
|
+
active: boolean;
|
|
5
|
+
count: number;
|
|
6
|
+
lastActive: string;
|
|
7
|
+
locked: boolean;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
seessionIds: string[];
|
|
10
|
+
startTime: number;
|
|
11
|
+
state: string;
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type Links = {
|
|
16
|
+
services: Record<'breakout' | 'record', {url: string}>; // there exist also other services, but these are the ones we currently use
|
|
17
|
+
resources: Record<'webcastInstance' | 'visibleDataSets', {url: string}>; // there exist also other resources, but these are the ones we currently use
|
|
18
|
+
};
|
|
19
|
+
|
|
3
20
|
export type LocusDTO = {
|
|
4
21
|
controls?: any;
|
|
5
|
-
fullState?:
|
|
6
|
-
active: boolean;
|
|
7
|
-
count: number;
|
|
8
|
-
lastActive: string;
|
|
9
|
-
locked: boolean;
|
|
10
|
-
sessionId: string;
|
|
11
|
-
seessionIds: string[];
|
|
12
|
-
startTime: number;
|
|
13
|
-
state: string;
|
|
14
|
-
type: string;
|
|
15
|
-
};
|
|
22
|
+
fullState?: LocusFullState;
|
|
16
23
|
host?: {
|
|
17
24
|
id: string;
|
|
18
25
|
incomingCallProtocols: any[];
|
|
@@ -25,7 +32,7 @@ export type LocusDTO = {
|
|
|
25
32
|
jsSdkMeta?: {
|
|
26
33
|
removedParticipantIds: string[]; // list of ids of participants that are removed in the last update
|
|
27
34
|
};
|
|
28
|
-
links?:
|
|
35
|
+
links?: Links;
|
|
29
36
|
mediaShares?: any[];
|
|
30
37
|
meetings?: any[];
|
|
31
38
|
participants: any[];
|