@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.
Files changed (58) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.d.ts +18 -4
  5. package/dist/constants.js +23 -9
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/locus-info/index.d.ts +1 -1
  10. package/dist/locus-info/index.js +8 -8
  11. package/dist/locus-info/index.js.map +1 -1
  12. package/dist/meeting/index.d.ts +119 -31
  13. package/dist/meeting/index.js +1021 -805
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +25 -18
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.d.ts +16 -0
  18. package/dist/meeting/util.js +71 -0
  19. package/dist/meeting/util.js.map +1 -1
  20. package/dist/meeting/voicea-meeting.d.ts +20 -0
  21. package/dist/meeting/voicea-meeting.js +201 -0
  22. package/dist/meeting/voicea-meeting.js.map +1 -0
  23. package/dist/meetings/index.d.ts +25 -3
  24. package/dist/meetings/index.js +83 -32
  25. package/dist/meetings/index.js.map +1 -1
  26. package/dist/reachability/index.js +11 -6
  27. package/dist/reachability/index.js.map +1 -1
  28. package/dist/reconnection-manager/index.js +3 -1
  29. package/dist/reconnection-manager/index.js.map +1 -1
  30. package/dist/roap/index.js +50 -54
  31. package/dist/roap/index.js.map +1 -1
  32. package/dist/statsAnalyzer/index.js +1 -1
  33. package/dist/statsAnalyzer/index.js.map +1 -1
  34. package/dist/statsAnalyzer/mqaUtil.js +13 -10
  35. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  36. package/dist/webinar/index.js +1 -1
  37. package/package.json +22 -21
  38. package/src/constants.ts +22 -4
  39. package/src/locus-info/index.ts +13 -12
  40. package/src/meeting/index.ts +546 -276
  41. package/src/meeting/request.ts +7 -0
  42. package/src/meeting/util.ts +97 -0
  43. package/src/meeting/voicea-meeting.ts +161 -0
  44. package/src/meetings/index.ts +59 -18
  45. package/src/reachability/index.ts +7 -4
  46. package/src/reconnection-manager/index.ts +1 -1
  47. package/src/roap/index.ts +49 -51
  48. package/src/statsAnalyzer/index.ts +2 -2
  49. package/src/statsAnalyzer/mqaUtil.ts +15 -14
  50. package/test/unit/spec/locus-info/index.js +53 -5
  51. package/test/unit/spec/meeting/index.js +1792 -1139
  52. package/test/unit/spec/meeting/request.js +22 -12
  53. package/test/unit/spec/meeting/utils.js +93 -0
  54. package/test/unit/spec/meetings/index.js +180 -21
  55. package/test/unit/spec/reachability/index.ts +2 -1
  56. package/test/unit/spec/reconnection-manager/index.js +1 -0
  57. package/test/unit/spec/roap/index.ts +28 -42
  58. package/test/unit/spec/stats-analyzer/index.js +415 -30
@@ -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
  }
@@ -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
+ };
@@ -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
- if (enableUnifiedMeetings && !isMeetingActive && useRandomDelayForInfo && waitingTime > 0) {
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
- * @returns {undefined}
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
- if (meeting.locusUrl && !activeLocusUrl.includes(meeting.locusUrl)) {
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
- includeTcpReachability ? ' and TCP' : ''
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, reconnect, tieBreaker} = options;
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
- // When reconnecting, it's important that the first roap message being sent out has empty media id.
201
- // Normally this is the roap offer, but when TURN discovery is enabled,
202
- // then this is the TURN discovery request message
203
- return this.turnDiscovery.isSkipped(meeting).then((isTurnDiscoverySkipped) => {
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
- return this.roapRequest
207
- .sendRoap({
208
- roapMessage,
209
- locusSelfUrl: meeting.selfUrl,
210
- mediaId: sendEmptyMediaId ? '' : meeting.mediaId,
211
- meetingId: meeting.id,
212
- preferTranscoding: !meeting.isMultistream,
213
- locusMediaRequest: meeting.locusMediaRequest,
214
- ipVersion: MeetingUtil.getIpVersion(meeting.webex),
215
- })
216
- .then(({locus, mediaConnections}) => {
217
- if (mediaConnections) {
218
- meeting.updateMediaConnections(mediaConnections);
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
- let roapAnswer;
220
+ let roapAnswer;
222
221
 
223
- if (mediaConnections?.[0]?.remoteSdp) {
224
- const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
222
+ if (mediaConnections?.[0]?.remoteSdp) {
223
+ const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
225
224
 
226
- if (remoteSdp.roapMessage) {
227
- const {
228
- seq: answerSeq,
229
- messageType,
230
- sdps,
231
- errorType,
232
- errorCause,
233
- headers,
234
- } = remoteSdp.roapMessage;
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
- roapAnswer = {
237
- seq: answerSeq,
238
- messageType,
239
- sdp: sdps[0],
240
- errorType,
241
- errorCause,
242
- headers,
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
- if (!roapAnswer) {
248
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
249
- correlationId: meeting.correlationId,
250
- messageType: 'ANSWER',
251
- isMultistream: meeting.isMultistream,
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
- return {locus, roapAnswer};
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
- MQA_INTEVAL,
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
- }, MQA_INTEVAL);
302
+ }, MQA_INTERVAL);
303
303
  });
304
304
  }
305
305