@webex/plugin-meetings 3.10.0-next.14 → 3.10.0-next.15

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 (36) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/hashTree/constants.js +20 -0
  4. package/dist/hashTree/constants.js.map +1 -0
  5. package/dist/hashTree/hashTree.js +515 -0
  6. package/dist/hashTree/hashTree.js.map +1 -0
  7. package/dist/hashTree/hashTreeParser.js +1115 -14
  8. package/dist/hashTree/hashTreeParser.js.map +1 -1
  9. package/dist/hashTree/types.js +9 -3
  10. package/dist/hashTree/types.js.map +1 -1
  11. package/dist/hashTree/utils.js +48 -0
  12. package/dist/hashTree/utils.js.map +1 -0
  13. package/dist/interpretation/index.js +1 -1
  14. package/dist/interpretation/siLanguage.js +1 -1
  15. package/dist/locus-info/index.js +41 -22
  16. package/dist/locus-info/index.js.map +1 -1
  17. package/dist/locus-info/types.js.map +1 -1
  18. package/dist/types/hashTree/constants.d.ts +8 -0
  19. package/dist/types/hashTree/hashTree.d.ts +129 -0
  20. package/dist/types/hashTree/hashTreeParser.d.ts +151 -0
  21. package/dist/types/hashTree/types.d.ts +9 -0
  22. package/dist/types/hashTree/utils.d.ts +9 -0
  23. package/dist/types/locus-info/types.d.ts +12 -11
  24. package/dist/webinar/index.js +1 -1
  25. package/package.json +22 -21
  26. package/src/hashTree/constants.ts +9 -0
  27. package/src/hashTree/hashTree.ts +463 -0
  28. package/src/hashTree/hashTreeParser.ts +1022 -7
  29. package/src/hashTree/types.ts +11 -1
  30. package/src/hashTree/utils.ts +42 -0
  31. package/src/locus-info/index.ts +49 -27
  32. package/src/locus-info/types.ts +13 -11
  33. package/test/unit/spec/hashTree/hashTree.ts +655 -0
  34. package/test/unit/spec/hashTree/hashTreeParser.ts +1532 -0
  35. package/test/unit/spec/hashTree/utils.ts +103 -0
  36. package/test/unit/spec/locus-info/index.js +95 -4
@@ -1,15 +1,25 @@
1
1
  import {Enum} from '../constants';
2
2
 
3
- // todo: Locus docs have now more types like CONTROL_ENTRY, EMBEDDED_APP, FULL_STATE, INFO, MEDIA_SHARE - need to add support for them once Locus implements them
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',
9
11
  } as const;
10
12
 
11
13
  export type ObjectType = Enum<typeof ObjectType>;
12
14
 
