@webex/plugin-meetings 2.60.1-next.13 → 2.60.1-next.15

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 (63) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/interpretation/index.js +1 -1
  4. package/dist/interpretation/siLanguage.js +1 -1
  5. package/dist/mediaQualityMetrics/config.d.ts +103 -99
  6. package/dist/mediaQualityMetrics/config.js +133 -129
  7. package/dist/mediaQualityMetrics/config.js.map +1 -1
  8. package/dist/meeting/index.d.ts +0 -1
  9. package/dist/meeting/index.js +7 -15
  10. package/dist/meeting/index.js.map +1 -1
  11. package/dist/meeting/request.d.ts +2 -0
  12. package/dist/meeting/request.js +4 -0
  13. package/dist/meeting/request.js.map +1 -1
  14. package/dist/meeting/voicea-meeting.d.ts +0 -4
  15. package/dist/meeting/voicea-meeting.js +26 -58
  16. package/dist/meeting/voicea-meeting.js.map +1 -1
  17. package/dist/meetings/index.js +19 -0
  18. package/dist/meetings/index.js.map +1 -1
  19. package/dist/meetings/util.js +1 -1
  20. package/dist/meetings/util.js.map +1 -1
  21. package/dist/metrics/constants.d.ts +2 -0
  22. package/dist/metrics/constants.js +3 -1
  23. package/dist/metrics/constants.js.map +1 -1
  24. package/dist/reachability/index.js +14 -20
  25. package/dist/reachability/index.js.map +1 -1
  26. package/dist/reconnection-manager/index.js +63 -43
  27. package/dist/reconnection-manager/index.js.map +1 -1
  28. package/dist/roap/turnDiscovery.d.ts +18 -2
  29. package/dist/roap/turnDiscovery.js +163 -69
  30. package/dist/roap/turnDiscovery.js.map +1 -1
  31. package/dist/rtcMetrics/index.d.ts +7 -0
  32. package/dist/rtcMetrics/index.js +38 -1
  33. package/dist/rtcMetrics/index.js.map +1 -1
  34. package/dist/statsAnalyzer/index.js +135 -23
  35. package/dist/statsAnalyzer/index.js.map +1 -1
  36. package/dist/statsAnalyzer/mqaUtil.d.ts +28 -4
  37. package/dist/statsAnalyzer/mqaUtil.js +278 -148
  38. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  39. package/dist/webinar/index.js +1 -1
  40. package/package.json +21 -21
  41. package/src/mediaQualityMetrics/config.ts +107 -107
  42. package/src/meeting/index.ts +5 -18
  43. package/src/meeting/request.ts +6 -0
  44. package/src/meeting/voicea-meeting.ts +26 -65
  45. package/src/meetings/index.ts +22 -0
  46. package/src/meetings/util.ts +1 -1
  47. package/src/metrics/constants.ts +2 -0
  48. package/src/reachability/index.ts +0 -6
  49. package/src/reconnection-manager/index.ts +18 -7
  50. package/src/roap/turnDiscovery.ts +100 -24
  51. package/src/rtcMetrics/index.ts +43 -1
  52. package/src/statsAnalyzer/index.ts +158 -24
  53. package/src/statsAnalyzer/mqaUtil.ts +302 -154
  54. package/test/unit/spec/meeting/index.js +195 -4
  55. package/test/unit/spec/meeting/request.js +2 -0
  56. package/test/unit/spec/meeting/voicea-meeting.ts +266 -0
  57. package/test/unit/spec/meetings/utils.js +35 -8
  58. package/test/unit/spec/reachability/index.ts +74 -0
  59. package/test/unit/spec/reconnection-manager/index.js +36 -1
  60. package/test/unit/spec/roap/turnDiscovery.ts +326 -76
  61. package/test/unit/spec/rtcMetrics/index.ts +32 -3
  62. package/test/unit/spec/stats-analyzer/index.js +439 -1
  63. package/test/utils/webex-test-users.js +12 -4
