@webex/plugin-meetings 3.11.0-next.2 → 3.11.0-next.21

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 (64) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/hashTree/hashTree.js +18 -0
  4. package/dist/hashTree/hashTree.js.map +1 -1
  5. package/dist/hashTree/hashTreeParser.js +307 -139
  6. package/dist/hashTree/hashTreeParser.js.map +1 -1
  7. package/dist/hashTree/types.js +2 -1
  8. package/dist/hashTree/types.js.map +1 -1
  9. package/dist/hashTree/utils.js +10 -0
  10. package/dist/hashTree/utils.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/locus-info/index.js +55 -42
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/media/MediaConnectionAwaiter.js +57 -1
  16. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  17. package/dist/media/properties.js +4 -2
  18. package/dist/media/properties.js.map +1 -1
  19. package/dist/meeting/index.js +33 -22
  20. package/dist/meeting/index.js.map +1 -1
  21. package/dist/meeting/util.js +108 -2
  22. package/dist/meeting/util.js.map +1 -1
  23. package/dist/meetings/index.js +76 -26
  24. package/dist/meetings/index.js.map +1 -1
  25. package/dist/metrics/constants.js +2 -1
  26. package/dist/metrics/constants.js.map +1 -1
  27. package/dist/multistream/mediaRequestManager.js +1 -1
  28. package/dist/multistream/mediaRequestManager.js.map +1 -1
  29. package/dist/reactions/reactions.type.js.map +1 -1
  30. package/dist/types/hashTree/hashTree.d.ts +7 -0
  31. package/dist/types/hashTree/hashTreeParser.d.ts +47 -12
  32. package/dist/types/hashTree/types.d.ts +1 -0
  33. package/dist/types/hashTree/utils.d.ts +6 -0
  34. package/dist/types/locus-info/index.d.ts +9 -2
  35. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  36. package/dist/types/media/properties.d.ts +2 -1
  37. package/dist/types/meeting/index.d.ts +8 -5
  38. package/dist/types/meeting/util.d.ts +28 -0
  39. package/dist/types/meetings/index.d.ts +3 -1
  40. package/dist/types/metrics/constants.d.ts +1 -0
  41. package/dist/types/reactions/reactions.type.d.ts +1 -0
  42. package/dist/webinar/index.js +1 -1
  43. package/package.json +22 -22
  44. package/src/hashTree/hashTree.ts +17 -0
  45. package/src/hashTree/hashTreeParser.ts +294 -96
  46. package/src/hashTree/types.ts +1 -0
  47. package/src/hashTree/utils.ts +9 -0
  48. package/src/locus-info/index.ts +83 -35
  49. package/src/media/MediaConnectionAwaiter.ts +41 -1
  50. package/src/media/properties.ts +3 -1
  51. package/src/meeting/index.ts +24 -11
  52. package/src/meeting/util.ts +132 -1
  53. package/src/meetings/index.ts +93 -8
  54. package/src/metrics/constants.ts +1 -0
  55. package/src/multistream/mediaRequestManager.ts +1 -1
  56. package/src/reactions/reactions.type.ts +1 -0
  57. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  58. package/test/unit/spec/hashTree/hashTreeParser.ts +942 -110
  59. package/test/unit/spec/locus-info/index.js +88 -17
  60. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  61. package/test/unit/spec/media/properties.ts +12 -3
  62. package/test/unit/spec/meeting/index.js +160 -2
  63. package/test/unit/spec/meeting/utils.js +294 -22
  64. package/test/unit/spec/meetings/index.js +594 -17
@@ -10,6 +10,7 @@ export const ObjectType = {
10
10
  fullState: 'fullstate',
11
11
  links: 'links',
12
12
  control: 'controlentry',
13
+ metadata: 'metadata',
13
14
  } as const;
14
15
 
15
16
  export type ObjectType = Enum<typeof ObjectType>;
@@ -11,6 +11,15 @@ export function isSelf(object: HashTreeObject) {
11
11
  return object.htMeta.elementId.type.toLowerCase() === ObjectType.self;
12
12
  }
13
13
 
