@webex/plugin-meetings 3.11.0 → 3.12.0

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 (170) 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 +14 -5
  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 +28 -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 +290 -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/interceptors/utils.js +27 -0
  32. package/dist/interceptors/utils.js.map +1 -0
  33. package/dist/interpretation/index.js +2 -2
  34. package/dist/interpretation/index.js.map +1 -1
  35. package/dist/interpretation/siLanguage.js +1 -1
  36. package/dist/locus-info/controlsUtils.js +5 -3
  37. package/dist/locus-info/controlsUtils.js.map +1 -1
  38. package/dist/locus-info/index.js +217 -79
  39. package/dist/locus-info/index.js.map +1 -1
  40. package/dist/locus-info/selfUtils.js +1 -0
  41. package/dist/locus-info/selfUtils.js.map +1 -1
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/MediaConnectionAwaiter.js +57 -1
  44. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  45. package/dist/media/properties.js +4 -2
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +7 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1082 -861
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/request.js +50 -0
  52. package/dist/meeting/request.js.map +1 -1
  53. package/dist/meeting/request.type.js.map +1 -1
  54. package/dist/meeting/util.js +133 -3
  55. package/dist/meeting/util.js.map +1 -1
  56. package/dist/meetings/index.js +100 -45
  57. package/dist/meetings/index.js.map +1 -1
  58. package/dist/member/index.js +10 -0
  59. package/dist/member/index.js.map +1 -1
  60. package/dist/member/util.js +10 -0
  61. package/dist/member/util.js.map +1 -1
  62. package/dist/metrics/constants.js +2 -1
  63. package/dist/metrics/constants.js.map +1 -1
  64. package/dist/multistream/mediaRequestManager.js +9 -60
  65. package/dist/multistream/mediaRequestManager.js.map +1 -1
  66. package/dist/multistream/remoteMediaManager.js +11 -0
  67. package/dist/multistream/remoteMediaManager.js.map +1 -1
  68. package/dist/reachability/index.js +18 -10
  69. package/dist/reachability/index.js.map +1 -1
  70. package/dist/reactions/reactions.type.js.map +1 -1
  71. package/dist/reconnection-manager/index.js +0 -1
  72. package/dist/reconnection-manager/index.js.map +1 -1
  73. package/dist/types/aiEnableRequest/index.d.ts +5 -0
  74. package/dist/types/aiEnableRequest/utils.d.ts +2 -0
  75. package/dist/types/config.d.ts +3 -0
  76. package/dist/types/constants.d.ts +23 -1
  77. package/dist/types/hashTree/constants.d.ts +1 -0
  78. package/dist/types/hashTree/hashTree.d.ts +7 -0
  79. package/dist/types/hashTree/hashTreeParser.d.ts +99 -14
  80. package/dist/types/hashTree/types.d.ts +3 -0
  81. package/dist/types/hashTree/utils.d.ts +6 -0
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interceptors/constant.d.ts +5 -0
  84. package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
  85. package/dist/types/interceptors/index.d.ts +2 -1
  86. package/dist/types/interceptors/utils.d.ts +1 -0
  87. package/dist/types/locus-info/index.d.ts +21 -2
  88. package/dist/types/locus-info/types.d.ts +1 -0
  89. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  90. package/dist/types/media/properties.d.ts +2 -1
  91. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  92. package/dist/types/meeting/index.d.ts +38 -6
  93. package/dist/types/meeting/request.d.ts +16 -1
  94. package/dist/types/meeting/request.type.d.ts +5 -0
  95. package/dist/types/meeting/util.d.ts +31 -0
  96. package/dist/types/meetings/index.d.ts +4 -2
  97. package/dist/types/member/index.d.ts +1 -0
  98. package/dist/types/member/util.d.ts +5 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
  101. package/dist/types/reactions/reactions.type.d.ts +1 -0
  102. package/dist/types/webinar/utils.d.ts +6 -0
  103. package/dist/webinar/index.js +260 -90
  104. package/dist/webinar/index.js.map +1 -1
  105. package/dist/webinar/utils.js +25 -0
  106. package/dist/webinar/utils.js.map +1 -0
  107. package/package.json +24 -23
  108. package/src/aiEnableRequest/README.md +84 -0
  109. package/src/aiEnableRequest/index.ts +170 -0
  110. package/src/aiEnableRequest/utils.ts +25 -0
  111. package/src/annotation/index.ts +27 -7
  112. package/src/config.ts +3 -0
  113. package/src/constants.ts +29 -1
  114. package/src/hashTree/constants.ts +1 -0
  115. package/src/hashTree/hashTree.ts +17 -0
  116. package/src/hashTree/hashTreeParser.ts +627 -249
  117. package/src/hashTree/types.ts +4 -0
  118. package/src/hashTree/utils.ts +9 -0
  119. package/src/index.ts +8 -1
  120. package/src/interceptors/constant.ts +6 -0
  121. package/src/interceptors/dataChannelAuthToken.ts +170 -0
  122. package/src/interceptors/index.ts +2 -1
  123. package/src/interceptors/utils.ts +16 -0
  124. package/src/interpretation/index.ts +2 -2
  125. package/src/locus-info/controlsUtils.ts +11 -0
  126. package/src/locus-info/index.ts +231 -61
  127. package/src/locus-info/selfUtils.ts +1 -0
  128. package/src/locus-info/types.ts +1 -0
  129. package/src/media/MediaConnectionAwaiter.ts +41 -1
  130. package/src/media/properties.ts +3 -1
  131. package/src/meeting/in-meeting-actions.ts +12 -0
  132. package/src/meeting/index.ts +205 -44
  133. package/src/meeting/request.ts +42 -0
  134. package/src/meeting/request.type.ts +6 -0
  135. package/src/meeting/util.ts +160 -2
  136. package/src/meetings/index.ts +135 -41
  137. package/src/member/index.ts +10 -0
  138. package/src/member/util.ts +12 -0
  139. package/src/metrics/constants.ts +1 -0
  140. package/src/multistream/mediaRequestManager.ts +4 -54
  141. package/src/multistream/remoteMediaManager.ts +13 -0
  142. package/src/reachability/index.ts +9 -0
  143. package/src/reactions/reactions.type.ts +1 -0
  144. package/src/reconnection-manager/index.ts +0 -1
  145. package/src/webinar/index.ts +162 -5
  146. package/src/webinar/utils.ts +16 -0
  147. package/test/unit/spec/aiEnableRequest/index.ts +981 -0
  148. package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
  149. package/test/unit/spec/annotation/index.ts +69 -7
  150. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  151. package/test/unit/spec/hashTree/hashTreeParser.ts +1869 -189
  152. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
  153. package/test/unit/spec/interceptors/utils.ts +75 -0
  154. package/test/unit/spec/locus-info/controlsUtils.js +29 -0
  155. package/test/unit/spec/locus-info/index.js +383 -46
  156. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  157. package/test/unit/spec/media/properties.ts +12 -3
  158. package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
  159. package/test/unit/spec/meeting/index.js +716 -115
  160. package/test/unit/spec/meeting/request.js +70 -0
  161. package/test/unit/spec/meeting/utils.js +438 -26
  162. package/test/unit/spec/meetings/index.js +652 -31
  163. package/test/unit/spec/member/index.js +28 -4
  164. package/test/unit/spec/member/util.js +65 -27
  165. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
  166. package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
  167. package/test/unit/spec/reachability/index.ts +23 -0
  168. package/test/unit/spec/reconnection-manager/index.js +4 -8
  169. package/test/unit/spec/webinar/index.ts +348 -36
  170. package/test/unit/spec/webinar/utils.ts +39 -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 = {
@@ -79,6 +82,89 @@ const LocusObjectStateAfterUpdates = {
79
82
 
80
83
  type LocusObjectStateAfterUpdates = Enum<typeof LocusObjectStateAfterUpdates>;
81
84
 
85
+ /**
86
+ * Creates a locus object from the objects received in a hash tree message. It usually will be
87
+ * incomplete, because hash tree messages only contain the parts of locus that have changed,
88
+ * and some updates come separately over Mercury or LLM in separate messages.
89
+ *
90
+ * @param {HashTreeMessage} message hash tree message to created the locus from
91
+ * @returns {Object} the created locus object and metadata if present
92
+ */
93
+ export function createLocusFromHashTreeMessage(message: HashTreeMessage): {
94
+ locus: LocusDTO;
95
+ metadata?: Metadata;
96
+ } {
97
+ const locus: LocusDTO = {
98
+ participants: [],
99
+ url: message.locusUrl,
100
+ };
101
+ let metadata: Metadata | undefined;
102
+
103
+ if (!message.locusStateElements) {
104
+ return {locus, metadata};
105
+ }
106
+
107
+ for (const element of message.locusStateElements) {
108
+ if (!element.data) {
109
+ // eslint-disable-next-line no-continue
110
+ continue;
111
+ }
112
+
113
+ const type = element.htMeta.elementId.type.toLowerCase();
114
+
115
+ switch (type) {
116
+ case ObjectType.locus: {
117
+ // spread locus object data onto the top level, but remove keys managed by other ObjectTypes
118
+ const locusObjectData = {...element.data};
119
+
120
+ Object.values(ObjectTypeToLocusKeyMap).forEach((locusDtoKey) => {
121
+ delete locusObjectData[locusDtoKey];
122
+ });
123
+
124
+ Object.assign(locus, locusObjectData);
125
+ break;
126
+ }
127
+ case ObjectType.participant:
128
+ locus.participants.push(element.data);
129
+ break;
130
+ case ObjectType.mediaShare:
131
+ if (!locus.mediaShares) {
132
+ locus.mediaShares = [];
133
+ }
134
+ locus.mediaShares.push(element.data);
135
+ break;
136
+ case ObjectType.embeddedApp:
137
+ if (!locus.embeddedApps) {
138
+ locus.embeddedApps = [];
139
+ }
140
+ locus.embeddedApps.push(element.data);
141
+ break;
142
+ case ObjectType.control:
143
+ if (!locus.controls) {
144
+ locus.controls = {};
145
+ }
146
+ Object.assign(locus.controls, element.data);
147
+ break;
148
+ case ObjectType.links:
149
+ case ObjectType.info:
150
+ case ObjectType.fullState:
151
+ case ObjectType.self: {
152
+ const locusDtoKey = ObjectTypeToLocusKeyMap[type];
153
+ locus[locusDtoKey] = element.data;
154
+ break;
155
+ }
156
+ case ObjectType.metadata:
157
+ // metadata is not part of Locus DTO
158
+ metadata = {...element.data, htMeta: element.htMeta} as Metadata;
159
+ break;
160
+ default:
161
+ break;
162
+ }
163
+ }
164
+
165
+ return {locus, metadata};
166
+ }
167
+
82
168
  /**
83
169
  * @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
84
170
  * @export
@@ -239,7 +325,7 @@ export default class LocusInfo extends EventsScope {
239
325
  'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
240
326
  );
241
327
  }
242
- meeting.locusInfo.onFullLocus(res.body);
328
+ meeting.locusInfo.onFullLocus('classic Locus sync', res.body);
243
329
  })
244
330
  .catch((e) => {
245
331
  LoggerProxy.logger.info(
@@ -362,17 +448,21 @@ export default class LocusInfo extends EventsScope {
362
448
  */
363
449
  private createHashTreeParser({
364
450
  initialLocus,
451
+ metadata,
365
452
  }: {
366
453
  initialLocus: {
367
454
  dataSets: Array<DataSet>;
368
455
  locus: any;
369
456
  };
457
+ metadata: Metadata;
370
458
  }) {
371
459
  return new HashTreeParser({
372
460
  initialLocus,
461
+ metadata,
373
462
  webexRequest: this.webex.request.bind(this.webex),
374
463
  locusInfoUpdateCallback: this.updateFromHashTree.bind(this),
375
464
  debugId: `HT-${this.meetingId.substring(0, 4)}`,
465
+ excludedDataSets: this.webex.config.meetings.locus?.excludedDataSets,
376
466
  });
377
467
  }
378
468
 
@@ -387,6 +477,7 @@ export default class LocusInfo extends EventsScope {
387
477
  trigger: 'join-response';
388
478
  locus: LocusDTO;
389
479
  dataSets?: DataSet[];
480
+ metadata?: Metadata;
390
481
  }
391
482
  | {
392
483
  trigger: 'locus-message';
@@ -401,28 +492,36 @@ export default class LocusInfo extends EventsScope {
401
492
  switch (data.trigger) {
402
493
  case 'locus-message':
403
494
  if (data.hashTreeMessage) {
404
- // we need the SELF object to be in the received message, because it contains visibleDataSets
495
+ // we need the Metadata object to be in the received message, because it contains visibleDataSets
405
496
  // and these are needed to initialize all the hash trees
406
- const selfObject = data.hashTreeMessage.locusStateElements?.find((el) => isSelf(el));
497
+ const metadataObject = data.hashTreeMessage.locusStateElements?.find((el) =>
498
+ isMetadata(el)
499
+ );
407
500
 
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`
501
+ if (!metadataObject?.data?.visibleDataSets) {
502
+ // this is a common case (not an error)
503
+ // it happens for example after we leave the meeting and still get some heartbeats or delayed messages
504
+ LoggerProxy.logger.info(
505
+ `Locus-info:index#initialSetup --> cannot initialize HashTreeParser, Metadata object with visibleDataSets is missing in the message`
411
506
  );
412
507
 
413
- throw new Error('SELF object with visibleDataSets is missing in the message');
508
+ // throw so that handleLocusEvent() catches it and destroys the partially created meeting object
509
+ throw new Error('Metadata object with visibleDataSets is missing in the message');
414
510
  }
415
511
 
416
512
  LoggerProxy.logger.info(
417
513
  'Locus-info:index#initialSetup --> creating HashTreeParser from message'
418
514
  );
419
515
  // 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
516
  this.hashTreeParser = this.createHashTreeParser({
422
517
  initialLocus: {
423
- locus: {self: {visibleDataSets: selfObject.data.visibleDataSets}},
518
+ locus: null,
424
519
  dataSets: [], // empty, because they will be populated in initializeFromMessage() call // dataSets: data.hashTreeMessage.dataSets,
425
520
  },
521
+ metadata: {
522
+ htMeta: metadataObject.htMeta,
523
+ visibleDataSets: metadataObject.data.visibleDataSets,
524
+ },
426
525
  });
427
526
 
428
527
  // now handle the message - that should populate all the visible datasets
@@ -430,12 +529,12 @@ export default class LocusInfo extends EventsScope {
430
529
  } else {
431
530
  // "classic" Locus case, no hash trees involved
432
531
  this.updateLocusCache(data.locus);
433
- this.onFullLocus(data.locus, undefined);
532
+ this.onFullLocus('classic locus message', data.locus, undefined);
434
533
  }
435
534
  break;
436
535
  case 'join-response':
437
536
  this.updateLocusCache(data.locus);
438
- this.onFullLocus(data.locus, undefined, data.dataSets);
537
+ this.onFullLocus('join response', data.locus, undefined, data.dataSets, data.metadata);
439
538
  break;
440
539
  case 'get-loci-response':
441
540
  if (data.locus?.links?.resources?.visibleDataSets?.url) {
@@ -443,12 +542,12 @@ export default class LocusInfo extends EventsScope {
443
542
  'Locus-info:index#initialSetup --> creating HashTreeParser from get-loci-response'
444
543
  );
445
544
  // 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
545
  this.hashTreeParser = this.createHashTreeParser({
448
546
  initialLocus: {
449
- locus: {self: {visibleDataSets: data.locus?.self?.visibleDataSets}},
547
+ locus: null,
450
548
  dataSets: [], // empty, because we don't have them yet
451
549
  },
550
+ metadata: null, // get-loci-response doesn't contain Metadata object
452
551
  });
453
552
 
454
553
  // now initialize all the data
@@ -456,7 +555,7 @@ export default class LocusInfo extends EventsScope {
456
555
  } else {
457
556
  // "classic" Locus case, no hash trees involved
458
557
  this.updateLocusCache(data.locus);
459
- this.onFullLocus(data.locus, undefined);
558
+ this.onFullLocus('classic get-loci-response', data.locus, undefined);
460
559
  }
461
560
  }
462
561
  // Change it to true after it receives it first locus object
@@ -571,6 +670,31 @@ export default class LocusInfo extends EventsScope {
571
670
  );
572
671
  }
573
672
  break;
673
+ case ObjectType.embeddedApp:
674
+ if (object.data) {
675
+ LoggerProxy.logger.info(
676
+ `Locus-info:index#updateLocusFromHashTreeObject --> embeddedApp id=${object.htMeta.elementId.id} url='${object.data.url}' updated version=${object.htMeta.elementId.version}:`,
677
+ object.data
678
+ );
679
+ const existingEmbeddedApp = locus.embeddedApps?.find(
680
+ (ms) => ms.htMeta.elementId.id === object.htMeta.elementId.id
681
+ );
682
+
683
+ if (existingEmbeddedApp) {
684
+ Object.assign(existingEmbeddedApp, object.data);
685
+ } else {
686
+ locus.embeddedApps = locus.embeddedApps || [];
687
+ locus.embeddedApps.push(object.data);
688
+ }
689
+ } else {
690
+ LoggerProxy.logger.info(
691
+ `Locus-info:index#updateLocusFromHashTreeObject --> embeddedApp id=${object.htMeta.elementId.id} removed, version=${object.htMeta.elementId.version}`
692
+ );
693
+ locus.embeddedApps = locus.embeddedApps?.filter(
694
+ (ms) => ms.htMeta.elementId.id !== object.htMeta.elementId.id
695
+ );
696
+ }
697
+ break;
574
698
  case ObjectType.participant:
575
699
  LoggerProxy.logger.info(
576
700
  `Locus-info:index#updateLocusFromHashTreeObject --> participant id=${
@@ -643,6 +767,12 @@ export default class LocusInfo extends EventsScope {
643
767
  }
644
768
  }
645
769
  break;
770
+ case ObjectType.metadata:
771
+ LoggerProxy.logger.info(
772
+ `Locus-info:index#updateLocusFromHashTreeObject --> metadata object updated to version ${object.htMeta.elementId.version}`
773
+ );
774
+ // we don't use hash tree metadata right now for anything, it's mainly used internally by HashTreeParser
775
+ break;
646
776
  default:
647
777
  LoggerProxy.logger.warn(
648
778
  `Locus-info:index#updateLocusFromHashTreeObject --> received unsupported object type ${type}`
@@ -792,11 +922,14 @@ export default class LocusInfo extends EventsScope {
792
922
  }
793
923
 
794
924
  case LocusInfoUpdateType.MEETING_ENDED: {
795
- LoggerProxy.logger.info(
796
- `Locus-info:index#updateFromHashTree --> received signal that meeting ended, destroying meeting ${this.meetingId}`
797
- );
798
925
  const meeting = this.webex.meetings.meetingCollection.get(this.meetingId);
799
- this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.SELF_REMOVED);
926
+
927
+ if (meeting) {
928
+ LoggerProxy.logger.info(
929
+ `Locus-info:index#updateFromHashTree --> received signal that meeting ended, destroying meeting ${this.meetingId}`
930
+ );
931
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.SELF_REMOVED);
932
+ }
800
933
  }
801
934
  }
802
935
  }