15
+ // mapping from ObjectType to top level LocusDTO keys
16
+ export const ObjectTypeToLocusKeyMap = {
17
+ [ObjectType.info]: 'info',
18
+ [ObjectType.fullState]: 'fullState',
19
+ [ObjectType.self]: 'self',
20
+ [ObjectType.participant]: 'participants', // note: each object is a single participant in participants array
21
+ [ObjectType.mediaShare]: 'mediaShares', // note: each object is a single mediaShare in mediaShares array
22
+ };
13
23
  export interface HtMeta {
14
24
  elementId: {
15
25
  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
+ };
@@ -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 {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',
@@ -478,12 +480,19 @@ export default class LocusInfo extends EventsScope {
478
480
  */
479
481
  handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
480
482
  if (this.hashTreeParser) {
481
- // API responses with hash tree are a bit problematic and not fully confirmed how they will look like
482
- // we don't really need them, because all updates are guaranteed to come via Mercury or LLM messages anyway
483
- // so it's OK to skip them for now
483
+ if (!responseBody.dataSets) {
484
+ this.sendClassicVsHashTreeMismatchMetric(
485
+ meeting,
486
+ `expected hash tree dataSets in API response but they are missing`
487
+ );
488
+ // continuing as we can still manage without responseBody.dataSets, but this is very suspicious
489
+ }
484
490
  LoggerProxy.logger.info(
485
- 'Locus-info:index#handleLocusAPIResponse: skipping handling of API http response with hashTreeParser'
491
+ 'Locus-info:index#handleLocusAPIResponse --> passing Locus API response to HashTreeParser: ',
492
+ responseBody
486
493
  );
494
+ // update the data in our hash trees
495
+ this.hashTreeParser.handleLocusUpdate(responseBody);
487
496
  } else {
488
497
  if (responseBody.dataSets) {
489
498
  this.sendClassicVsHashTreeMismatchMetric(
@@ -511,23 +520,25 @@ export default class LocusInfo extends EventsScope {
511
520
  // not doing anything here, as we need Locus to always be there (at least some fields)
512
521
  // and that's already taken care of in updateFromHashTree()
513
522
  LoggerProxy.logger.info(
514
- `Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object removed`
523
+ `Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object removed, version=${object.htMeta.elementId.version}`
515
524
  );
516
525
 
517
526
  return locus;
518
527
  }
519
528
  // replace the main locus
520
529
 
521
- // The Locus object we receive from backend has empty participants, so removing them to avoid it overriding the ones in our current locus object
522
- // Also, other fields like mediaShares are managed by other ObjectType updates, so removing them too
523
- // BTW, it also doesn't have "self". That's OK as it won't override existing locus.self and also existing SDK code can handle that missing self in Locus updates
530
+ // The Locus object we receive from backend has empty participants array,
531
+ // and may have (although it shouldn't) other fields that are managed by other ObjectTypes
532
+ // like "fullState" or "info", so we're making sure to delete them here
524
533
  const locusObjectFromData = object.data;
525
- delete locusObjectFromData.participants;
526
- delete locusObjectFromData.mediaShares;
534
+
535
+ Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
536
+ delete locusObjectFromData[locusDtoKey];
537
+ });
527
538
 
528
539
  locus = {...locus, ...locusObjectFromData};
529
540
  LoggerProxy.logger.info(
530
- `Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object updated`
541
+ `Locus-info:index#updateLocusFromHashTreeObject --> LOCUS object updated to version=${object.htMeta.elementId.version}`
531
542
  );
532
543
  break;
533
544
  }
@@ -540,7 +551,7 @@ export default class LocusInfo extends EventsScope {
540
551
  object.data.name === 'content'
541
552
  ? `floor=${object.data.floor?.disposition}, ${object.data.floor?.beneficiary?.id}`
542
553
  : ''
543
- }`
554
+ } version=${object.htMeta.elementId.version}`
544
555
  );
545
556
  const existingMediaShare = locus.mediaShares?.find(
546
557
  (ms) => ms.htMeta.elementId.id === object.htMeta.elementId.id
@@ -554,7 +565,7 @@ export default class LocusInfo extends EventsScope {
554
565
  }
555
566
  } else {
556
567
  LoggerProxy.logger.info(
557
- `Locus-info:index#updateLocusFromHashTreeObject --> mediaShare id=${object.htMeta.elementId.id} removed`
568
+ `Locus-info:index#updateLocusFromHashTreeObject --> mediaShare id=${object.htMeta.elementId.id} removed, version=${object.htMeta.elementId.version}`
558
569
  );
559
570
  locus.mediaShares = locus.mediaShares?.filter(
560
571
  (ms) => ms.htMeta.elementId.id !== object.htMeta.elementId.id
@@ -565,7 +576,7 @@ export default class LocusInfo extends EventsScope {
565
576
  LoggerProxy.logger.info(
566
577
  `Locus-info:index#updateLocusFromHashTreeObject --> participant id=${
567
578
  object.htMeta.elementId.id
568
- } ${object.data ? 'updated' : 'removed'}`
579
+ } ${object.data ? 'updated' : 'removed'} version=${object.htMeta.elementId.version}`
569
580
  );
570
581
  if (object.data) {
571
582
  if (!locus.participants) {
@@ -585,19 +596,22 @@ export default class LocusInfo extends EventsScope {
585
596
  this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
586
597
  }
587
598
  break;
599
+ case ObjectType.info:
600
+ case ObjectType.fullState:
588
601
  case ObjectType.self:
589
602
  if (!object.data) {
590
603
  // self without data is handled inside HashTreeParser and results in LocusInfoUpdateType.MEETING_ENDED, so we should never get here
604
+ // other types like info or fullstate - Locus should never send them without data
591
605
  LoggerProxy.logger.warn(
592
- `Locus-info:index#updateLocusFromHashTreeObject --> received SELF object without data, this is not expected!`
606
+ `Locus-info:index#updateLocusFromHashTreeObject --> received ${type} object without data, this is not expected! version=${object.htMeta.elementId.version}`
593
607
  );
594
-
595
- return locus;
608
+ } else {
609
+ LoggerProxy.logger.info(
610
+ `Locus-info:index#updateLocusFromHashTreeObject --> ${type} object updated to version ${object.htMeta.elementId.version}`
611
+ );
612
+ const locusDtoKey = ObjectTypeToLocusKeyMap[type];
613
+ locus[locusDtoKey] = object.data;
596
614
  }
597
- LoggerProxy.logger.info(
598
- `Locus-info:index#updateLocusFromHashTreeObject --> SELF object updated`
599
- );
600
- locus.self = object.data;
601
615
  break;
602
616
  default:
603
617
  LoggerProxy.logger.warn(
@@ -704,19 +718,27 @@ export default class LocusInfo extends EventsScope {
704
718
 
705
719
  // if Locus object is unchanged or removed, we need to keep using the existing locus
706
720
  // 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 don't need to do anything and we start with an empty one
708
- // so that when it gets updated, if the new one is missing some field, that field will
721
+ // if it gets updated, we only need to have the fields that are not part of "locus" object (like "info" or "mediaShares")
722
+ // so that when Locus object gets updated, if the new one is missing some field, that field will
709
723
  // be removed from our locusInfo
710
724
  if (
711
725
  locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.unchanged ||
712
726
  locusObjectStateAfterUpdates === LocusObjectStateAfterUpdates.removed
713
727
  ) {
714
- // copy over existing locus
728
+ // copy over all of existing locus except participants
715
729
  LocusDtoTopLevelKeys.forEach((key) => {
716
730
  if (key !== 'participants') {
717
731
  locus[key] = cloneDeep(this[key]);
718
732
  }
719
733
  });
734
+ } else {
735
+ // initialize only the fields that are not part of main "Locus" object
736
+ // (except participants, which need to stay empty - that means "no participant changes")
737
+ Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
738
+ if (locusDtoKey !== 'participants') {
739
+ locus[locusDtoKey] = cloneDeep(this[locusDtoKey]);
740
+ }
741
+ });
720
742
  }
721
743
 
722
744
  LoggerProxy.logger.info(
@@ -724,7 +746,7 @@ export default class LocusInfo extends EventsScope {
724
746
  data.updatedObjects.map((o) => ({
725
747
  type: o.htMeta.elementId.type,
726
748
  id: o.htMeta.elementId.id,
727
- hasData: o.data !== undefined,
749
+ hasData: !!o.data,
728
750
  }))
729
751
  )}`
730
752
  );
@@ -1,18 +1,20 @@
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
+
3
15
  export type LocusDTO = {
4
16
  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
- };
17
+ fullState?: LocusFullState;
16
18
  host?: {
17
19
  id: string;
18
20
  incomingCallProtocols: any[];