@@ -15,21 +15,18 @@ export const getSpeakerFromProxyOrStore = ({csisKey, meetingMembers, transcriptD
15
15
 
16
16
  if (csisKey && transcriptData.speakerProxy[csisKey]) {
17
17
  speaker = transcriptData.speakerProxy[csisKey];
18
+
19
+ return {speaker, needsCaching};
18
20
  }
21
+
19
22
  const meetingMember: any = getSpeaker(meetingMembers, [csisKey]);
20
23
 
21
- const speakerInStore = {
24
+ speaker = {
22
25
  speakerId: meetingMember?.participant.person.id ?? '',
23
26
  name: meetingMember?.participant.person.name ?? '',
24
27
  };
25
28
 
26
- if (
27
- meetingMember &&
28
- (speakerInStore.speakerId !== speaker.speakerId || speakerInStore.name !== speaker.name)
29
- ) {
30
- needsCaching = true;
31
- speaker = speakerInStore;
32
- }
29
+ needsCaching = true;
33
30
 
34
31
  return {speaker, needsCaching};
35
32
  };
@@ -39,20 +36,16 @@ export const processNewCaptions = ({data, meeting}) => {
39
36
  const transcriptData = meeting.transcription;
40
37
 
41
38
  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
- }
39
+ transcriptData.interimCaptions[transcriptId].forEach((interimId) => {
40
+ const interimTranscriptIndex = transcriptData.captions.findIndex(
41
+ (transcript) => transcript.id === interimId
42
+ );
43
+
44
+ if (interimTranscriptIndex !== -1) {
45
+ transcriptData.captions.splice(interimTranscriptIndex, 1);
46
+ }
47
+ });
48
+ delete transcriptData.interimCaptions[transcriptId];
56
49
  const csisKey = data.transcript?.csis[0];
57
50
 
58
51
  const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
@@ -69,7 +62,7 @@ export const processNewCaptions = ({data, meeting}) => {
69
62
  isFinal: data.isFinal,
70
63
  translations: data.translations,
71
64
  text: data.transcript?.text,
72
- currentSpokenLanguage: data.transcript?.transcriptLanguageCode,
65
+ currentSpokenLanguage: data.transcript?.transcript_language_code,
73
66
  timestamp: data.timestamp,
74
67
  speaker,
75
68
  };
@@ -85,12 +78,12 @@ export const processNewCaptions = ({data, meeting}) => {
85
78
  csis: [csisMember],
86
79
  } = transcript;
87
80
 
88
- const newCaption = `${transcriptsPerCsis.get(csisMember)?.text ?? ''} ${text}`;
81
+ const newCaption = `${transcriptsPerCsis.get(csisMember)?.text ?? ''} ${text}`.trim();
89
82
 
90
83
  // eslint-disable-next-line camelcase
91
84
  transcriptsPerCsis.set(csisMember, {text: newCaption, currentSpokenLanguage});
92
85
  }
93
- const fakeTranscriptionIds = [];
86
+ const interimTranscriptionIds = [];
94
87
 
95
88
  for (const [key, value] of transcriptsPerCsis) {
96
89
  const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
@@ -103,9 +96,9 @@ export const processNewCaptions = ({data, meeting}) => {
103
96
  transcriptData.speakerProxy[key] = speaker;
104
97
  }
105
98
  const {speakerId} = speaker;
106
- const fakeId = `${transcriptId}_${speakerId}`;
99
+ const interimId = `${transcriptId}_${speakerId}`;
107
100
  const captionData = {
108
- id: fakeId,
101
+ id: interimId,
109
102
  isFinal: data.isFinal,
110
103
  translations: value.translations,
111
104
  text: value.text,
@@ -114,48 +107,16 @@ export const processNewCaptions = ({data, meeting}) => {
114
107
  speaker,
115
108
  };
116
109
 
117
- const fakeTranscriptIndex = transcriptData.captions.findIndex(
118
- (transcript) => transcript.id === fakeId
110
+ const interimTranscriptIndex = transcriptData.captions.findIndex(
111
+ (transcript) => transcript.id === interimId
119
112
  );
120
113
 
121
- if (fakeTranscriptIndex !== -1) {
122
- transcriptData.captions.splice(fakeTranscriptIndex, 1);
114
+ if (interimTranscriptIndex !== -1) {
115
+ transcriptData.captions.splice(interimTranscriptIndex, 1);
123
116
  }
124
117
 
125
- fakeTranscriptionIds.push(fakeId);
118
+ interimTranscriptionIds.push(interimId);
126
119
  transcriptData.captions.push(captionData);
127
120
  }
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);
121
+ transcriptData.interimCaptions[transcriptId] = interimTranscriptionIds;
161
122
  };
@@ -726,12 +726,20 @@ export default class Meetings extends WebexPlugin {
726
726
  * @memberof Meetings
727
727
  */
728
728
  public register() {
729
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
730
+ step: '[sdk] begin registration',
731
+ });
732
+
729
733
  // @ts-ignore
730
734
  if (!this.webex.canAuthorize) {
731
735
  LoggerProxy.logger.error(
732
736
  'Meetings:index#register --> ERROR, Unable to register, SDK cannot authorize'
733
737
  );
734
738
 
739
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
740
+ step: '[sdk] cannot authorize',
741
+ });
742
+
735
743
  return Promise.reject(new Error('SDK cannot authorize'));
736
744
  }