@@ -815,8 +948,18 @@ export default class LocusInfo extends EventsScope {
815
948
  data.stateElementsMessage as HashTreeMessage
816
949
  );
817
950
  } else {
818
- // eslint-disable-next-line @typescript-eslint/no-shadow
819
951
  const {eventType} = data;
952
+
953
+ if (eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
954
+ // this can happen when we get an event before join http response
955
+ // it's OK to just ignore it
956
+ LoggerProxy.logger.info(
957
+ `Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created`
958
+ );
959
+
960
+ return;
961
+ }
962
+
820
963
  const locus = this.getTheLocusToUpdate(data.locus);
821
964
  LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
822
965
 
@@ -837,17 +980,11 @@ export default class LocusInfo extends EventsScope {
837
980
  case LOCUSEVENT.PARTICIPANT_DECLINED:
838
981
  case LOCUSEVENT.FLOOR_GRANTED:
839
982
  case LOCUSEVENT.FLOOR_RELEASED:
840
- this.onFullLocus(locus, eventType);
983
+ this.onFullLocus(`classic locus event ${eventType}`, locus, eventType);
841
984
  break;
842
985
  case LOCUSEVENT.DIFFERENCE:
843
986
  this.handleLocusDelta(locus, meeting);
844
987
  break;
845
- case LOCUSEVENT.HASH_TREE_DATA_UPDATED:
846
- this.sendClassicVsHashTreeMismatchMetric(
847
- meeting,
848
- `got ${eventType}, expected classic events`
849
- );
850
- break;
851
988
 
852
989
  default:
853
990
  // Why will there be a event with no eventType ????
@@ -871,22 +1008,35 @@ export default class LocusInfo extends EventsScope {
871
1008
  /**
872
1009
  * Function for handling full locus when it's using hash trees (so not the "classic" one).
873
1010
  *
1011
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
874
1012
  * @param {object} locus locus object
1013
+ * @param {object} metadata locus hash trees metadata
875
1014
  * @param {string} eventType locus event
876
1015
  * @param {DataSet[]} dataSets
877
1016
  * @returns {void}
878
1017
  */
879
- private onFullLocusWithHashTrees(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
1018
+ private onFullLocusWithHashTrees(
1019
+ debugText: string,
1020
+ locus: any,
1021
+ metadata: Metadata,
1022
+ eventType?: string,
1023
+ dataSets?: Array<DataSet>
1024
+ ) {
880
1025
  if (!this.hashTreeParser) {
881
- LoggerProxy.logger.info(`Locus-info:index#onFullLocus --> creating hash tree parser`);
882
1026
  LoggerProxy.logger.info(
883
- 'Locus-info:index#onFullLocus --> dataSets:',
1027
+ `Locus-info:index#onFullLocus (${debugText}) --> creating hash tree parser`
1028
+ );
1029
+ LoggerProxy.logger.info(
1030
+ `Locus-info:index#onFullLocus (${debugText}) --> dataSets:`,
884
1031
  dataSets,
885
1032
  ' and locus:',
886
- locus
1033
+ locus,
1034
+ ' and metadata:',
1035
+ metadata
887
1036
  );
888
1037
  this.hashTreeParser = this.createHashTreeParser({
889
1038
  initialLocus: {locus, dataSets},
1039
+ metadata,
890
1040
  });
891
1041
  this.onFullLocusCommon(locus, eventType);
892
1042
  } else {
@@ -894,23 +1044,24 @@ export default class LocusInfo extends EventsScope {
894
1044
  // so treat it like if we just got it in any api response
895
1045
 
896
1046
  LoggerProxy.logger.info(
897
- 'Locus-info:index#onFullLocus --> hash tree parser already exists, handling it like a normal API response'
1047
+ `Locus-info:index#onFullLocus (${debugText}) --> hash tree parser already exists, handling it like a normal API response`
898
1048
  );
899
- this.handleLocusAPIResponse(undefined, {dataSets, locus});
1049
+ this.handleLocusAPIResponse(undefined, {dataSets, locus, metadata});
900
1050
  }
901
1051
  }
902
1052
 
903
1053
  /**
904
1054
  * Function for handling full locus when it's the "classic" one (not hash trees)
905
1055
  *
1056
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
906
1057
  * @param {object} locus locus object
907
1058
  * @param {string} eventType locus event
908
1059
  * @returns {void}
909
1060
  */
910
- private onFullLocusClassic(locus: any, eventType?: string) {
1061
+ private onFullLocusClassic(debugText: string, locus: any, eventType?: string) {
911
1062
  if (!this.locusParser.isNewFullLocus(locus)) {
912
1063
  LoggerProxy.logger.info(
913
- `Locus-info:index#onFullLocus --> ignoring old full locus DTO, eventType=${eventType}`
1064
+ `Locus-info:index#onFullLocus (${debugText}) --> ignoring old full locus DTO, eventType=${eventType}`
914
1065
  );
915
1066
 
916
1067
  return;
@@ -920,24 +1071,37 @@ export default class LocusInfo extends EventsScope {
920
1071
 
921
1072
  /**
922
1073
  * updates the locus with full locus object
1074
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
923
1075
  * @param {object} locus locus object
924
1076
  * @param {string} eventType locus event
925
1077
  * @param {DataSet[]} dataSets
1078
+ * @param {object} metadata locus hash trees metadata
926
1079
  * @returns {object} null
927
1080
  * @memberof LocusInfo
928
1081
  */
929
- onFullLocus(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
1082
+ onFullLocus(
1083
+ debugText: string,
1084
+ locus: any,
1085
+ eventType?: string,
1086
+ dataSets?: Array<DataSet>,
1087
+ metadata?: Metadata
1088
+ ) {
930
1089
  if (!locus) {
931
1090
  LoggerProxy.logger.error(
932
- 'Locus-info:index#onFullLocus --> object passed as argument was invalid, continuing.'
1091
+ `Locus-info:index#onFullLocus (${debugText}) --> object passed as argument was invalid, continuing.`
933
1092
  );
934
1093
  }
935
1094
 
936
1095
  if (dataSets) {
1096
+ if (!metadata) {
1097
+ throw new Error(
1098
+ `Locus-info:index#onFullLocus (${debugText}) --> hash tree metadata is missing with full Locus`
1099
+ );
1100
+ }
937
1101
  // this is the new hashmap Locus DTO format (only applicable to webinars for now)
938
- this.onFullLocusWithHashTrees(locus, eventType, dataSets);
1102
+ this.onFullLocusWithHashTrees(debugText, locus, metadata, eventType, dataSets);
939
1103
  } else {
940
- this.onFullLocusClassic(locus, eventType);
1104
+ this.onFullLocusClassic(debugText, locus, eventType);
941
1105
  }
942
1106
  }
943
1107
 
@@ -1217,27 +1381,6 @@ export default class LocusInfo extends EventsScope {
1217
1381
  shouldLeave: false,
1218
1382
  }
1219
1383
  );
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
1384
  }
1242
1385
  // If you are guest and you are removed from the meeting
1243
1386
  // You wont get any further events
@@ -1373,6 +1516,7 @@ export default class LocusInfo extends EventsScope {
1373
1516
  hasMeetingContainerChanged,
1374
1517
  hasTranscribeChanged,
1375
1518
  hasHesiodLLMIdChanged,
1519
+ hasAiSummaryNotificationChanged,
1376
1520
  hasTranscribeSpokenLanguageChanged,
1377
1521
  hasManualCaptionChanged,
1378
1522
  hasEntryExitToneChanged,
@@ -1529,6 +1673,19 @@ export default class LocusInfo extends EventsScope {
1529
1673
  );
1530
1674
  }
1531
1675
 
1676
+ if (hasAiSummaryNotificationChanged) {
1677
+ this.emitScoped(
1678
+ {
1679
+ file: 'locus-info',
1680
+ function: 'updateControls',
1681
+ },
1682
+ LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
1683
+ {
1684
+ aiSummaryNotification: current.transcribe.aiSummaryNotification,
1685
+ }
1686
+ );
1687
+ }
1688
+
1532
1689
  if (hasTranscribeSpokenLanguageChanged) {
1533
1690
  const {spokenLanguage} = current.transcribe;
1534
1691
 
@@ -2038,6 +2195,19 @@ export default class LocusInfo extends EventsScope {
2038
2195
  );
2039
2196
  }
2040
2197
 
2198
+ if (parsedSelves.updates.selfIdChanged) {
2199
+ this.emitScoped(
2200
+ {
2201
+ file: 'locus-info',
2202
+ function: 'updateSelf',
2203
+ },
2204
+ LOCUSINFO.EVENTS.SELF_ID_CHANGED,
2205
+ {
2206
+ selfId: parsedSelves.current.selfId,
2207
+ }
2208
+ );
2209
+ }
2210
+
2041
2211
  if (parsedSelves.updates.interpretationChanged) {
2042
2212
  this.emitScoped(
2043
2213
  {
@@ -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();