14
+ /**
15
+ * Checks if the given hash tree object is of type "Metadata"
16
+ * @param {HashTreeObject} object object to check
17
+ * @returns {boolean} True if the object is of type "Metadata", false otherwise
18
+ */
19
+ export function isMetadata(object: HashTreeObject) {
20
+ return object.htMeta.elementId.type.toLowerCase() === ObjectType.metadata;
21
+ }
22
+
14
23
  /**
15
24
  * Analyzes given part of Locus DTO recursively and delete any nested objects that have their own htMeta
16
25
  *
@@ -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: {
@@ -69,6 +70,7 @@ const LocusDtoTopLevelKeys = [
69
70
  export type LocusApiResponseBody = {
70
71
  dataSets?: DataSet[];
71
72
  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)
73
+ metadata?: Metadata;
72
74
  };
73
75
 
74
76
  const LocusObjectStateAfterUpdates = {
@@ -239,7 +241,7 @@ export default class LocusInfo extends EventsScope {
239
241
  'Locus-info:index#doLocusSync --> got full DTO when we asked for delta'
240
242
  );
241
243
  }
242
- meeting.locusInfo.onFullLocus(res.body);
244
+ meeting.locusInfo.onFullLocus('classic Locus sync', res.body);
243
245
  })
244
246
  .catch((e) => {
245
247
  LoggerProxy.logger.info(
@@ -362,14 +364,17 @@ export default class LocusInfo extends EventsScope {
362
364
  */