737
745
 
@@ -740,9 +748,17 @@ export default class Meetings extends WebexPlugin {
740
748
  'Meetings:index#register --> INFO, Meetings plugin already registered'
741
749
  );
742
750
 
751
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
752
+ step: '[sdk] already registered',
753
+ });
754
+
743
755
  return Promise.resolve();
744
756
  }
745
757
 
758
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
759
+ step: '[sdk] begin Promise.all()',
760
+ });
761
+
746
762
  return Promise.all([
747
763
  this.fetchUserPreferredWebexSite(),
748
764
  this.getGeoHint(),
@@ -764,6 +780,9 @@ export default class Meetings extends WebexPlugin {
764
780
  MeetingsUtil.checkH264Support.call(this),
765
781
  ])
766
782
  .then(() => {
783
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
784
+ step: '[sdk] end Promise.all()',
785
+ });
767
786
  this.listenForEvents();
768
787
  Trigger.trigger(
769
788
  this,
@@ -773,6 +792,9 @@ export default class Meetings extends WebexPlugin {
773
792
  },
774
793
  EVENT_TRIGGERS.MEETINGS_REGISTERED
775
794
  );
795
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
796
+ step: '[sdk] registration complete, triggered MEETINGS_REGISTERED event',
797
+ });
776
798
  this.registered = true;
777
799
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_SUCCESS);
778
800
  })
