@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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +307 -139
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +2 -1
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +10 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +55 -42
- package/dist/locus-info/index.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +33 -22
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +108 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +76 -26
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +47 -12
- package/dist/types/hashTree/types.d.ts +1 -0
- package/dist/types/hashTree/utils.d.ts +6 -0
- package/dist/types/locus-info/index.d.ts +9 -2
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +8 -5
- package/dist/types/meeting/util.d.ts +28 -0
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +294 -96
- package/src/hashTree/types.ts +1 -0
- package/src/hashTree/utils.ts +9 -0
- package/src/locus-info/index.ts +83 -35
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/index.ts +24 -11
- package/src/meeting/util.ts +132 -1
- package/src/meetings/index.ts +93 -8
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/reactions/reactions.type.ts +1 -0
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +942 -110
- package/test/unit/spec/locus-info/index.js +88 -17
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/index.js +160 -2
- package/test/unit/spec/meeting/utils.js +294 -22
- package/test/unit/spec/meetings/index.js +594 -17
package/src/hashTree/types.ts
CHANGED
package/src/hashTree/utils.ts
CHANGED
|
@@ -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
|
*
|
package/src/locus-info/index.ts
CHANGED
|
@@ -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 {
|
|
41
|
-
import {Links, LocusDTO
|
|
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
|
|
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
|
|
412
|
+
const metadataObject = data.hashTreeMessage.locusStateElements?.find((el) =>
|
|
413
|
+
isMetadata(el)
|
|
414
|
+
);
|
|
407
415
|
|
|
408
|
-
if (!
|
|
416
|
+
if (!metadataObject?.data?.visibleDataSets) {
|
|
409
417
|
LoggerProxy.logger.warn(
|
|
410
|
-
`Locus-info:index#initialSetup --> cannot initialize HashTreeParser,
|
|
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('
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
package/src/media/properties.ts
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();
|
package/src/meeting/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|