@webex/plugin-meetings 2.60.1-next.1 → 2.60.1-next.10
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/README.md +12 -0
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.d.ts +18 -4
- package/dist/constants.js +23 -9
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.d.ts +1 -1
- package/dist/locus-info/index.js +8 -8
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.d.ts +119 -31
- package/dist/meeting/index.js +1021 -805
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +25 -18
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.d.ts +16 -0
- package/dist/meeting/util.js +71 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting/voicea-meeting.d.ts +20 -0
- package/dist/meeting/voicea-meeting.js +201 -0
- package/dist/meeting/voicea-meeting.js.map +1 -0
- package/dist/meetings/index.d.ts +25 -3
- package/dist/meetings/index.js +83 -32
- package/dist/meetings/index.js.map +1 -1
- package/dist/reachability/index.js +11 -6
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +3 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +50 -54
- package/dist/roap/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +1 -1
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +13 -10
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -21
- package/src/constants.ts +22 -4
- package/src/locus-info/index.ts +13 -12
- package/src/meeting/index.ts +546 -276
- package/src/meeting/request.ts +7 -0
- package/src/meeting/util.ts +97 -0
- package/src/meeting/voicea-meeting.ts +161 -0
- package/src/meetings/index.ts +59 -18
- package/src/reachability/index.ts +7 -4
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +49 -51
- package/src/statsAnalyzer/index.ts +2 -2
- package/src/statsAnalyzer/mqaUtil.ts +15 -14
- package/test/unit/spec/locus-info/index.js +53 -5
- package/test/unit/spec/meeting/index.js +1792 -1139
- package/test/unit/spec/meeting/request.js +22 -12
- package/test/unit/spec/meeting/utils.js +93 -0
- package/test/unit/spec/meetings/index.js +180 -21
- package/test/unit/spec/reachability/index.ts +2 -1
- package/test/unit/spec/reconnection-manager/index.js +1 -0
- package/test/unit/spec/roap/index.ts +28 -42
- package/test/unit/spec/stats-analyzer/index.js +415 -30
package/src/meeting/request.ts
CHANGED
|
@@ -185,6 +185,13 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
|
185
185
|
deviceCapabilities.push(ANNOTATION.ANNOTATION_ON_SHARE_SUPPORTED);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// append installationId to device config if it exists
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
if (this.webex.internal.device.config.installationId) {
|
|
191
|
+
// @ts-ignore
|
|
192
|
+
body.device.installationId = this.webex.internal.device.config.installationId;
|
|
193
|
+
}
|
|
194
|
+
|
|
188
195
|
if (locale) {
|
|
189
196
|
body.locale = locale;
|
|
190
197
|
}
|
package/src/meeting/util.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
FULL_STATE,
|
|
14
14
|
SELF_POLICY,
|
|
15
15
|
EVENT_TRIGGERS,
|
|
16
|
+
LOCAL_SHARE_ERRORS,
|
|
16
17
|
IP_VERSION,
|
|
17
18
|
} from '../constants';
|
|
18
19
|
import BrowserDetection from '../common/browser-detection';
|
|
@@ -692,6 +693,102 @@ const MeetingUtil = {
|
|
|
692
693
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
693
694
|
);
|
|
694
695
|
},
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Returns a CA-recognized error payload for the specified raw error message/reason.
|
|
699
|
+
*
|
|
700
|
+
* New errors can be added to this function for handling in the future
|
|
701
|
+
*
|
|
702
|
+
* @param {String} reason the raw error message
|
|
703
|
+
* @returns {Array<object>} an array of payload objects
|
|
704
|
+
*/
|
|
705
|
+
getChangeMeetingFloorErrorPayload: (reason: string) => {
|
|
706
|
+
const errorPayload = {
|
|
707
|
+
errorDescription: reason,
|
|
708
|
+
name: 'locus.response',
|
|
709
|
+
shownToUser: false,
|
|
710
|
+
};
|
|
711
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.UNDEFINED)) {
|
|
712
|
+
return [
|
|
713
|
+
{
|
|
714
|
+
...errorPayload,
|
|
715
|
+
fatal: true,
|
|
716
|
+
category: 'signaling',
|
|
717
|
+
errorCode: 1100,
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
}
|
|
721
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.DEVICE_NOT_JOINED)) {
|
|
722
|
+
return [
|
|
723
|
+
{
|
|
724
|
+
...errorPayload,
|
|
725
|
+
fatal: true,
|
|
726
|
+
category: 'signaling',
|
|
727
|
+
errorCode: 4050,
|
|
728
|
+
},
|
|
729
|
+
];
|
|
730
|
+
}
|
|
731
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.NO_MEDIA_FOR_DEVICE)) {
|
|
732
|
+
return [
|
|
733
|
+
{
|
|
734
|
+
...errorPayload,
|
|
735
|
+
fatal: true,
|
|
736
|
+
category: 'media',
|
|
737
|
+
errorCode: 2048,
|
|
738
|
+
},
|
|
739
|
+
];
|
|
740
|
+
}
|
|
741
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.NO_CONFLUENCE_ID)) {
|
|
742
|
+
return [
|
|
743
|
+
{
|
|
744
|
+
...errorPayload,
|
|
745
|
+
fatal: true,
|
|
746
|
+
category: 'signaling',
|
|
747
|
+
errorCode: 4064,
|
|
748
|
+
},
|
|
749
|
+
];
|
|
750
|
+
}
|
|
751
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.CONTENT_SHARING_DISABLED)) {
|
|
752
|
+
return [
|
|
753
|
+
{
|
|
754
|
+
...errorPayload,
|
|
755
|
+
fatal: true,
|
|
756
|
+
category: 'expected',
|
|
757
|
+
errorCode: 4065,
|
|
758
|
+
},
|
|
759
|
+
];
|
|
760
|
+
}
|
|
761
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.LOCUS_PARTICIPANT_DNE)) {
|
|
762
|
+
return [
|
|
763
|
+
{
|
|
764
|
+
...errorPayload,
|
|
765
|
+
fatal: true,
|
|
766
|
+
category: 'signaling',
|
|
767
|
+
errorCode: 4066,
|
|
768
|
+
},
|
|
769
|
+
];
|
|
770
|
+
}
|
|
771
|
+
if (reason.includes(LOCAL_SHARE_ERRORS.CONTENT_REQUEST_WHILE_PENDING_WHITEBOARD)) {
|
|
772
|
+
return [
|
|
773
|
+
{
|
|
774
|
+
...errorPayload,
|
|
775
|
+
fatal: true,
|
|
776
|
+
category: 'expected',
|
|
777
|
+
errorCode: 4067,
|
|
778
|
+
},
|
|
779
|
+
];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// return unknown error
|
|
783
|
+
return [
|
|
784
|
+
{
|
|
785
|
+
...errorPayload,
|
|
786
|
+
fatal: true,
|
|
787
|
+
category: 'signaling',
|
|
788
|
+
errorCode: 1100,
|
|
789
|
+
},
|
|
790
|
+
];
|
|
791
|
+
},
|
|
695
792
|
};
|
|
696
793
|
|
|
697
794
|
export default MeetingUtil;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export const getSpeaker = (members, csis = []) =>
|
|
2
|
+
Object.values(members).find((member: any) => {
|
|
3
|
+
const memberCSIs = member.participant.status.csis ?? [];
|
|
4
|
+
|
|
5
|
+
return csis.some((csi) => memberCSIs.includes(csi));
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const getSpeakerFromProxyOrStore = ({csisKey, meetingMembers, transcriptData}) => {
|
|
9
|
+
let speaker = {
|
|
10
|
+
speakerId: '',
|
|
11
|
+
name: '',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let needsCaching = false;
|
|
15
|
+
|
|
16
|
+
if (csisKey && transcriptData.speakerProxy[csisKey]) {
|
|
17
|
+
speaker = transcriptData.speakerProxy[csisKey];
|
|
18
|
+
}
|
|
19
|
+
const meetingMember: any = getSpeaker(meetingMembers, [csisKey]);
|
|
20
|
+
|
|
21
|
+
const speakerInStore = {
|
|
22
|
+
speakerId: meetingMember?.participant.person.id ?? '',
|
|
23
|
+
name: meetingMember?.participant.person.name ?? '',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
meetingMember &&
|
|
28
|
+
(speakerInStore.speakerId !== speaker.speakerId || speakerInStore.name !== speaker.name)
|
|
29
|
+
) {
|
|
30
|
+
needsCaching = true;
|
|
31
|
+
speaker = speakerInStore;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {speaker, needsCaching};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const processNewCaptions = ({data, meeting}) => {
|
|
38
|
+
const {transcriptId} = data;
|
|
39
|
+
const transcriptData = meeting.transcription;
|
|
40
|
+
|
|
41
|
+
if (data.isFinal) {
|
|
42
|
+
const doesInterimTranscriptionExist = transcriptId in transcriptData.interimCaptions;
|
|
43
|
+
|
|
44
|
+
if (doesInterimTranscriptionExist) {
|
|
45
|
+
transcriptData.interimCaptions[transcriptId].forEach((fakeId) => {
|
|
46
|
+
const fakeTranscriptIndex = transcriptData.captions.findIndex(
|
|
47
|
+
(transcript) => transcript.id === fakeId
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (fakeTranscriptIndex !== -1) {
|
|
51
|
+
transcriptData.captions.splice(fakeTranscriptIndex, 1);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
delete transcriptData.interimCaptions[transcriptId];
|
|
55
|
+
}
|
|
56
|
+
const csisKey = data.transcript?.csis[0];
|
|
57
|
+
|
|
58
|
+
const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
|
|
59
|
+
meetingMembers: meeting.members.membersCollection.members,
|
|
60
|
+
transcriptData,
|
|
61
|
+
csisKey,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (needsCaching) {
|
|
65
|
+
transcriptData.speakerProxy[csisKey] = speaker;
|
|
66
|
+
}
|
|
67
|
+
const captionData = {
|
|
68
|
+
id: transcriptId,
|
|
69
|
+
isFinal: data.isFinal,
|
|
70
|
+
translations: data.translations,
|
|
71
|
+
text: data.transcript?.text,
|
|
72
|
+
currentSpokenLanguage: data.transcript?.transcriptLanguageCode,
|
|
73
|
+
timestamp: data.timestamp,
|
|
74
|
+
speaker,
|
|
75
|
+
};
|
|
76
|
+
transcriptData.captions.push(captionData);
|
|
77
|
+
}
|
|
78
|
+
const {transcripts = []} = data;
|
|
79
|
+
const transcriptsPerCsis = new Map();
|
|
80
|
+
|
|
81
|
+
for (const transcript of transcripts) {
|
|
82
|
+
const {
|
|
83
|
+
text,
|
|
84
|
+
transcript_language_code: currentSpokenLanguage,
|
|
85
|
+
csis: [csisMember],
|
|
86
|
+
} = transcript;
|
|
87
|
+
|
|
88
|
+
const newCaption = `${transcriptsPerCsis.get(csisMember)?.text ?? ''} ${text}`;
|
|
89
|
+
|
|
90
|
+
// eslint-disable-next-line camelcase
|
|
91
|
+
transcriptsPerCsis.set(csisMember, {text: newCaption, currentSpokenLanguage});
|
|
92
|
+
}
|
|
93
|
+
const fakeTranscriptionIds = [];
|
|
94
|
+
|
|
95
|
+
for (const [key, value] of transcriptsPerCsis) {
|
|
96
|
+
const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
|
|
97
|
+
meetingMembers: meeting.members.membersCollection.members,
|
|
98
|
+
transcriptData,
|
|
99
|
+
csisKey: key,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (needsCaching) {
|
|
103
|
+
transcriptData.speakerProxy[key] = speaker;
|
|
104
|
+
}
|
|
105
|
+
const {speakerId} = speaker;
|
|
106
|
+
const fakeId = `${transcriptId}_${speakerId}`;
|
|
107
|
+
const captionData = {
|
|
108
|
+
id: fakeId,
|
|
109
|
+
isFinal: data.isFinal,
|
|
110
|
+
translations: value.translations,
|
|
111
|
+
text: value.text,
|
|
112
|
+
currentCaptionLanguage: value.currentSpokenLanguage,
|
|
113
|
+
timestamp: value?.timestamp,
|
|
114
|
+
speaker,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const fakeTranscriptIndex = transcriptData.captions.findIndex(
|
|
118
|
+
(transcript) => transcript.id === fakeId
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (fakeTranscriptIndex !== -1) {
|
|
122
|
+
transcriptData.captions.splice(fakeTranscriptIndex, 1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fakeTranscriptionIds.push(fakeId);
|
|
126
|
+
transcriptData.captions.push(captionData);
|
|
127
|
+
}
|
|
128
|
+
transcriptData.interimCaptions[transcriptId] = fakeTranscriptionIds;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const processHighlightCreated = ({data, meeting}) => {
|
|
132
|
+
const transcriptData = meeting.transcription;
|
|
133
|
+
|
|
134
|
+
if (!transcriptData.highlights) {
|
|
135
|
+
transcriptData.highlights = [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const csisKey = data.csis && data.csis.length > 0 ? data.csis[0] : undefined;
|
|
139
|
+
const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
|
|
140
|
+
meetingMembers: meeting.members.membersCollection.members,
|
|
141
|
+
transcriptData,
|
|
142
|
+
csisKey,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (needsCaching) {
|
|
146
|
+
transcriptData.speakerProxy[csisKey] = speaker;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const highlightCreated = {
|
|
150
|
+
id: data.highlightId,
|
|
151
|
+
meta: {
|
|
152
|
+
label: data.highlightLabel,
|
|
153
|
+
source: data.highlightSource,
|
|
154
|
+
},
|
|
155
|
+
text: data.text,
|
|
156
|
+
timestamp: data.timestamp,
|
|
157
|
+
speaker,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
meeting.transcription.highlights.push(highlightCreated);
|
|
161
|
+
};
|
package/src/meetings/index.ts
CHANGED
|
@@ -584,7 +584,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
584
584
|
|
|
585
585
|
// @ts-ignore
|
|
586
586
|
this.webex.internal.mercury.on(ONLINE, () => {
|
|
587
|
-
this.syncMeetings();
|
|
587
|
+
this.syncMeetings({keepOnlyLocusMeetings: false});
|
|
588
588
|
});
|
|
589
589
|
|
|
590
590
|
// @ts-ignore
|
|
@@ -699,6 +699,24 @@ export default class Meetings extends WebexPlugin {
|
|
|
699
699
|
}
|
|
700
700
|
}
|
|
701
701
|
|
|
702
|
+
/**
|
|
703
|
+
* API to toggle TCP reachability, needs to be called before webex.meetings.register()
|
|
704
|
+
* @param {Boolean} newValue
|
|
705
|
+
* @private
|
|
706
|
+
* @memberof Meetings
|
|
707
|
+
* @returns {undefined}
|
|
708
|
+
*/
|
|
709
|
+
private _toggleTcpReachability(newValue: boolean) {
|
|
710
|
+
if (typeof newValue !== 'boolean') {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
// @ts-ignore
|
|
714
|
+
if (this.config.experimental.enableTcpReachability !== newValue) {
|
|
715
|
+
// @ts-ignore
|
|
716
|
+
this.config.experimental.enableTcpReachability = newValue;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
702
720
|
/**
|
|
703
721
|
* Explicitly sets up the meetings plugin by registering
|
|
704
722
|
* the device, connecting to mercury, and listening for locus events.
|
|
@@ -1026,6 +1044,9 @@ export default class Meetings extends WebexPlugin {
|
|
|
1026
1044
|
|
|
1027
1045
|
/**
|
|
1028
1046
|
* Create a meeting or return an existing meeting.
|
|
1047
|
+
*
|
|
1048
|
+
* When meeting info passed it should be complete, e.g.: fetched after password or captcha provided
|
|
1049
|
+
*
|
|
1029
1050
|
* @param {string} destination - sipURL, phonenumber, or locus object}
|
|
1030
1051
|
* @param {string} [type] - the optional specified type, such as locusId
|
|
1031
1052
|
* @param {Boolean} useRandomDelayForInfo - whether a random delay should be added to fetching meeting info
|
|
@@ -1033,6 +1054,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1033
1054
|
* @param {string} correlationId - the optional specified correlationId (callStateForMetrics.correlationId can be provided instead)
|
|
1034
1055
|
* @param {Boolean} failOnMissingMeetingInfo - whether to throw an error if meeting info fails to fetch (for calls that are not 1:1 or content share)
|
|
1035
1056
|
* @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
|
|
1057
|
+
* @param {Object} [meetingInfo] - Pre-fetched complete meeting info
|
|
1058
|
+
* @param {String} [meetingLookupUrl] - meeting info prefetch url
|
|
1036
1059
|
* @returns {Promise<Meeting>} A new Meeting.
|
|
1037
1060
|
* @public
|
|
1038
1061
|
* @memberof Meetings
|
|
@@ -1044,7 +1067,9 @@ export default class Meetings extends WebexPlugin {
|
|
|
1044
1067
|
infoExtraParams = {},
|
|
1045
1068
|
correlationId: string = undefined,
|
|
1046
1069
|
failOnMissingMeetingInfo = false,
|
|
1047
|
-
callStateForMetrics: CallStateForMetrics = undefined
|
|
1070
|
+
callStateForMetrics: CallStateForMetrics = undefined,
|
|
1071
|
+
meetingInfo = undefined,
|
|
1072
|
+
meetingLookupUrl = undefined
|
|
1048
1073
|
) {
|
|
1049
1074
|
// TODO: type should be from a dictionary
|
|
1050
1075
|
|
|
@@ -1103,7 +1128,9 @@ export default class Meetings extends WebexPlugin {
|
|
|
1103
1128
|
useRandomDelayForInfo,
|
|
1104
1129
|
infoExtraParams,
|
|
1105
1130
|
callStateForMetrics,
|
|
1106
|
-
failOnMissingMeetingInfo
|
|
1131
|
+
failOnMissingMeetingInfo,
|
|
1132
|
+
meetingInfo,
|
|
1133
|
+
meetingLookupUrl
|
|
1107
1134
|
).then((createdMeeting: any) => {
|
|
1108
1135
|
// If the meeting was successfully created.
|
|
1109
1136
|
if (createdMeeting && createdMeeting.on) {
|
|
@@ -1158,12 +1185,18 @@ export default class Meetings extends WebexPlugin {
|
|
|
1158
1185
|
}
|
|
1159
1186
|
|
|
1160
1187
|
/**
|
|
1188
|
+
* Create meeting
|
|
1189
|
+
*
|
|
1190
|
+
* When meeting info passed it should be complete, e.g.: fetched after password or captcha provided
|
|
1191
|
+
*
|
|
1161
1192
|
* @param {String} destination see create()
|
|
1162
1193
|
* @param {String} type see create()
|
|
1163
1194
|
* @param {Boolean} useRandomDelayForInfo whether a random delay should be added to fetching meeting info
|
|
1164
1195
|
* @param {Object} infoExtraParams extra parameters to be provided when fetching meeting info
|
|
1165
1196
|
* @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
|
|
1166
1197
|
* @param {Boolean} failOnMissingMeetingInfo - whether to throw an error if meeting info fails to fetch (for calls that are not 1:1 or content share)
|
|
1198
|
+
* @param {Object} [meetingInfo] - Pre-fetched complete meeting info
|
|
1199
|
+
* @param {String} [meetingLookupUrl] - meeting info prefetch url
|
|
1167
1200
|
* @returns {Promise} a new meeting instance complete with meeting info and destination
|
|
1168
1201
|
* @private
|
|
1169
1202
|
* @memberof Meetings
|
|
@@ -1174,7 +1207,9 @@ export default class Meetings extends WebexPlugin {
|
|
|
1174
1207
|
useRandomDelayForInfo = false,
|
|
1175
1208
|
infoExtraParams = {},
|
|
1176
1209
|
callStateForMetrics: CallStateForMetrics = undefined,
|
|
1177
|
-
failOnMissingMeetingInfo = false
|
|
1210
|
+
failOnMissingMeetingInfo = false,
|
|
1211
|
+
meetingInfo = undefined,
|
|
1212
|
+
meetingLookupUrl = undefined
|
|
1178
1213
|
) {
|
|
1179
1214
|
const meeting = new Meeting(
|
|
1180
1215
|
{
|
|
@@ -1220,22 +1255,26 @@ export default class Meetings extends WebexPlugin {
|
|
|
1220
1255
|
const isMeetingActive = !!destination.fullState?.active;
|
|
1221
1256
|
// @ts-ignore
|
|
1222
1257
|
const {enableUnifiedMeetings} = this.config.experimental;
|
|
1223
|
-
|
|
1224
|
-
|
|
1258
|
+
const meetingInfoOptions = {
|
|
1259
|
+
extraParams: infoExtraParams,
|
|
1260
|
+
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
if (meetingInfo) {
|
|
1264
|
+
meeting.injectMeetingInfo(meetingInfo, meetingInfoOptions, meetingLookupUrl);
|
|
1265
|
+
} else if (
|
|
1266
|
+
enableUnifiedMeetings &&
|
|
1267
|
+
!isMeetingActive &&
|
|
1268
|
+
useRandomDelayForInfo &&
|
|
1269
|
+
waitingTime > 0
|
|
1270
|
+
) {
|
|
1225
1271
|
meeting.fetchMeetingInfoTimeoutId = setTimeout(
|
|
1226
|
-
() =>
|
|
1227
|
-
meeting.fetchMeetingInfo({
|
|
1228
|
-
extraParams: infoExtraParams,
|
|
1229
|
-
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1230
|
-
}),
|
|
1272
|
+
() => meeting.fetchMeetingInfo(meetingInfoOptions),
|
|
1231
1273
|
waitingTime
|
|
1232
1274
|
);
|
|
1233
1275
|
meeting.parseMeetingInfo(undefined, destination);
|
|
1234
1276
|
} else {
|
|
1235
|
-
await meeting.fetchMeetingInfo(
|
|
1236
|
-
extraParams: infoExtraParams,
|
|
1237
|
-
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1238
|
-
});
|
|
1277
|
+
await meeting.fetchMeetingInfo(meetingInfoOptions);
|
|
1239
1278
|
}
|
|
1240
1279
|
} catch (err) {
|
|
1241
1280
|
if (
|
|
@@ -1346,11 +1385,12 @@ export default class Meetings extends WebexPlugin {
|
|
|
1346
1385
|
|
|
1347
1386
|
/**
|
|
1348
1387
|
* syncs all the meeting from server
|
|
1349
|
-
* @
|
|
1388
|
+
* @param {boolean} keepOnlyLocusMeetings - whether the sync should keep only locus meetings or any other meeting in meetingCollection
|
|
1389
|
+
* @returns {Promise<void>}
|
|
1350
1390
|
* @public
|
|
1351
1391
|
* @memberof Meetings
|
|
1352
1392
|
*/
|
|
1353
|
-
public syncMeetings() {
|
|
1393
|
+
public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1354
1394
|
return this.request
|
|
1355
1395
|
.getActiveMeetings()
|
|
1356
1396
|
.then((locusArray) => {
|
|
@@ -1374,7 +1414,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1374
1414
|
// (they had a locusUrl previously but are no longer active) in the sync
|
|
1375
1415
|
for (const meeting of Object.values(meetingsCollection)) {
|
|
1376
1416
|
// @ts-ignore
|
|
1377
|
-
|
|
1417
|
+
const {locusUrl} = meeting;
|
|
1418
|
+
if ((keepOnlyLocusMeetings || locusUrl) && !activeLocusUrl.includes(locusUrl)) {
|
|
1378
1419
|
// destroy function also uploads logs
|
|
1379
1420
|
// @ts-ignore
|
|
1380
1421
|
this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
|
|
@@ -341,18 +341,21 @@ export default class Reachability {
|
|
|
341
341
|
return Promise.resolve(results);
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
// @ts-ignore
|
|
345
|
-
const includeTcpReachability = this.webex.config.meetings.experimental.enableTcpReachability;
|
|
346
|
-
|
|
347
344
|
LoggerProxy.logger.log(
|
|
348
345
|
`Reachability:index#performReachabilityChecks --> doing UDP${
|
|
349
|
-
|
|
346
|
+
// @ts-ignore
|
|
347
|
+
this.webex.config.meetings.experimental.enableTcpReachability ? ' and TCP' : ''
|
|
350
348
|
} reachability checks`
|
|
351
349
|
);
|
|
352
350
|
|
|
353
351
|
const clusterReachabilityChecks = Object.keys(clusterList).map((key) => {
|
|
354
352
|
const cluster = clusterList[key];
|
|
355
353
|
|
|
354
|
+
// Linus doesn't support TCP reachability checks on video mesh nodes
|
|
355
|
+
const includeTcpReachability =
|
|
356
|
+
// @ts-ignore
|
|
357
|
+
this.webex.config.meetings.experimental.enableTcpReachability && !cluster.isVideoMesh;
|
|
358
|
+
|
|
356
359
|
if (!includeTcpReachability) {
|
|
357
360
|
cluster.tcp = [];
|
|
358
361
|
}
|
|
@@ -442,7 +442,7 @@ export default class ReconnectionManager {
|
|
|
442
442
|
LoggerProxy.logger.info(
|
|
443
443
|
'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
|
|
444
444
|
);
|
|
445
|
-
await this.webex.meetings.syncMeetings();
|
|
445
|
+
await this.webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
|
|
446
446
|
} catch (syncError) {
|
|
447
447
|
LoggerProxy.logger.info(
|
|
448
448
|
'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
|
package/src/roap/index.ts
CHANGED
|
@@ -187,7 +187,7 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
187
187
|
* @memberof Roap
|
|
188
188
|
*/
|
|
189
189
|
sendRoapMediaRequest(options: any) {
|
|
190
|
-
const {meeting, seq, sdp,
|
|
190
|
+
const {meeting, seq, sdp, tieBreaker} = options;
|
|
191
191
|
const roapMessage = {
|
|
192
192
|
messageType: ROAP.ROAP_TYPES.OFFER,
|
|
193
193
|
sdps: [sdp],
|
|
@@ -197,64 +197,62 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
197
197
|
headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
const sendEmptyMediaId = reconnect && isTurnDiscoverySkipped;
|
|
200
|
+
// The only time we want to send an empty media id is when we are reconnecting, because this way we tell Locus
|
|
201
|
+
// that it needs to create a new confluence, but when reconnecting we always send TURN_DISCOVERY_REQUEST first,
|
|
202
|
+
// so we don't need to ever send an empty media id here
|
|
203
|
+
const sendEmptyMediaId = false;
|
|
205
204
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
205
|
+
return this.roapRequest
|
|
206
|
+
.sendRoap({
|
|
207
|
+
roapMessage,
|
|
208
|
+
locusSelfUrl: meeting.selfUrl,
|
|
209
|
+
mediaId: sendEmptyMediaId ? '' : meeting.mediaId,
|
|
210
|
+
meetingId: meeting.id,
|
|
211
|
+
preferTranscoding: !meeting.isMultistream,
|
|
212
|
+
locusMediaRequest: meeting.locusMediaRequest,
|
|
213
|
+
ipVersion: MeetingUtil.getIpVersion(meeting.webex),
|
|
214
|
+
})
|
|
215
|
+
.then(({locus, mediaConnections}) => {
|
|
216
|
+
if (mediaConnections) {
|
|
217
|
+
meeting.updateMediaConnections(mediaConnections);
|
|
218
|
+
}
|
|
220
219
|
|
|
221
|
-
|
|
220
|
+
let roapAnswer;
|
|
222
221
|
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
if (mediaConnections?.[0]?.remoteSdp) {
|
|
223
|
+
const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
|
|
225
224
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
225
|
+
if (remoteSdp.roapMessage) {
|
|
226
|
+
const {
|
|
227
|
+
seq: answerSeq,
|
|
228
|
+
messageType,
|
|
229
|
+
sdps,
|
|
230
|
+
errorType,
|
|
231
|
+
errorCause,
|
|
232
|
+
headers,
|
|
233
|
+
} = remoteSdp.roapMessage;
|
|
235
234
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
235
|
+
roapAnswer = {
|
|
236
|
+
seq: answerSeq,
|
|
237
|
+
messageType,
|
|
238
|
+
sdp: sdps[0],
|
|
239
|
+
errorType,
|
|
240
|
+
errorCause,
|
|
241
|
+
headers,
|
|
242
|
+
};
|
|
245
243
|
}
|
|
244
|
+
}
|
|
246
245
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
if (!roapAnswer) {
|
|
247
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
|
|
248
|
+
correlationId: meeting.correlationId,
|
|
249
|
+
messageType: 'ANSWER',
|
|
250
|
+
isMultistream: meeting.isMultistream,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
254
253
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
254
|
+
return {locus, roapAnswer};
|
|
255
|
+
});
|
|
258
256
|
}
|
|
259
257
|
|
|
260
258
|
/**
|
|
@@ -7,7 +7,7 @@ import EventsScope from '../common/events/events-scope';
|
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_GET_STATS_FILTER,
|
|
9
9
|
STATS,
|
|
10
|
-
|
|
10
|
+
MQA_INTERVAL,
|
|
11
11
|
NETWORK_TYPE,
|
|
12
12
|
MEDIA_DEVICES,
|
|
13
13
|
_UNKNOWN_,
|
|
@@ -299,7 +299,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
299
299
|
this.sendMqaData();
|
|
300
300
|
this.mqaInterval = setInterval(() => {
|
|
301
301
|
this.sendMqaData();
|
|
302
|
-
},
|
|
302
|
+
}, MQA_INTERVAL);
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|