@@ -62,7 +62,7 @@ MeetingsUtil.handleRoapMercury = (envelope, meetingCollection) => {
62
62
  if (messageType === ROAP.ROAP_TYPES.TURN_DISCOVERY_RESPONSE) {
63
63
  // turn discovery is not part of normal roap protocol and so we are not handling it
64
64
  // through the usual roap state machine
65
- meeting.roap.turnDiscovery.handleTurnDiscoveryResponse(data.message);
65
+ meeting.roap.turnDiscovery.handleTurnDiscoveryResponse(data.message, 'from mercury');
66
66
  } else {
67
67
  const roapMessage = {
68
68
  seq,
@@ -3,6 +3,7 @@
3
3
  const BEHAVIORAL_METRICS = {
4
4
  MEETINGS_REGISTRATION_FAILED: 'js_sdk_meetings_registration_failed',
5
5
  MEETINGS_REGISTRATION_SUCCESS: 'js_sdk_meetings_registration_success',
6
+ MEETINGS_REGISTRATION_STEP: 'meetings_registration_step',
6
7
  MERCURY_CONNECTION_FAILURE: 'js_sdk_mercury_connection_failure',
7
8
  MERCURY_CONNECTION_RESTORED: 'js_sdk_mercury_connection_restored',
8
9
  JOIN_SUCCESS: 'js_sdk_join_success',
@@ -67,6 +68,7 @@ const BEHAVIORAL_METRICS = {
67
68
  TURN_DISCOVERY_LATENCY: 'js_sdk_turn_discovery_latency',
68
69
  ROAP_OFFER_TO_ANSWER_LATENCY: 'js_sdk_roap_offer_to_answer_latency',
69
70
  ROAP_HTTP_RESPONSE_MISSING: 'js_sdk_roap_http_response_missing',
71
+ TURN_DISCOVERY_REQUIRES_OK: 'js_sdk_turn_discovery_requires_ok',
70
72
  };
71
73
 
72
74
  export {BEHAVIORAL_METRICS as default};
@@ -95,12 +95,6 @@ export default class Reachability {
95
95
  * @memberof Reachability
96
96
  */
97
97
  public async gatherReachability(): Promise<ReachabilityResults> {
98
- // Remove stored reachability results to ensure no stale data
99
- // @ts-ignore
100
- await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageResult);
101
- // @ts-ignore
102
- await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageJoinCookie);
103
-
104
98
  // Fetch clusters and measure latency
105
99
  try {
106
100
  const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
@@ -349,7 +349,20 @@ export default class ReconnectionManager {
349
349
  });
350
350
  }
351
351
 
352
- return this.executeReconnection({networkDisconnect}).catch((reconnectError) => {
352
+ try {
353
+ await this.webex.meetings.startReachability();
354
+ } catch (err) {
355
+ LoggerProxy.logger.info(
356
+ 'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
357
+ err
358
+ );
359
+ }
360
+
361
+ try {
362
+ const media = await this.executeReconnection({networkDisconnect});
363
+
364
+ return media;
365
+ } catch (reconnectError) {
353
366
  if (reconnectError instanceof NeedsRetryError) {
354
367
  LoggerProxy.logger.info(
355
368
  'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
@@ -370,6 +383,7 @@ export default class ReconnectionManager {
370
383
  'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
371
384
  );
372
385
 
386
+ // send call aborted event with catogery as expected as we are trying to rejoin
373
387
  // @ts-ignore
374
388
  this.webex.internal.newMetrics.submitClientEvent({
375
389
  name: 'client.call.aborted',
@@ -388,16 +402,13 @@ export default class ReconnectionManager {
388
402
  meetingId: this.meeting.id,
389
403
  },
390
404
  });
391
- if (reconnectError instanceof NeedsRejoinError) {
392
- // send call aborded event with catogery as expected as we are trying to rejoin
393
405
 
394
- if (this.autoRejoinEnabled) {
395
- return this.rejoinMeeting(reconnectError.wasSharing);
396
- }
406
+ if (reconnectError instanceof NeedsRejoinError && this.autoRejoinEnabled) {
407
+ return this.rejoinMeeting(reconnectError.wasSharing);
397
408
  }
398
409
 
399
410
  throw reconnectError;
400
- });
411
+ }
401
412
  }
402
413
 
403
414
  /**
@@ -56,7 +56,7 @@ export default class TurnDiscovery {
56
56
  * @private
57
57
  * @memberof Roap
58
58
  */
59
- private waitForTurnDiscoveryResponse() {
59
+ private waitForTurnDiscoveryResponse(): Promise<{isOkRequired: boolean}> {
60
60
  if (!this.defer) {
61
61
  LoggerProxy.logger.warn(
62
62
  'Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress'
@@ -88,22 +88,32 @@ export default class TurnDiscovery {
88
88
  * handles TURN_DISCOVERY_RESPONSE roap message
89
89
  *
90
90
  * @param {Object} roapMessage
91
+ * @param {string} from string to indicate how we got the response (used just for logging)
91
92
  * @returns {void}
92
93
  * @public
93
94
  * @memberof Roap
94
95
  */
95
- public handleTurnDiscoveryResponse(roapMessage: object) {
96
- // @ts-ignore - Fix missing type
96
+ public handleTurnDiscoveryResponse(roapMessage: any, from: string) {
97
97
  const {headers} = roapMessage;
98
98
 
99
99
  if (!this.defer) {
100
100
  LoggerProxy.logger.warn(
101
- 'Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response'
101
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response ${from}`
102
102
  );
103
103
 
104
104
  return;
105
105
  }
106
106
 
107
+ if (roapMessage.messageType !== ROAP.ROAP_TYPES.TURN_DISCOVERY_RESPONSE) {
108
+ this.defer.reject(
109
+ new Error(
110
+ `TURN_DISCOVERY_RESPONSE ${from} has unexpected messageType: ${JSON.stringify(
111
+ roapMessage
112
+ )}`
113
+ )
114
+ );
115
+ }
116
+
107
117
  const expectedHeaders = [
108
118
  {headerName: 'x-cisco-turn-url', field: 'url'},
109
119
  {headerName: 'x-cisco-turn-username', field: 'username'},
@@ -129,21 +139,39 @@ export default class TurnDiscovery {
129
139
 
130
140
  if (foundHeaders !== expectedHeaders.length) {
131
141
  LoggerProxy.logger.warn(
132
- `Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(
142
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received ${from}: ${JSON.stringify(
133
143
  headers
134
144
  )}`
135
145
  );
136
146
  this.defer.reject(
137
- new Error(`TURN_DISCOVERY_RESPONSE missing some headers: ${JSON.stringify(headers)}`)
147
+ new Error(
148
+ `TURN_DISCOVERY_RESPONSE ${from} missing some headers: ${JSON.stringify(headers)}`
149
+ )
138
150
  );
139
151
  } else {
140
152
  LoggerProxy.logger.info(
141
- `Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`
153
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from}, url=${this.turnInfo.url}`
142
154
  );
143
- this.defer.resolve();
155
+
156
+ this.defer.resolve({isOkRequired: !headers?.includes('noOkInTransaction')});
144
157
  }
145
158
  }
146
159
 
160
+ /**
161
+ * handles TURN_DISCOVERY_RESPONSE roap message that came in http response
162
+ *
163
+ * @param {Object} roapMessage
164
+ * @returns {Promise}
165
+ * @memberof Roap
166
+ */
167
+ private async handleTurnDiscoveryResponseInHttpResponse(
168
+ roapMessage: object
169
+ ): Promise<{isOkRequired: boolean}> {
170
+ this.handleTurnDiscoveryResponse(roapMessage, 'in http response');
171
+
172
+ return this.defer.promise;
173
+ }
174
+
147
175
  /**
148
176
  * sends the TURN_DISCOVERY_REQUEST roap request
149
177
  *
@@ -168,6 +196,7 @@ export default class TurnDiscovery {
168
196
  messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,
169
197
  version: ROAP.ROAP_VERSION,
170
198
  seq: TURN_DISCOVERY_SEQ,
199
+ headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
171
200
  };
172
201
 
173
202
  LoggerProxy.logger.info(
@@ -186,10 +215,41 @@ export default class TurnDiscovery {
186
215
  // @ts-ignore - because of meeting.webex
187
216
  ipVersion: MeetingUtil.getIpVersion(meeting.webex),
188
217
  })
189
- .then(({mediaConnections}) => {
218
+ .then((response) => {
219
+ const {mediaConnections} = response;
220
+
221
+ let turnDiscoveryResponse;
222
+
190
223
  if (mediaConnections) {
191
224
  meeting.updateMediaConnections(mediaConnections);
225
+
226
+ if (mediaConnections[0]?.remoteSdp) {
227
+ const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
228
+
229
+ if (remoteSdp.roapMessage) {
230
+ // yes, it's misleading that remoteSdp actually contains a TURN discovery response, but that's how the backend works...
231
+ const {seq, messageType, errorType, errorCause, headers} = remoteSdp.roapMessage;
232
+
233
+ turnDiscoveryResponse = {
234
+ seq,
235
+ messageType,
236
+ errorType,
237
+ errorCause,
238
+ headers,
239
+ };
240
+ }
241
+ }
242
+ }
243
+
244
+ if (!turnDiscoveryResponse) {
245
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
246
+ correlationId: meeting.correlationId,
247
+ messageType: 'TURN_DISCOVERY_RESPONSE',
248
+ isMultistream: meeting.isMultistream,
249
+ });
192
250
  }
251
+
252
+ return turnDiscoveryResponse;
193
253
  });
194
254
  }
195
255
 
@@ -284,30 +344,46 @@ export default class TurnDiscovery {
284
344
  };
285
345
  }
286
346
 
287
- return this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting)
288
- .then(() => this.waitForTurnDiscoveryResponse())
289
- .then(() => this.sendRoapOK(meeting))
290
- .then(() => {
291
- this.defer = undefined;
347
+ try {
348
+ const httpResponse = await this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting);
292
349
 
293
- LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
350
+ // if we haven't got the response over http, we need to wait for it to come over the websocket via Mercury
351
+ const {isOkRequired} = httpResponse
352
+ ? await this.handleTurnDiscoveryResponseInHttpResponse(httpResponse)
353
+ : await this.waitForTurnDiscoveryResponse();
354
+
355
+ if (isOkRequired) {
356
+ await this.sendRoapOK(meeting);
294
357
 
295
- return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
296
- })
297
- .catch((e) => {
298
- // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
299
358
  LoggerProxy.logger.info(
300
- `Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`
359
+ 'Roap:turnDiscovery#doTurnDiscovery --> TURN discovery response requires OK'
301
360
  );
302
361
 
303
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE, {
362
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK, {
304
363
  correlation_id: meeting.correlationId,
305
364
  locus_id: meeting.locusUrl.split('/').pop(),
306
- reason: e.message,
307
- stack: e.stack,
308
365
  });
366
+ }
309
367
 
310
- return {turnServerInfo: undefined, turnDiscoverySkippedReason: undefined};
368
+ this.defer = undefined;
369
+
370
+ LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
371
+
372
+ return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
373
+ } catch (e) {
374
+ // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
375
+ LoggerProxy.logger.info(
376
+ `Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`
377
+ );
378
+
379
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE, {
380
+ correlation_id: meeting.correlationId,
381
+ locus_id: meeting.locusUrl.split('/').pop(),
382
+ reason: e.message,
383
+ stack: e.stack,
311
384
  });
385
+
386
+ return {turnServerInfo: undefined, turnDiscoverySkippedReason: undefined};
387
+ }
312
388
  }
313
389
  }
@@ -1,7 +1,20 @@
1
1
  /* eslint-disable class-methods-use-this */
2
2
  import {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';
3
+ import uuid from 'uuid';
3
4
  import RTC_METRICS from './constants';
4
5
 
6
+ const parseJsonPayload = (payload: any[]): any | null => {
7
+ try {
8
+ if (payload && payload[0]) {
9
+ return JSON.parse(payload[0]);
10
+ }
11
+
12
+ return null;
13
+ } catch (_) {
14
+ return null;
15
+ }
16
+ };
17
+
5
18
  /**
6
19
  * Rtc Metrics
7
20
  */
@@ -19,6 +32,8 @@ export default class RtcMetrics {
19
32
 
20
33
  correlationId: string;
21
34
 
35
+ connectionId: string;
36
+
22
37
  /**
23
38
  * Initialize the interval.
24
39
  *
@@ -32,6 +47,7 @@ export default class RtcMetrics {
32
47
  this.meetingId = meetingId;
33
48
  this.webex = webex;
34
49
  this.correlationId = correlationId;
50
+ this.setNewConnectionId();
35
51
  // Send the first set of metrics at 5 seconds in the case of a user leaving the call shortly after joining.
36
52
  setTimeout(this.sendMetricsInQueue.bind(this), 5 * 1000);
37
53
  }
@@ -60,7 +76,23 @@ export default class RtcMetrics {
60
76
  if (data.name === 'stats-report') {
61
77
  data.payload = data.payload.map(this.anonymizeIp);
62
78
  }
79
+
63
80
  this.metricsQueue.push(data);
81
+
82
+ try {
83
+ // If a connection fails, send the rest of the metrics in queue and get a new connection id.
84
+ const parsedPayload = parseJsonPayload(data.payload);
85
+ if (
86
+ data.name === 'onconnectionstatechange' &&
87
+ parsedPayload &&
88
+ parsedPayload.value === 'failed'
89
+ ) {
90
+ this.sendMetricsInQueue();
91
+ this.setNewConnectionId();
92
+ }
93
+ } catch (e) {
94
+ console.error(e);
95
+ }
64
96
  }
65
97
  }
66
98
 
@@ -93,6 +125,15 @@ export default class RtcMetrics {
93
125
  return JSON.stringify(data);
94
126
  }
95
127
 
128
+ /**
129
+ * Set a new connection id.
130
+ *
131
+ * @returns {void}
132
+ */
133
+ private setNewConnectionId() {
134
+ this.connectionId = uuid.v4();
135
+ }
136
+
96
137
  /**
97
138
  * Send metrics to the metrics service.
98
139
  *
@@ -111,10 +152,11 @@ export default class RtcMetrics {
111
152
  metrics: [
112
153
  {
113
154
  type: 'webrtc',
114
- version: '1.0.1',
155
+ version: '1.1.0',
115
156
  userId: this.webex.internal.device.userId,
116
157
  meetingId: this.meetingId,
117
158
  correlationId: this.correlationId,
159
+ connectionId: this.connectionId,
118
160
  data: this.metricsQueue,
119
161
  },
120
162
  ],