@webex/plugin-meetings 3.11.0-next.4 → 3.11.0-next.41

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 (146) hide show
  1. package/dist/aiEnableRequest/index.js +184 -0
  2. package/dist/aiEnableRequest/index.js.map +1 -0
  3. package/dist/aiEnableRequest/utils.js +36 -0
  4. package/dist/aiEnableRequest/utils.js.map +1 -0
  5. package/dist/annotation/index.js +3 -3
  6. package/dist/annotation/index.js.map +1 -1
  7. package/dist/breakouts/breakout.js +1 -1
  8. package/dist/breakouts/index.js +1 -1
  9. package/dist/config.js +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +26 -6
  12. package/dist/constants.js.map +1 -1
  13. package/dist/hashTree/constants.js +3 -1
  14. package/dist/hashTree/constants.js.map +1 -1
  15. package/dist/hashTree/hashTree.js +18 -0
  16. package/dist/hashTree/hashTree.js.map +1 -1
  17. package/dist/hashTree/hashTreeParser.js +709 -380
  18. package/dist/hashTree/hashTreeParser.js.map +1 -1
  19. package/dist/hashTree/types.js +4 -2
  20. package/dist/hashTree/types.js.map +1 -1
  21. package/dist/hashTree/utils.js +10 -0
  22. package/dist/hashTree/utils.js.map +1 -1
  23. package/dist/index.js +11 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/interceptors/constant.js +12 -0
  26. package/dist/interceptors/constant.js.map +1 -0
  27. package/dist/interceptors/dataChannelAuthToken.js +233 -0
  28. package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
  29. package/dist/interceptors/index.js +7 -0
  30. package/dist/interceptors/index.js.map +1 -1
  31. package/dist/interpretation/index.js +2 -2
  32. package/dist/interpretation/index.js.map +1 -1
  33. package/dist/interpretation/siLanguage.js +1 -1
  34. package/dist/locus-info/controlsUtils.js +5 -3
  35. package/dist/locus-info/controlsUtils.js.map +1 -1
  36. package/dist/locus-info/index.js +125 -68
  37. package/dist/locus-info/index.js.map +1 -1
  38. package/dist/locus-info/selfUtils.js +1 -0
  39. package/dist/locus-info/selfUtils.js.map +1 -1
  40. package/dist/locus-info/types.js.map +1 -1
  41. package/dist/media/MediaConnectionAwaiter.js +57 -1
  42. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  43. package/dist/media/properties.js +4 -2
  44. package/dist/media/properties.js.map +1 -1
  45. package/dist/meeting/in-meeting-actions.js +7 -1
  46. package/dist/meeting/in-meeting-actions.js.map +1 -1
  47. package/dist/meeting/index.js +209 -90
  48. package/dist/meeting/index.js.map +1 -1
  49. package/dist/meeting/request.js +50 -0
  50. package/dist/meeting/request.js.map +1 -1
  51. package/dist/meeting/request.type.js.map +1 -1
  52. package/dist/meeting/util.js +128 -2
  53. package/dist/meeting/util.js.map +1 -1
  54. package/dist/meetings/index.js +78 -36
  55. package/dist/meetings/index.js.map +1 -1
  56. package/dist/member/index.js +10 -0
  57. package/dist/member/index.js.map +1 -1
  58. package/dist/member/util.js +10 -0
  59. package/dist/member/util.js.map +1 -1
  60. package/dist/metrics/constants.js +2 -1
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/multistream/mediaRequestManager.js +1 -1
  63. package/dist/multistream/mediaRequestManager.js.map +1 -1
  64. package/dist/multistream/remoteMediaManager.js +11 -0
  65. package/dist/multistream/remoteMediaManager.js.map +1 -1
  66. package/dist/reactions/reactions.type.js.map +1 -1
  67. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  68. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  69. package/dist/types/config.d.ts +3 -0
  70. package/dist/types/constants.d.ts +21 -1
  71. package/dist/types/hashTree/constants.d.ts +1 -0
  72. package/dist/types/hashTree/hashTree.d.ts +7 -0
  73. package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
  74. package/dist/types/hashTree/types.d.ts +3 -0
  75. package/dist/types/hashTree/utils.d.ts +6 -0
  76. package/dist/types/index.d.ts +1 -0
  77. package/dist/types/interceptors/constant.d.ts +5 -0
  78. package/dist/types/interceptors/dataChannelAuthToken.d.ts +35 -0
  79. package/dist/types/interceptors/index.d.ts +2 -1
  80. package/dist/types/locus-info/index.d.ts +9 -2
  81. package/dist/types/locus-info/types.d.ts +1 -0
  82. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  83. package/dist/types/media/properties.d.ts +2 -1
  84. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  85. package/dist/types/meeting/index.d.ts +24 -2
  86. package/dist/types/meeting/request.d.ts +16 -1
  87. package/dist/types/meeting/request.type.d.ts +5 -0
  88. package/dist/types/meeting/util.d.ts +31 -0
  89. package/dist/types/meetings/index.d.ts +4 -2
  90. package/dist/types/member/index.d.ts +1 -0
  91. package/dist/types/member/util.d.ts +5 -0
  92. package/dist/types/metrics/constants.d.ts +1 -0
  93. package/dist/types/reactions/reactions.type.d.ts +1 -0
  94. package/dist/webinar/index.js +1 -1
  95. package/package.json +22 -22
  96. package/src/aiEnableRequest/README.md +84 -0
  97. package/src/aiEnableRequest/index.ts +170 -0
  98. package/src/aiEnableRequest/utils.ts +25 -0
  99. package/src/annotation/index.ts +7 -4
  100. package/src/config.ts +3 -0
  101. package/src/constants.ts +26 -1
  102. package/src/hashTree/constants.ts +1 -0
  103. package/src/hashTree/hashTree.ts +17 -0
  104. package/src/hashTree/hashTreeParser.ts +627 -249
  105. package/src/hashTree/types.ts +4 -0
  106. package/src/hashTree/utils.ts +9 -0
  107. package/src/index.ts +8 -1
  108. package/src/interceptors/constant.ts +6 -0
  109. package/src/interceptors/dataChannelAuthToken.ts +142 -0
  110. package/src/interceptors/index.ts +2 -1
  111. package/src/interpretation/index.ts +2 -2
  112. package/src/locus-info/controlsUtils.ts +11 -0
  113. package/src/locus-info/index.ts +146 -58
  114. package/src/locus-info/selfUtils.ts +1 -0
  115. package/src/locus-info/types.ts +1 -0
  116. package/src/media/MediaConnectionAwaiter.ts +41 -1
  117. package/src/media/properties.ts +3 -1
  118. package/src/meeting/in-meeting-actions.ts +12 -0
  119. package/src/meeting/index.ts +127 -17
  120. package/src/meeting/request.ts +42 -0
  121. package/src/meeting/request.type.ts +6 -0
  122. package/src/meeting/util.ts +156 -1
  123. package/src/meetings/index.ts +94 -9
  124. package/src/member/index.ts +10 -0
  125. package/src/member/util.ts +12 -0
  126. package/src/metrics/constants.ts +1 -0
  127. package/src/multistream/mediaRequestManager.ts +1 -1
  128. package/src/multistream/remoteMediaManager.ts +13 -0
  129. package/src/reactions/reactions.type.ts +1 -0
  130. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  131. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  132. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  133. package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
  134. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +141 -0
  135. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  136. package/test/unit/spec/locus-info/index.js +201 -45
  137. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  138. package/test/unit/spec/media/properties.ts +12 -3
  139. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  140. package/test/unit/spec/meeting/index.js +441 -75
  141. package/test/unit/spec/meeting/request.js +64 -0
  142. package/test/unit/spec/meeting/utils.js +433 -22
  143. package/test/unit/spec/meetings/index.js +550 -10
  144. package/test/unit/spec/member/index.js +28 -4
  145. package/test/unit/spec/member/util.js +65 -27
  146. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