363
365
  private createHashTreeParser({
364
366
  initialLocus,
367
+ metadata,
365
368
  }: {
366
369
  initialLocus: {
367
370
  dataSets: Array<DataSet>;
368
371
  locus: any;
369
372
  };
373
+ metadata: Metadata;
370
374
  }) {
371
375
  return new HashTreeParser({
372
376
  initialLocus,
377
+ metadata,
373
378
  webexRequest: this.webex.request.bind(this.webex),
374
379
  locusInfoUpdateCallback: this.updateFromHashTree.bind(this),
375
380
  debugId: `HT-${this.meetingId.substring(0, 4)}`,
@@ -387,6 +392,7 @@ export default class LocusInfo extends EventsScope {
387
392
  trigger: 'join-response';
388
393
  locus: LocusDTO;
389
394
  dataSets?: DataSet[];
395
+ metadata?: Metadata;
390
396
  }
391
397
  | {
392
398
  trigger: 'locus-message';
@@ -401,28 +407,33 @@ export default class LocusInfo extends EventsScope {
401
407
  switch (data.trigger) {
402
408
  case 'locus-message':
403
409
  if (data.hashTreeMessage) {
404
- // we need the SELF object to be in the received message, because it contains visibleDataSets
410
+ // we need the Metadata object to be in the received message, because it contains visibleDataSets
405
411
  // and these are needed to initialize all the hash trees
406
- const selfObject = data.hashTreeMessage.locusStateElements?.find((el) => isSelf(el));
412
+ const metadataObject = data.hashTreeMessage.locusStateElements?.find((el) =>
413
+ isMetadata(el)
414
+ );
407
415
 
408
- if (!selfObject?.data?.visibleDataSets) {
416
+ if (!metadataObject?.data?.visibleDataSets) {
409
417
  LoggerProxy.logger.warn(
410
- `Locus-info:index#initialSetup --> cannot initialize HashTreeParser, SELF object with visibleDataSets is missing in the message`
418
+ `Locus-info:index#initialSetup --> cannot initialize HashTreeParser, Metadata object with visibleDataSets is missing in the message`
411
419
  );
412
420
 
413
- throw new Error('SELF object with visibleDataSets is missing in the message');
421
+ throw new Error('Metadata object with visibleDataSets is missing in the message');
414
422
  }
415
423
 
416
424
  LoggerProxy.logger.info(
417
425
  'Locus-info:index#initialSetup --> creating HashTreeParser from message'
418
426
  );
419
427
  // 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
428
  this.hashTreeParser = this.createHashTreeParser({
422
429
  initialLocus: {
423
- locus: {self: {visibleDataSets: selfObject.data.visibleDataSets}},
430
+ locus: null,
424
431
  dataSets: [], // empty, because they will be populated in initializeFromMessage() call // dataSets: data.hashTreeMessage.dataSets,
425
432
  },
433
+ metadata: {
434
+ htMeta: metadataObject.htMeta,
435
+ visibleDataSets: metadataObject.data.visibleDataSets,
436
+ },
426
437
  });
427
438
 
428
439
  // now handle the message - that should populate all the visible datasets
@@ -430,12 +441,12 @@ export default class LocusInfo extends EventsScope {
430
441
  } else {
431
442
  // "classic" Locus case, no hash trees involved
432
443
  this.updateLocusCache(data.locus);
433
- this.onFullLocus(data.locus, undefined);
444
+ this.onFullLocus('classic locus message', data.locus, undefined);
434
445
  }
435
446
  break;
436
447
  case 'join-response':
437
448
  this.updateLocusCache(data.locus);
438
- this.onFullLocus(data.locus, undefined, data.dataSets);
449
+ this.onFullLocus('join response', data.locus, undefined, data.dataSets, data.metadata);
439
450
  break;
440
451
  case 'get-loci-response':
441
452
  if (data.locus?.links?.resources?.visibleDataSets?.url) {
@@ -443,12 +454,12 @@ export default class LocusInfo extends EventsScope {
443
454
  'Locus-info:index#initialSetup --> creating HashTreeParser from get-loci-response'
444
455
  );
445
456
  // 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
457
  this.hashTreeParser = this.createHashTreeParser({
448
458
  initialLocus: {
449
- locus: {self: {visibleDataSets: data.locus?.self?.visibleDataSets}},
459
+ locus: null,
450
460
  dataSets: [], // empty, because we don't have them yet
451
461
  },
462
+ metadata: null, // get-loci-response doesn't contain Metadata object
452
463
  });
453
464
 
454
465
  // now initialize all the data
@@ -456,7 +467,7 @@ export default class LocusInfo extends EventsScope {
456
467
  } else {
457
468
  // "classic" Locus case, no hash trees involved
458
469
  this.updateLocusCache(data.locus);
459
- this.onFullLocus(data.locus, undefined);
470
+ this.onFullLocus('classic get-loci-response', data.locus, undefined);
460
471
  }
461
472
  }
462
473
  // Change it to true after it receives it first locus object
@@ -643,6 +654,12 @@ export default class LocusInfo extends EventsScope {
643
654
  }
644
655
  }
645
656
  break;
657
+ case ObjectType.metadata:
658
+ LoggerProxy.logger.info(
659
+ `Locus-info:index#updateLocusFromHashTreeObject --> metadata object updated to version ${object.htMeta.elementId.version}`
660
+ );
661
+ // we don't use hash tree metadata right now for anything, it's mainly used internally by HashTreeParser
662
+ break;
646
663
  default:
647
664
  LoggerProxy.logger.warn(
648
665
  `Locus-info:index#updateLocusFromHashTreeObject --> received unsupported object type ${type}`
@@ -815,8 +832,18 @@ export default class LocusInfo extends EventsScope {
815
832
  data.stateElementsMessage as HashTreeMessage
816
833
  );
817
834
  } else {
818
- // eslint-disable-next-line @typescript-eslint/no-shadow
819
835
  const {eventType} = data;
836
+
837
+ if (eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
838
+ // this can happen when we get an event before join http response
839
+ // it's OK to just ignore it
840
+ LoggerProxy.logger.info(
841
+ `Locus-info:index#parse --> received locus hash tree event before hashTreeParser is created`
842
+ );
843
+
844
+ return;
845
+ }
846
+
820
847
  const locus = this.getTheLocusToUpdate(data.locus);
821
848
  LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
822
849
 
@@ -837,17 +864,11 @@ export default class LocusInfo extends EventsScope {
837
864
  case LOCUSEVENT.PARTICIPANT_DECLINED:
838
865
  case LOCUSEVENT.FLOOR_GRANTED:
839
866
  case LOCUSEVENT.FLOOR_RELEASED:
840
- this.onFullLocus(locus, eventType);
867
+ this.onFullLocus(`classic locus event ${eventType}`, locus, eventType);
841
868
  break;
842
869
  case LOCUSEVENT.DIFFERENCE:
843
870
  this.handleLocusDelta(locus, meeting);
844
871
  break;
845
- case LOCUSEVENT.HASH_TREE_DATA_UPDATED:
846
- this.sendClassicVsHashTreeMismatchMetric(
847
- meeting,
848
- `got ${eventType}, expected classic events`
849
- );
850
- break;
851
872
 
852
873
  default:
853
874
  // Why will there be a event with no eventType ????
@@ -871,22 +892,35 @@ export default class LocusInfo extends EventsScope {
871
892
  /**
872
893
  * Function for handling full locus when it's using hash trees (so not the "classic" one).
873
894
  *
895
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
874
896
  * @param {object} locus locus object
897
+ * @param {object} metadata locus hash trees metadata
875
898
  * @param {string} eventType locus event
876
899
  * @param {DataSet[]} dataSets
877
900
  * @returns {void}
878
901
  */
879
- private onFullLocusWithHashTrees(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
902
+ private onFullLocusWithHashTrees(
903
+ debugText: string,
904
+ locus: any,
905
+ metadata: Metadata,
906
+ eventType?: string,
907
+ dataSets?: Array<DataSet>
908
+ ) {
880
909
  if (!this.hashTreeParser) {
881
- LoggerProxy.logger.info(`Locus-info:index#onFullLocus --> creating hash tree parser`);
882
910
  LoggerProxy.logger.info(
883
- 'Locus-info:index#onFullLocus --> dataSets:',
911
+ `Locus-info:index#onFullLocus (${debugText}) --> creating hash tree parser`
912
+ );
913
+ LoggerProxy.logger.info(
914
+ `Locus-info:index#onFullLocus (${debugText}) --> dataSets:`,
884
915
  dataSets,
885
916
  ' and locus:',
886
- locus
917
+ locus,
918
+ ' and metadata:',
919
+ metadata
887
920
  );
888
921
  this.hashTreeParser = this.createHashTreeParser({
889
922
  initialLocus: {locus, dataSets},
923
+ metadata,
890
924
  });
891
925
  this.onFullLocusCommon(locus, eventType);
892
926
  } else {
@@ -894,23 +928,24 @@ export default class LocusInfo extends EventsScope {
894
928
  // so treat it like if we just got it in any api response
895
929
 
896
930
  LoggerProxy.logger.info(
897
- 'Locus-info:index#onFullLocus --> hash tree parser already exists, handling it like a normal API response'
931
+ `Locus-info:index#onFullLocus (${debugText}) --> hash tree parser already exists, handling it like a normal API response`
898
932
  );
899
- this.handleLocusAPIResponse(undefined, {dataSets, locus});
933
+ this.handleLocusAPIResponse(undefined, {dataSets, locus, metadata});
900
934
  }
901
935
  }
902
936
 
903
937
  /**
904
938
  * Function for handling full locus when it's the "classic" one (not hash trees)
905
939
  *
940
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
906
941
  * @param {object} locus locus object
907
942
  * @param {string} eventType locus event
908
943
  * @returns {void}
909
944
  */
910
- private onFullLocusClassic(locus: any, eventType?: string) {
945
+ private onFullLocusClassic(debugText: string, locus: any, eventType?: string) {
911
946
  if (!this.locusParser.isNewFullLocus(locus)) {
912
947
  LoggerProxy.logger.info(
913
- `Locus-info:index#onFullLocus --> ignoring old full locus DTO, eventType=${eventType}`
948
+ `Locus-info:index#onFullLocus (${debugText}) --> ignoring old full locus DTO, eventType=${eventType}`
914
949
  );
915
950
 
916
951
  return;
@@ -920,24 +955,37 @@ export default class LocusInfo extends EventsScope {
920
955
 
921
956
  /**
922
957
  * updates the locus with full locus object
958
+ * @param {string} debugText string explaining the trigger for this call, added to logs for debugging purposes
923
959
  * @param {object} locus locus object
924
960
  * @param {string} eventType locus event
925
961
  * @param {DataSet[]} dataSets
962
+ * @param {object} metadata locus hash trees metadata
926
963
  * @returns {object} null
927
964
  * @memberof LocusInfo
928
965
  */
929
- onFullLocus(locus: any, eventType?: string, dataSets?: Array<DataSet>) {
966
+ onFullLocus(
967
+ debugText: string,
968
+ locus: any,
969
+ eventType?: string,
970
+ dataSets?: Array<DataSet>,
971
+ metadata?: Metadata
972
+ ) {
930
973
  if (!locus) {
931
974
  LoggerProxy.logger.error(
932
- 'Locus-info:index#onFullLocus --> object passed as argument was invalid, continuing.'
975
+ `Locus-info:index#onFullLocus (${debugText}) --> object passed as argument was invalid, continuing.`
933
976
  );
934
977
  }
935
978
 
936
979
  if (dataSets) {
980
+ if (!metadata) {
981
+ throw new Error(
982
+ `Locus-info:index#onFullLocus (${debugText}) --> hash tree metadata is missing with full Locus`
983
+ );
984
+ }
937
985
  // this is the new hashmap Locus DTO format (only applicable to webinars for now)
938
- this.onFullLocusWithHashTrees(locus, eventType, dataSets);
986
+ this.onFullLocusWithHashTrees(debugText, locus, metadata, eventType, dataSets);
939
987
  } else {
940
- this.onFullLocusClassic(locus, eventType);
988
+ this.onFullLocusClassic(debugText, locus, eventType);
941
989
  }
942
990
  }
943
991
 
@@ -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();
@@ -13,7 +13,7 @@ import {
13
13
  CALL_DIAGNOSTIC_CONFIG,
14
14
  RtcMetrics,
15
15
  } from '@webex/internal-plugin-metrics';
16
- import {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
16
+ import type {ClientEvent as RawClientEvent} from '@webex/event-dictionary-ts';
17
17
 
18
18
  import {
19
19
  ConnectionState,
@@ -179,7 +179,7 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
179
179
  import {ReachabilityMetrics} from '../reachability/reachability.types';
180
180
  import {SetStageOptions, SetStageVideoLayout, UnsetStageVideoLayout} from './request.type';
181
181
  import {Invitee} from './type';
182
- import {DataSet} from '../hashTree/hashTreeParser';
182
+ import {DataSet, Metadata} from '../hashTree/hashTreeParser';
183
183
  import {LocusDTO} from '../locus-info/types';
184
184
 
185
185
  // default callback so we don't call an undefined function, but in practice it should never be used
@@ -250,6 +250,7 @@ export type AddMediaOptions = {
250
250
  remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
251
251
  bundlePolicy?: BundlePolicy; // applies only to multistream meetings
252
252
  allowMediaInLobby?: boolean; // allows adding media when in the lobby
253
+ allowPublishMediaInLobby?: boolean; // allows publishing media when in the lobby, if not specified, default value false is used
253
254
  additionalMediaOptions?: AdditionalMediaOptions; // allows adding additional options like send/receive audio/video
254
255
  };
255
256
 
@@ -623,6 +624,13 @@ export default class Meeting extends StatelessWebexPlugin {
623
624
  keepAliveTimerId: NodeJS.Timeout;
624
625
  lastVideoLayoutInfo: any;
625
626
  locusInfo: any;
627
+ // this group of properties is populated via updateMeetingObject() that's registered as a callback with LocusInfo
628
+ isUserUnadmitted?: boolean;
629
+ joinedWith?: any;
630
+ selfId?: string;
631
+ roles: any[];
632
+ // ... there is more ... see SelfUtils.parse()
633
+ // end of the group
626
634
  locusMediaRequest?: LocusMediaRequest;
627
635
  mediaProperties: MediaProperties;
628
636
  mediaRequestManagers: {
@@ -657,7 +665,6 @@ export default class Meeting extends StatelessWebexPlugin {
657
665
  endCallInitJoinReq: any;
658
666
  endJoinReqResp: any;
659
667
  endLocalSDPGenRemoteSDPRecvDelay: any;
660
- joinedWith: any;
661
668
  locusId: any;
662
669
  startCallInitJoinReq: any;
663
670
  startJoinReqResp: any;
@@ -672,12 +679,10 @@ export default class Meeting extends StatelessWebexPlugin {
672
679
  permissionTokenReceivedLocalTime: number;
673
680
  resourceId: any;
674
681
  resourceUrl: string;
675
- selfId: string;
676
682
  state: any;
677
683
  localAudioStreamMuteStateHandler: () => void;
678
684
  localVideoStreamMuteStateHandler: () => void;
679
685
  localOutputTrackChangeHandler: () => void;
680
- roles: any[];
681
686
  environment: string;
682
687
  namespace = MEETINGS;
683
688
  allowMediaInLobby: boolean;
@@ -4593,7 +4598,8 @@ export default class Meeting extends StatelessWebexPlugin {
4593
4598
  mediaId: string;
4594
4599
  host: object;
4595
4600
  selfId: string;
4596
- dataSets: DataSet[];
4601
+ dataSets: DataSet[]; // only sent by Locus when hash trees are used
4602
+ metadata: Metadata; // only sent by Locus when hash trees are used
4597
4603
  }) {
4598
4604
  const mtgLocus: any = data.locus;
4599
4605
 
@@ -4609,6 +4615,7 @@ export default class Meeting extends StatelessWebexPlugin {
4609
4615
  trigger: 'join-response',
4610
4616
  locus: mtgLocus,
4611
4617
  dataSets: data.dataSets,
4618
+ metadata: data.metadata,
4612
4619
  });
4613
4620
  }
4614
4621
 
@@ -5781,7 +5788,7 @@ export default class Meeting extends StatelessWebexPlugin {
5781
5788
  this.isReactionsSupported()
5782
5789
  ) {
5783
5790
  const member = this.members.membersCollection.get(e.data.sender.participantId);
5784
- if (!member) {
5791
+ if (!member && !this.locusInfo?.info?.isWebinar) {
5785
5792
  // @ts-ignore -- fix type
5786
5793
  LoggerProxy.logger.warn(
5787
5794
  `Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
@@ -5789,7 +5796,7 @@ export default class Meeting extends StatelessWebexPlugin {
5789
5796
  break;
5790
5797
  }
5791
5798
 
5792
- const {name} = member;
5799
+ const name = (member && member.name) || e.data.sender.displayName;
5793
5800
  const processedReaction: ProcessedReaction = {
5794
5801
  reaction: e.data.reaction,
5795
5802
  sender: {
@@ -7433,7 +7440,7 @@ export default class Meeting extends StatelessWebexPlugin {
7433
7440
  */
7434
7441
  private async waitForMediaConnectionConnected(): Promise<void> {
7435
7442
  try {
7436
- await this.mediaProperties.waitForMediaConnectionConnected();
7443
+ await this.mediaProperties.waitForMediaConnectionConnected(this.correlationId);
7437
7444
  } catch (error) {
7438
7445
  const {iceConnected} = error;
7439
7446
 
@@ -8026,6 +8033,7 @@ export default class Meeting extends StatelessWebexPlugin {
8026
8033
  remoteMediaManagerConfig,
8027
8034
  bundlePolicy = 'max-bundle',
8028
8035
  additionalMediaOptions = {},
8036
+ allowPublishMediaInLobby = false,
8029
8037
  } = options;
8030
8038
 
8031
8039
  const {
@@ -8046,7 +8054,6 @@ export default class Meeting extends StatelessWebexPlugin {
8046
8054
  const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
8047
8055
 
8048
8056
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
8049
- // @ts-ignore - isUserUnadmitted coming from SelfUtil
8050
8057
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
8051
8058
  throw new UserInLobbyError();
8052
8059
  }
@@ -8091,7 +8098,13 @@ export default class Meeting extends StatelessWebexPlugin {
8091
8098
  this.brbState = createBrbState(this, false);
8092
8099
 
8093
8100
  try {
8094
- await this.setUpLocalStreamReferences(localStreams);
8101
+ // if we're in a lobby and allowPublishMediaInLobby==false, we don't want to
8102
+ // setup local streams for publishing, because if we ever end up admitted to the meeting
8103
+ // but Locus event about it for us is delayed or missed, others could see/hear our user's video/audio
8104
+ // while the user would still think they're in the lobby
8105
+ if (allowPublishMediaInLobby || !this.isUserUnadmitted) {
8106
+ await this.setUpLocalStreamReferences(localStreams);
8107
+ }
8095
8108
 
8096
8109
  this.setMercuryListener();
8097
8110