@@ -35,10 +35,11 @@ import HashTreeParser, {
35
35
  DataSet,
36
36
  HashTreeMessage,
37
37
  LocusInfoUpdateType,
38
+ Metadata,
38
39
  } from '../hashTree/hashTreeParser';
39
40
  import {HashTreeObject, ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
40
- import {isSelf} from '../hashTree/utils';
41
- import {Links, LocusDTO, LocusFullState} from './types';
41
+ import {isMetadata} from '../hashTree/utils';
42
+ import {Links, LocusDTO} from './types';
42
43
 
43
44
  export type LocusLLMEvent = {
44
45
  data: {
@@ -52,6 +53,7 @@ export type LocusLLMEvent = {
52
53
  const LocusDtoTopLevelKeys = [
53
54
  'controls',
54
55
  'fullState',
56
+ 'embeddedApps',
55
57
  'host',
56
58
  'info',
57
59
  'links',
@@ -69,6 +71,7 @@ const LocusDtoTopLevelKeys = [
69
71
  export type LocusApiResponseBody = {
70
72
  dataSets?: DataSet[];
71
73
  locus: LocusDTO; // this LocusDTO here might not be the full one (for example it won't have all the participants, but it should have self)
74
+ metadata?: Metadata;
72
75
  };
73
76
 
74
77
  const LocusObjectStateAfterUpdates = {
@@ -239,7 +242,7 @@ export default class LocusInfo extends EventsScope {
239
242
  'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
240
243
  );
241
244
  }
242
- meeting.locusInfo.onFullLocus(res.body);
245
+ meeting.locusInfo.onFullLocus('classic Locus sync', res.body);
243
246
  })
244
247
  .catch((e) => {
245
248
  LoggerProxy.logger.info(
@@ -362,17 +365,21 @@ export default class LocusInfo extends EventsScope {
362
365
  */
363
366
  private createHashTreeParser({
364
367
  initialLocus,
368
+ metadata,
365
369
  }: {
366
370
  initialLocus: {
367
371
  dataSets: Array<DataSet>;
368
372
  locus: any;
369
373
  };
374
+ metadata: Metadata;
370
375
  }) {
371
376
  return new HashTreeParser({
372
377
  initialLocus,
378
+ metadata,
373
379
  webexRequest: this.webex.request.bind(this.webex),
374
380
  locusInfoUpdateCallback: this.updateFromHashTree.bind(this),
375
381
  debugId: `HT-${this.meetingId.substring(0, 4)}`,
382
+ excludedDataSets: this.webex.config.meetings.locus?.excludedDataSets,
376
383
  });
377
384
  }
378
385
 
@@ -387,6 +394,7 @@ export default class LocusInfo extends EventsScope {
387
394
  trigger: 'join-response';
388
395
  locus: LocusDTO;
389
396
  dataSets?: DataSet[];
397
+ metadata?: Metadata;
390
398
  }
391
399
  | {
392
400
  trigger: 'locus-message';
@@ -401,28 +409,36 @@ export default class LocusInfo extends EventsScope {
401
409
  switch (data.trigger) {
402
410
  case 'locus-message':
403
411
  if (data.hashTreeMessage) {
404
- // we need the SELF object to be in the received message, because it contains visibleDataSets
412
+ // we need the Metadata object to be in the received message, because it contains visibleDataSets
405
413
  // and these are needed to initialize all the hash trees
406
- const selfObject = data.hashTreeMessage.locusStateElements?.find((el) => isSelf(el));
414
+ const metadataObject = data.hashTreeMessage.locusStateElements?.find((el) =>
415
+ isMetadata(el)
416
+ );
407
417
 
408
- if (!selfObject?.data?.visibleDataSets) {
409
- LoggerProxy.logger.warn(
410
- `Locus-info:index#initialSetup --> cannot initialize HashTreeParser, SELF object with visibleDataSets is missing in the message`
418
+ if (!metadataObject?.data?.visibleDataSets) {
419
+ // this is a common case (not an error)
420
+ // it happens for example after we leave the meeting and still get some heartbeats or delayed messages
421
+ LoggerProxy.logger.info(
422
+ `Locus-info:index#initialSetup --> cannot initialize HashTreeParser, Metadata object with visibleDataSets is missing in the message`
411
423
  );
412
424
 
413
- throw new Error('SELF object with visibleDataSets is missing in the message');
425
+ // throw so that handleLocusEvent() catches it and destroys the partially created meeting object
426
+ throw new Error('Metadata object with visibleDataSets is missing in the message');
414
427
  }
415
428
 
416
429
  LoggerProxy.logger.info(
417
430
  'Locus-info:index#initialSetup --> creating HashTreeParser from message'
418
431
  );
419
432
  // first create the HashTreeParser, but don't initialize it with any data yet
420
- // pass just a fake locus that contains only the visibleDataSets
421
433
  this.hashTreeParser = this.createHashTreeParser({
422
434
  initialLocus: {
423
- locus: {self: {visibleDataSets: selfObject.data.visibleDataSets}},
435
+ locus: null,
424
436
  dataSets: [], // empty, because they will be populated in initializeFromMessage() call // dataSets: data.hashTreeMessage.dataSets,
425
437
  },
438
+ metadata: {
439
+ htMeta: metadataObject.htMeta,
440
+ visibleDataSets: metadataObject.data.visibleDataSets,
441
+ },
426
442
  });
427
443
 
428
444
  // now handle the message - that should populate all the visible datasets
@@ -430,12 +446,12 @@ export default class LocusInfo extends EventsScope {
430
446
  } else {
431
447
  // "classic" Locus case, no hash trees involved
432
448
  this.updateLocusCache(data.locus);
433
- this.onFullLocus(data.locus, undefined);
449
+ this.onFullLocus('classic locus message', data.locus, undefined);
434
450
  }
435
451
  break;
436
452
  case 'join-response':
437
453
  this.updateLocusCache(data.locus);
438
- this.onFullLocus(data.locus, undefined, data.dataSets);
454
+ this.onFullLocus('join response', data.locus, undefined, data.dataSets, data.metadata);
439
455
  break;
440
456
  case 'get-loci-response':
441
457
  if (data.locus?.links?.resources?.visibleDataSets?.url) {
@@ -443,12 +459,12 @@ export default class LocusInfo extends EventsScope {
443
459
  'Locus-info:index#initialSetup --> creating HashTreeParser from get-loci-response'
444
460
  );
445
461
  // first create the HashTreeParser, but don't initialize it with any data yet
446
- // pass just a fake locus that contains only the visibleDataSets
447
462
  this.hashTreeParser = this.createHashTreeParser({
448
463
  initialLocus: {
449
- locus: {self: {visibleDataSets: data.locus?.self?.visibleDataSets}},
464
+ locus: null,
450
465
  dataSets: [], // empty, because we don't have them yet
451
466
  },
467
+ metadata: null, // get-loci-response doesn't contain Metadata object
452
468
  });
453
469
 
454
470
  // now initialize all the data
@@ -456,7 +472,7 @@ export default class LocusInfo extends EventsScope {
456
472
  } else {
457
473
  // "classic" Locus case, no hash trees involved
458
474
  this.updateLocusCache(data.locus);
459
- this.onFullLocus(data.locus, undefined);
475
+ this.onFullLocus('classic get-loci-response', data.locus, undefined);
460
476
  }
461
477
  }
462
478
  // Change it to true after it receives it first locus object
@@ -571,6 +587,31 @@ export default class LocusInfo extends EventsScope {
571
587
  );
572
588
  }
573
589
  break;
590
+ case ObjectType.embeddedApp:
591
+ if (object.data) {
592
+ LoggerProxy.logger.info(
593
+ `Locus-info:index#updateLocusFromHashTreeObject --> embeddedApp id=${object.htMeta.elementId.id} url='${object.data.url}' updated version=${object.htMeta.elementId.version}:`,
594
+ object.data
595
+ );
596
+ const existingEmbeddedApp = locus.embeddedApps?.find(
597
+ (ms) => ms.htMeta.elementId.id === object.htMeta.elementId.id
598
+ );
599
+
600
+ if (existingEmbeddedApp) {
601
+ Object.assign(existingEmbeddedApp, object.data);
602
+ } else {
603
+ locus.embeddedApps = locus.embeddedApps || [];
604
+ locus.embeddedApps.push(object.data);
605
+ }
606
+ } else {
607
+ LoggerProxy.logger.info(
608
+ `Locus-info:index#updateLocusFromHashTreeObject --> embeddedApp id=${object.htMeta.elementId.id} removed, version=${object.htMeta.elementId.version}`
609
+ );
610
+ locus.embeddedApps = locus.embeddedApps?.filter(
611
+ (ms) => ms.htMeta.elementId.id !== object.htMeta.elementId.id
612
+ );
613
+ }
614
+ break;
574
615
  case ObjectType.participant:
575
616
  LoggerProxy.logger.info(
576
617
  `Locus-info:index#updateLocusFromHashTreeObject --> participant id=${
@@ -643,6 +684,12 @@ export default class LocusInfo extends EventsScope {
643
684
  }
644
685
  }
645
686
  break;
687
+ case ObjectType.metadata:
688
+ LoggerProxy.logger.info(
689
+ `Locus-info:index#updateLocusFromHashTreeObject --> metadata object updated to version ${object.htMeta.elementId.version}`
690
+ );
691
+ // we don't use hash tree metadata right now for anything, it's mainly used internally by HashTreeParser
692
+ break;
646
693
  default:
647
694
  LoggerProxy.logger.warn(
648
695
  `Locus-info:index#updateLocusFromHashTreeObject --> received unsupported object type ${type}`
@@ -683,7 +730,11 @@ export default class LocusInfo extends EventsScope {
683
730
  * @param {HashTreeMessage} message incoming hash tree message
684
731
  * @returns {void}
685
732
  */
686
- private handleHashTreeMessage(meeting: any, eventType: LOCUSEVENT, message: HashTreeMessage) {
733
+ private async handleHashTreeMessage(
734
+ meeting: any,
735
+ eventType: LOCUSEVENT,
736
+ message: HashTreeMessage
737
+ ) {
687
738
  if (eventType !== LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
688
739
  this.sendClassicVsHashTreeMismatchMetric(
689
740
  meeting,
@@ -815,8 +866,18 @@ export default class LocusInfo extends EventsScope {
815
866
  data.stateElementsMessage as HashTreeMessage
816
867
  );
817
868
  } else {
818
- // eslint-disable-next-line @typescript-eslint/no-shadow
819
869
  const {eventType} = data;
870
+
871
+ if (eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
872
+ // this can happen when we get an event before join http response
873
+ // it's OK to just ignore it
874
+ LoggerProxy.logger.info(
875
+ `Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created`
876
+ );
877
+
878
+ return;
879
+ }
880
+
820
881
  const locus = this.getTheLocusToUpdate(data.locus);
821
882
  LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
822
883
 
@@ -837,17 +898,11 @@ export default class LocusInfo extends EventsScope {
837
898
  case LOCUSEVENT.PARTICIPANT_DECLINED:
838
899
  case LOCUSEVENT.FLOOR_GRANTED:
839
900
  case LOCUSEVENT.FLOOR_RELEASED:
840
- this.onFullLocus(locus, eventType);
901
+ this.onFullLocus(`classic locus event ${eventType}`, locus, eventType);
841
902
  break;
842
903
  case LOCUSEVENT.DIFFERENCE:
843
904
  this.handleLocusDelta(locus, meeting);
844
905
  break;
845
- case LOCUSEVENT.HASH_TREE_DATA_UPDATED:
846
- this.sendClassicVsHashTreeMismatchMetric(
847
- meeting,
848
- `got ${eventType}, expected classic events`
849
- );
850
- break;
851
906
 
852
907
  default:
853
908
  // Why will there be a event with no eventType ????
@@ -871,22 +926,35 @@ export default class LocusInfo extends EventsScope {
871
926
  /**
872
927
  * Function for handling full locus when it's using hash trees (so not the "classic" one).
873
928
  *
929
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
874
930
  * @param {object} locus locus object
931
+ * @param {object} metadata locus hash trees metadata
875
932
  * @param {string} eventType locus event
876
933
  * @param {DataSet[]} dataSets
877
934
  * @returns {void}
878
935
  */
879
- private onFullLocusWithHashTrees(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
936
+ private onFullLocusWithHashTrees(
937
+ debugText: string,
938
+ locus: any,
939
+ metadata: Metadata,
940
+ eventType?: string,
941
+ dataSets?: Array<DataSet>
942
+ ) {
880
943
  if (!this.hashTreeParser) {
881
- LoggerProxy.logger.info(`Locus-info:index#onFullLocus --> creating hash tree parser`);
882
944
  LoggerProxy.logger.info(
883
- 'Locus-info:index#onFullLocus --> dataSets:',
945
+ `Locus-info:index#onFullLocus (${debugText}) --> creating hash tree parser`
946
+ );
947
+ LoggerProxy.logger.info(
948
+ `Locus-info:index#onFullLocus (${debugText}) --> dataSets:`,
884
949
  dataSets,
885
950
  ' and locus:',
886
- locus
951
+ locus,
952
+ ' and metadata:',
953
+ metadata
887
954
  );
888
955
  this.hashTreeParser = this.createHashTreeParser({
889
956
  initialLocus: {locus, dataSets},
957
+ metadata,
890
958
  });
891
959
  this.onFullLocusCommon(locus, eventType);
892
960
  } else {
@@ -894,23 +962,24 @@ export default class LocusInfo extends EventsScope {
894
962
  // so treat it like if we just got it in any api response
895
963
 
896
964
  LoggerProxy.logger.info(
897
- 'Locus-info:index#onFullLocus --> hash tree parser already exists, handling it like a normal API response'
965
+ `Locus-info:index#onFullLocus (${debugText}) --> hash tree parser already exists, handling it like a normal API response`
898
966
  );
899
- this.handleLocusAPIResponse(undefined, {dataSets, locus});
967
+ this.handleLocusAPIResponse(undefined, {dataSets, locus, metadata});
900
968
  }
901
969
  }
902
970
 
903
971
  /**
904
972
  * Function for handling full locus when it's the "classic" one (not hash trees)
905
973
  *
974
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
906
975
  * @param {object} locus locus object
907
976
  * @param {string} eventType locus event
908
977
  * @returns {void}
909
978
  */
910
- private onFullLocusClassic(locus: any, eventType?: string) {
979
+ private onFullLocusClassic(debugText: string, locus: any, eventType?: string) {
911
980
  if (!this.locusParser.isNewFullLocus(locus)) {
912
981
  LoggerProxy.logger.info(
913
- `Locus-info:index#onFullLocus --> ignoring old full locus DTO, eventType=${eventType}`
982
+ `Locus-info:index#onFullLocus (${debugText}) --> ignoring old full locus DTO, eventType=${eventType}`
914
983
  );
915
984
 
916
985
  return;
@@ -920,24 +989,37 @@ export default class LocusInfo extends EventsScope {
920
989
 
921
990
  /**
922
991
  * updates the locus with full locus object
992
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
923
993
  * @param {object} locus locus object
924
994
  * @param {string} eventType locus event
925
995
  * @param {DataSet[]} dataSets
996
+ * @param {object} metadata locus hash trees metadata
926
997
  * @returns {object} null
927
998
  * @memberof LocusInfo
928
999
  */
929
- onFullLocus(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
1000
+ onFullLocus(
1001
+ debugText: string,
1002
+ locus: any,
1003
+ eventType?: string,
1004
+ dataSets?: Array<DataSet>,
1005
+ metadata?: Metadata
1006
+ ) {
930
1007
  if (!locus) {
931
1008
  LoggerProxy.logger.error(
932
- 'Locus-info:index#onFullLocus --> object passed as argument was invalid, continuing.'
1009
+ `Locus-info:index#onFullLocus (${debugText}) --> object passed as argument was invalid, continuing.`
933
1010
  );
934
1011
  }
935
1012
 
936
1013
  if (dataSets) {
1014
+ if (!metadata) {
1015
+ throw new Error(
1016
+ `Locus-info:index#onFullLocus (${debugText}) --> hash tree metadata is missing with full Locus`
1017
+ );
1018
+ }
937
1019
  // this is the new hashmap Locus DTO format (only applicable to webinars for now)
938
- this.onFullLocusWithHashTrees(locus, eventType, dataSets);
1020
+ this.onFullLocusWithHashTrees(debugText, locus, metadata, eventType, dataSets);
939
1021
  } else {
940
- this.onFullLocusClassic(locus, eventType);
1022
+ this.onFullLocusClassic(debugText, locus, eventType);
941
1023
  }
942
1024
  }
943
1025
 
@@ -1217,27 +1299,6 @@ export default class LocusInfo extends EventsScope {
1217
1299
  shouldLeave: false,
1218
1300
  }
1219
1301
  );
1220
- } else if (this.fullState && this.fullState.removed) {
1221
- // user has been dropped from a meeting
1222
-
1223
- // @ts-ignore
1224
- this.webex.internal.newMetrics.submitClientEvent({
1225
- name: 'client.call.remote-ended',
1226
- options: {
1227
- meetingId: this.meetingId,
1228
- },
1229
- });
1230
- this.emitScoped(
1231
- {
1232
- file: 'locus-info',
1233
- function: 'isMeetingActive',
1234
- },
1235
- EVENTS.DESTROY_MEETING,
1236
- {
1237
- reason: MEETING_REMOVED_REASON.FULLSTATE_REMOVED,
1238
- shouldLeave: false,
1239
- }
1240
- );
1241
1302
  }
1242
1303
  // If you are guest and you are removed from the meeting
1243
1304
  // You wont get any further events
@@ -1373,6 +1434,7 @@ export default class LocusInfo extends EventsScope {
1373
1434
  hasMeetingContainerChanged,
1374
1435
  hasTranscribeChanged,
1375
1436
  hasHesiodLLMIdChanged,
1437
+ hasAiSummaryNotificationChanged,
1376
1438
  hasTranscribeSpokenLanguageChanged,
1377
1439
  hasManualCaptionChanged,
1378
1440
  hasEntryExitToneChanged,
@@ -1529,6 +1591,19 @@ export default class LocusInfo extends EventsScope {
1529
1591
  );
1530
1592
  }
1531
1593
 
1594
+ if (hasAiSummaryNotificationChanged) {
1595
+ this.emitScoped(
1596
+ {
1597
+ file: 'locus-info',
1598
+ function: 'updateControls',
1599
+ },
1600
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
1601
+ {
1602
+ aiSummaryNotification: current.transcribe.aiSummaryNotification,
1603
+ }
1604
+ );
1605
+ }
1606
+
1532
1607
  if (hasTranscribeSpokenLanguageChanged) {
1533
1608
  const {spokenLanguage} = current.transcribe;
1534
1609
 
@@ -2038,6 +2113,19 @@ export default class LocusInfo extends EventsScope {
2038
2113
  );
2039
2114
  }
2040
2115
 
2116
+ if (parsedSelves.updates.selfIdChanged) {
2117
+ this.emitScoped(
2118
+ {
2119
+ file: 'locus-info',
2120
+ function: 'updateSelf',
2121
+ },
2122
+ LOCUSINFO.EVENTS.SELF_ID_CHANGED,
2123
+ {
2124
+ selfId: parsedSelves.current.selfId,
2125
+ }
2126
+ );
2127
+ }
2128
+
2041
2129
  if (parsedSelves.updates.interpretationChanged) {
2042
2130
  this.emitScoped(
2043
2131
  {
@@ -138,6 +138,7 @@ const SelfUtils = {
138
138
  updates.breakoutsChanged = SelfUtils.breakoutsChanged(previous, current);
139
139
  updates.interpretationChanged = SelfUtils.interpretationChanged(previous, current);
140
140
  updates.brbChanged = SelfUtils.brbChanged(previous, current);
141
+ updates.selfIdChanged = previous?.selfId !== current.selfId;
141
142
 
142
143
  return {
143
144
  previous,
@@ -19,6 +19,7 @@ export type Links = {
19
19
 
20
20
  export type LocusDTO = {
21
21
  controls?: any;
22
+ embeddedApps?: any[];
22
23
  fullState?: LocusFullState;
23
24
  host?: {
24
25
  id: string;
@@ -2,9 +2,12 @@ import {Defer} from '@webex/common';
2
2
  import {ConnectionState, MediaConnectionEventNames} from '@webex/internal-media-core';
3
3
  import LoggerProxy from '../common/logs/logger-proxy';
4
4
  import {ICE_AND_DTLS_CONNECTION_TIMEOUT} from '../constants';
5
+ import BEHAVIORAL_METRICS from '../metrics/constants';
6
+ import Metrics from '../metrics';
5
7
 
6
8
  export interface MediaConnectionAwaiterProps {
7
9
  webrtcMediaConnection: any;
10
+ correlationId: string;
8
11
  }
9
12
 
10
13
  /**
@@ -16,6 +19,7 @@ export default class MediaConnectionAwaiter {
16
19
  private defer: Defer;
17
20
  private retried: boolean;
18
21
  private iceConnected: boolean;
22
+ private correlationId: string;
19
23
  private onTimeoutCallback: () => void;
20
24
  private peerConnectionStateCallback: () => void;
21
25
  private iceConnectionStateCallback: () => void;
@@ -24,11 +28,12 @@ export default class MediaConnectionAwaiter {
24
28
  /**
25
29
  * @param {MediaConnectionAwaiterProps} mediaConnectionAwaiterProps
26
30
  */
27
- constructor({webrtcMediaConnection}: MediaConnectionAwaiterProps) {
31
+ constructor({webrtcMediaConnection, correlationId}: MediaConnectionAwaiterProps) {
28
32
  this.webrtcMediaConnection = webrtcMediaConnection;
29
33
  this.defer = new Defer();
30
34
  this.retried = false;
31
35
  this.iceConnected = false;
36
+ this.correlationId = correlationId;
32
37
  this.onTimeoutCallback = this.onTimeout.bind(this);
33
38
  this.peerConnectionStateCallback = this.peerConnectionStateHandler.bind(this);
34
39
  this.iceConnectionStateCallback = this.iceConnectionStateHandler.bind(this);
@@ -175,6 +180,32 @@ export default class MediaConnectionAwaiter {
175
180
  this.timer = setTimeout(this.onTimeoutCallback, ICE_AND_DTLS_CONNECTION_TIMEOUT);
176
181
  }
177
182
 
183
+ /**
184
+ * sends a metric with some additional info that might help debugging
185
+ * issues where browser doesn't update the RTCPeerConnection's iceConnectionState or connectionState
186
+ *
187
+ * @returns {void}
188
+ */
189
+ async sendMetric() {
190
+ const stats = await this.webrtcMediaConnection.getStats();
191
+
192
+ // in theory we can end up with more than one transport report in the stats,
193
+ // but for the purpose of this metric it's fine to just use the first one
194
+ const transportReports = Array.from(
195
+ stats.values().filter((report) => report.type === 'transport')
196
+ ) as Record<string, number | string>[];
197
+
198
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEDIA_STILL_NOT_CONNECTED, {
199
+ correlation_id: this.correlationId,
200
+ numTransports: transportReports.length,
201
+ dtlsState: transportReports[0]?.dtlsState,
202
+ iceState: transportReports[0]?.iceState,
203
+ packetsSent: transportReports[0]?.packetsSent,
204
+ packetsReceived: transportReports[0]?.packetsReceived,
205
+ dataChannelState: this.webrtcMediaConnection.multistreamConnection?.dataChannel?.readyState,
206
+ });
207
+ }
208
+
178
209
  /**
179
210
  * Function called when the timeout is reached.
180
211
  *
@@ -189,6 +220,8 @@ export default class MediaConnectionAwaiter {
189
220
  return;
190
221
  }
191
222
 
223
+ this.sendMetric();
224
+
192
225
  if (!this.isIceGatheringCompleted()) {
193
226
  if (!this.retried) {
194
227
  LoggerProxy.logger.warn(
@@ -226,8 +259,15 @@ export default class MediaConnectionAwaiter {
226
259
  */
227
260
  waitForMediaConnectionConnected(): Promise<void> {
228
261
  if (this.isConnected()) {
262
+ LoggerProxy.logger.log(
263
+ 'Media:MediaConnectionAwaiter#waitForMediaConnectionConnected --> Already connected'
264
+ );
265
+
229
266
  return Promise.resolve();
230
267
  }
268
+ LoggerProxy.logger.log(
269
+ 'Media:MediaConnectionAwaiter#waitForMediaConnectionConnected --> Waiting for media connection to be connected'
270
+ );
231
271
 
232
272
  this.webrtcMediaConnection.on(
233
273
  MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED,
@@ -196,11 +196,13 @@ export default class MediaProperties {
196
196
  /**
197
197
  * Waits for the webrtc media connection to be connected.
198
198
  *
199
+ * @param {string} correlationId
199
200
  * @returns {Promise<void>}
200
201
  */
201
- waitForMediaConnectionConnected(): Promise<void> {
202
+ waitForMediaConnectionConnected(correlationId: string): Promise<void> {
202
203
  const mediaConnectionAwaiter = new MediaConnectionAwaiter({
203
204
  webrtcMediaConnection: this.webrtcMediaConnection,
205
+ correlationId,
204
206
  });
205
207
 
206
208
  return mediaConnectionAwaiter.waitForMediaConnectionConnected();
@@ -32,6 +32,7 @@ interface IInMeetingActions {
32
32
  canLowerAllHands?: boolean;
33
33
  canLowerSomeoneElsesHand?: boolean;
34
34
  bothLeaveAndEndMeetingAvailable?: boolean;
35
+ requireHostEndMeetingBeforeLeave?: boolean;
35
36
  canEnableClosedCaption?: boolean;
36
37
  canStartTranscribing?: boolean;
37
38
  canStopTranscribing?: boolean;
@@ -117,6 +118,8 @@ interface IInMeetingActions {
117
118
  canMoveToLobby?: boolean;
118
119
  canEnablePollingQA?: boolean;
119
120
  canDisablePollingQA?: boolean;
121
+ canAttendeeRequestAiAssistantEnabled?: boolean;
122
+ isAttendeeRequestAiAssistantDeclinedAll?: boolean;
120
123
  }
121
124
 
122
125
  /**
@@ -169,6 +172,8 @@ export default class InMeetingActions implements IInMeetingActions {
169
172
 
170
173
  bothLeaveAndEndMeetingAvailable = null;
171
174
 
175
+ requireHostEndMeetingBeforeLeave = null;
176
+
172
177
  canEnableClosedCaption = null;
173
178
 
174
179
  canStartTranscribing = null;
@@ -337,6 +342,10 @@ export default class InMeetingActions implements IInMeetingActions {
337
342
 
338
343
  canDisablePollingQA = null;
339
344
 
345
+ canAttendeeRequestAiAssistantEnabled = null;
346
+
347
+ isAttendeeRequestAiAssistantDeclinedAll = null;
348
+
340
349
  /**
341
350
  * Returns all meeting action options
342
351
  * @returns {Object}
@@ -364,6 +373,7 @@ export default class InMeetingActions implements IInMeetingActions {
364
373
  canLowerAllHands: this.canLowerAllHands,
365
374
  canLowerSomeoneElsesHand: this.canLowerSomeoneElsesHand,
366
375
  bothLeaveAndEndMeetingAvailable: this.bothLeaveAndEndMeetingAvailable,
376
+ requireHostEndMeetingBeforeLeave: this.requireHostEndMeetingBeforeLeave,
367
377
  canEnableClosedCaption: this.canEnableClosedCaption,
368
378
  canStartTranscribing: this.canStartTranscribing,
369
379
  canStopTranscribing: this.canStopTranscribing,
@@ -448,6 +458,8 @@ export default class InMeetingActions implements IInMeetingActions {
448
458
  canMoveToLobby: this.canMoveToLobby,
449
459
  canEnablePollingQA: this.canEnablePollingQA,
450
460
  canDisablePollingQA: this.canDisablePollingQA,
461
+ canAttendeeRequestAiAssistantEnabled: this.canAttendeeRequestAiAssistantEnabled,
462
+ isAttendeeRequestAiAssistantDeclinedAll: this.isAttendeeRequestAiAssistantDeclinedAll,
451
463
  });
452
464
 
453
465
  /**