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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/{reconnection-in-progress.js → reconnection-not-started.js} +27 -15
  4. package/dist/common/errors/reconnection-not-started.js.map +1 -0
  5. package/dist/constants.js +16 -5
  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/controlsUtils.js +7 -1
  10. package/dist/locus-info/controlsUtils.js.map +1 -1
  11. package/dist/locus-info/index.js +10 -0
  12. package/dist/locus-info/index.js.map +1 -1
  13. package/dist/media/properties.js +102 -57
  14. package/dist/media/properties.js.map +1 -1
  15. package/dist/mediaQualityMetrics/config.js +10 -10
  16. package/dist/mediaQualityMetrics/config.js.map +1 -1
  17. package/dist/meeting/in-meeting-actions.js +6 -0
  18. package/dist/meeting/in-meeting-actions.js.map +1 -1
  19. package/dist/meeting/index.js +543 -467
  20. package/dist/meeting/index.js.map +1 -1
  21. package/dist/meeting/locusMediaRequest.js +27 -0
  22. package/dist/meeting/locusMediaRequest.js.map +1 -1
  23. package/dist/meeting/util.js +9 -16
  24. package/dist/meeting/util.js.map +1 -1
  25. package/dist/meeting/voicea-meeting.js +37 -49
  26. package/dist/meeting/voicea-meeting.js.map +1 -1
  27. package/dist/meetings/index.js +12 -28
  28. package/dist/meetings/index.js.map +1 -1
  29. package/dist/reachability/index.js +88 -9
  30. package/dist/reachability/index.js.map +1 -1
  31. package/dist/reconnection-manager/index.js +138 -109
  32. package/dist/reconnection-manager/index.js.map +1 -1
  33. package/dist/roap/request.js +3 -27
  34. package/dist/roap/request.js.map +1 -1
  35. package/dist/statsAnalyzer/index.js +8 -2
  36. package/dist/statsAnalyzer/index.js.map +1 -1
  37. package/dist/statsAnalyzer/mqaUtil.js +17 -0
  38. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  39. package/dist/types/common/errors/reconnection-not-started.d.ts +13 -0
  40. package/dist/types/constants.d.ts +13 -3
  41. package/dist/types/media/properties.d.ts +26 -2
  42. package/dist/types/mediaQualityMetrics/config.d.ts +8 -2
  43. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  44. package/dist/types/meeting/index.d.ts +5 -6
  45. package/dist/types/meeting/locusMediaRequest.d.ts +1 -0
  46. package/dist/types/meeting/util.d.ts +3 -0
  47. package/dist/types/meeting/voicea-meeting.d.ts +3 -2
  48. package/dist/types/meetings/index.d.ts +1 -16
  49. package/dist/types/reachability/index.d.ts +11 -0
  50. package/dist/types/reconnection-manager/index.d.ts +4 -14
  51. package/dist/webinar/index.js +1 -1
  52. package/package.json +21 -21
  53. package/src/common/errors/reconnection-not-started.ts +25 -0
  54. package/src/constants.ts +14 -5
  55. package/src/locus-info/controlsUtils.ts +11 -0
  56. package/src/locus-info/index.ts +16 -0
  57. package/src/media/properties.ts +67 -15
  58. package/src/mediaQualityMetrics/config.ts +13 -7
  59. package/src/meeting/in-meeting-actions.ts +12 -0
  60. package/src/meeting/index.ts +121 -98
  61. package/src/meeting/locusMediaRequest.ts +31 -0
  62. package/src/meeting/util.ts +9 -16
  63. package/src/meeting/voicea-meeting.ts +44 -46
  64. package/src/meetings/index.ts +15 -27
  65. package/src/reachability/index.ts +60 -0
  66. package/src/reconnection-manager/index.ts +128 -105
  67. package/src/roap/request.ts +1 -24
  68. package/src/statsAnalyzer/index.ts +10 -3
  69. package/src/statsAnalyzer/mqaUtil.ts +23 -0
  70. package/test/unit/spec/locus-info/controlsUtils.js +20 -0
  71. package/test/unit/spec/locus-info/index.js +21 -0
  72. package/test/unit/spec/media/properties.ts +145 -140
  73. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  74. package/test/unit/spec/meeting/index.js +243 -97
  75. package/test/unit/spec/meeting/locusMediaRequest.ts +49 -0
  76. package/test/unit/spec/meeting/utils.js +3 -10
  77. package/test/unit/spec/meeting/voicea-meeting.ts +5 -14
  78. package/test/unit/spec/meetings/index.js +59 -17
  79. package/test/unit/spec/reachability/index.ts +266 -0
  80. package/test/unit/spec/reconnection-manager/index.js +127 -39
  81. package/test/unit/spec/roap/request.ts +0 -37
  82. package/test/unit/spec/stats-analyzer/index.js +100 -8
  83. package/dist/common/errors/reconnection-in-progress.js.map +0 -1
  84. package/dist/types/common/errors/reconnection-in-progress.d.ts +0 -9
  85. package/src/common/errors/reconnection-in-progress.ts +0 -8
@@ -1,3 +1,5 @@
1
+ import {type MeetingTranscriptPayload} from '@webex/internal-plugin-voicea';
2
+
1
3
  export const getSpeaker = (members, csis = []) =>
2
4
  Object.values(members).find((member: any) => {
3
5
  const memberCSIs = member.participant.status.csis ?? [];
@@ -31,43 +33,16 @@ export const getSpeakerFromProxyOrStore = ({csisKey, meetingMembers, transcriptD
31
33
  return {speaker, needsCaching};
32
34
  };
33
35
 
34
- export const processNewCaptions = ({data, meeting}) => {
36
+ export const processNewCaptions = ({
37
+ data,
38
+ meeting,
39
+ }: {
40
+ data: MeetingTranscriptPayload;
41
+ meeting: any;
42
+ }) => {
35
43
  const {transcriptId} = data;
36
44
  const transcriptData = meeting.transcription;
37
45
 
38
- if (data.isFinal) {
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];
49
- const csisKey = data.transcript?.csis[0];
50
-
51
- const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
52
- meetingMembers: meeting.members.membersCollection.members,
53
- transcriptData,
54
- csisKey,
55
- });
56
-
57
- if (needsCaching) {
58
- transcriptData.speakerProxy[csisKey] = speaker;
59
- }
60
- const captionData = {
61
- id: transcriptId,
62
- isFinal: data.isFinal,
63
- translations: data.translations,
64
- text: data.transcript?.text,
65
- currentSpokenLanguage: data.transcript?.transcript_language_code,
66
- timestamp: data.timestamp,
67
- speaker,
68
- };
69
- transcriptData.captions.push(captionData);
70
- }
71
46
  const {transcripts = []} = data;
72
47
  const transcriptsPerCsis = new Map();
73
48
 
@@ -80,8 +55,11 @@ export const processNewCaptions = ({data, meeting}) => {
80
55
 
81
56
  const newCaption = `${transcriptsPerCsis.get(csisMember)?.text ?? ''} ${text}`.trim();
82
57
 
83
- // eslint-disable-next-line camelcase
84
- transcriptsPerCsis.set(csisMember, {text: newCaption, currentSpokenLanguage});
58
+ transcriptsPerCsis.set(csisMember, {
59
+ ...transcript,
60
+ text: newCaption,
61
+ currentSpokenLanguage,
62
+ });
85
63
  }
86
64
  const interimTranscriptionIds = [];
87
65
 
@@ -91,31 +69,51 @@ export const processNewCaptions = ({data, meeting}) => {
91
69
  transcriptData,
92
70
  csisKey: key,
93
71
  });
72
+ const {speakerId} = speaker;
73
+ const interimId = `${transcriptId}_${speakerId}`;
94
74
 
95
75
  if (needsCaching) {
96
76
  transcriptData.speakerProxy[key] = speaker;
97
77
  }
98
- const {speakerId} = speaker;
99
- const interimId = `${transcriptId}_${speakerId}`;
78
+
100
79
  const captionData = {
101
80
  id: interimId,
102
81
  isFinal: data.isFinal,
103
82
  translations: value.translations,
104
83
  text: value.text,
105
- currentCaptionLanguage: value.currentSpokenLanguage,
84
+ currentCaptionLanguage:
85
+ meeting.transcription?.languageOptions?.currentCaptionLanguage ||
86
+ value.currentSpokenLanguage,
87
+ currentSpokenLanguage:
88
+ meeting.transcription?.languageOptions?.currentSpokenLanguage ||
89
+ data.transcripts[0]?.transcript_language_code,
106
90
  timestamp: value?.timestamp,
107
91
  speaker,
108
92
  };
109
93
 
110
- const interimTranscriptIndex = transcriptData.captions.findIndex(
111
- (transcript) => transcript.id === interimId
112
- );
94
+ if (!data.isFinal) {
95
+ const interimTranscriptIndex = transcriptData.captions.findIndex(
96
+ (transcript) => transcript.id === interimId
97
+ );
113
98
 
114
- if (interimTranscriptIndex !== -1) {
115
- transcriptData.captions.splice(interimTranscriptIndex, 1);
116
- }
99
+ if (interimTranscriptIndex !== -1) {
100
+ transcriptData.captions.splice(interimTranscriptIndex, 1);
101
+ }
117
102
 
118
- interimTranscriptionIds.push(interimId);
103
+ interimTranscriptionIds.push(interimId);
104
+ } else {
105
+ transcriptData.interimCaptions[transcriptId].forEach((innerInterimId) => {
106
+ const interimTranscriptIndex = transcriptData.captions.findIndex(
107
+ (transcript) => transcript.id === innerInterimId
108
+ );
109
+
110
+ if (interimTranscriptIndex !== -1) {
111
+ transcriptData.captions.splice(interimTranscriptIndex, 1);
112
+ }
113
+ });
114
+ delete transcriptData.interimCaptions[transcriptId];
115
+ captionData.id = transcriptId;
116
+ }
119
117
  transcriptData.captions.push(captionData);
120
118
  }
121
119
  transcriptData.interimCaptions[transcriptId] = interimTranscriptionIds;
@@ -1,5 +1,5 @@
1
1
  /* eslint no-shadow: ["error", { "allow": ["eventType"] }] */
2
-
2
+ import {union} from 'lodash';
3
3
  import '@webex/internal-plugin-mercury';
4
4
  import '@webex/internal-plugin-conversation';
5
5
  import '@webex/internal-plugin-metrics';
@@ -45,6 +45,8 @@ import {
45
45
  MEETINGNUMBER,
46
46
  _JOINED_,
47
47
  _MOVED_,
48
+ _ON_HOLD_LOBBY_,
49
+ _WAIT_,
48
50
  } from '../constants';
49
51
  import BEHAVIORAL_METRICS from '../metrics/constants';
50
52
  import MeetingInfo from '../meeting-info';
@@ -340,6 +342,9 @@ export default class Meetings extends WebexPlugin {
340
342
  if (newLocus) {
341
343
  const isNewLocusAsBreakout = MeetingsUtil.isBreakoutLocusDTO(newLocus);
342
344
  const isSelfMoved = newLocus?.self?.state === _LEFT_ && newLocus?.self?.reason === _MOVED_;
345
+ const isSelfMovedToLobby =
346
+ newLocus?.self?.devices[0]?.intent?.reason === _ON_HOLD_LOBBY_ &&
347
+ newLocus?.self?.devices[0]?.intent?.type === _WAIT_;
343
348
  if (!meeting) {
344
349
  if (isNewLocusAsBreakout) {
345
350
  LoggerProxy.logger.log(
@@ -352,7 +357,7 @@ export default class Meetings extends WebexPlugin {
352
357
  return this.isNeedHandleMainLocus(meeting, newLocus);
353
358
  }
354
359
  if (!isNewLocusAsBreakout) {
355
- return this.isNeedHandleMainLocus(meeting, newLocus);
360
+ return isSelfMovedToLobby || this.isNeedHandleMainLocus(meeting, newLocus);
356
361
  }
357
362
 
358
363
  return !isSelfMoved;
@@ -996,7 +1001,10 @@ export default class Meetings extends WebexPlugin {
996
1001
  fetchUserPreferredWebexSite() {
997
1002
  return this.request.getMeetingPreferences().then((res) => {
998
1003
  if (res) {
999
- this.preferredWebexSite = MeetingsUtil.parseDefaultSiteFromMeetingPreferences(res);
1004
+ const preferredWebexSite = MeetingsUtil.parseDefaultSiteFromMeetingPreferences(res);
1005
+ this.preferredWebexSite = preferredWebexSite;
1006
+ // @ts-ignore
1007
+ this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1000
1008
  }
1001
1009
 
1002
1010
  // fall back to getting the preferred site from the user information
@@ -1009,6 +1017,8 @@ export default class Meetings extends WebexPlugin {
1009
1017
  user?.userPreferences?.userPreferencesItems?.preferredWebExSite;
1010
1018
  if (preferredWebexSite) {
1011
1019
  this.preferredWebexSite = preferredWebexSite;
1020
+ // @ts-ignore
1021
+ this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1012
1022
  } else {
1013
1023
  throw new Error('site not found');
1014
1024
  }
@@ -1383,22 +1393,12 @@ export default class Meetings extends WebexPlugin {
1383
1393
 
1384
1394
  /**
1385
1395
  * Get all meetings.
1386
- * @param {object} options
1387
- * @param {object} options.startDate - get meetings after this start date
1388
- * @param {object} options.endDate - get meetings before this end date
1389
1396
  * @returns {Object} All currently active meetings.
1390
1397
  * @public
1391
1398
  * @memberof Meetings
1392
1399
  */
1393
- public getAllMeetings(
1394
- options: {
1395
- startDate: object;
1396
- endDate: object;
1397
- } = {} as any
1398
- ) {
1399
- // Options may include other parameters to filter this collection
1400
- // of meetings.
1401
- return this.meetingCollection.getAll(options);
1400
+ public getAllMeetings() {
1401
+ return this.meetingCollection.getAll();
1402
1402
  }
1403
1403
 
1404
1404
  /**
@@ -1524,18 +1524,6 @@ export default class Meetings extends WebexPlugin {
1524
1524
  this.breakoutLocusForHandleLater.splice(existIndex, 1);
1525
1525
  }
1526
1526
 
1527
- /**
1528
- * Get all scheduled meetings.
1529
- * @param {object} options
1530
- * @param {object} options.startDate - get meetings after this start date
1531
- * @param {object} options.endDate - get meetings before this end date
1532
- * @returns {Object} All scheduled meetings.
1533
- * @memberof Meetings
1534
- */
1535
- getScheduledMeetings() {
1536
- return this.meetingCollection.getAll({scheduled: true});
1537
- }
1538
-
1539
1527
  /**
1540
1528
  * Get the logger instance for plugin-meetings
1541
1529
  * @returns {Logger}
@@ -296,6 +296,63 @@ export default class Reachability {
296
296
  return reachable;
297
297
  }
298
298
 
299
+ /**
300
+ * Returns true only if ALL protocols (UDP, TCP and TLS) have been tested and none
301
+ * of the media clusters where reachable with any of the protocols. This is done
302
+ * irrespective of the config, so for example:
303
+ * if config.meetings.experimental.enableTlsReachability === false,
304
+ * it will return false, because TLS reachability won't be tested,
305
+ * so we can't say for sure that media backend is unreachable over TLS.
306
+ *
307
+ * @returns {boolean}
308
+ */
309
+ async isWebexMediaBackendUnreachable() {
310
+ let unreachable = false;
311
+
312
+ // @ts-ignore
313
+ const reachabilityData = await this.webex.boundedStorage
314
+ .get(this.namespace, REACHABILITY.localStorageResult)
315
+ .catch(() => {});
316
+
317
+ if (reachabilityData) {
318
+ try {
319
+ const reachabilityResults: ReachabilityResults = JSON.parse(reachabilityData);
320
+
321
+ const protocols = {
322
+ udp: {tested: false, reachable: undefined},
323
+ tcp: {tested: false, reachable: undefined},
324
+ xtls: {tested: false, reachable: undefined},
325
+ };
326
+
327
+ Object.values(reachabilityResults).forEach((result) => {
328
+ Object.keys(protocols).forEach((protocol) => {
329
+ if (
330
+ result[protocol]?.result === 'reachable' ||
331
+ result[protocol]?.result === 'unreachable'
332
+ ) {
333
+ protocols[protocol].tested = true;
334
+
335
+ // we need at least 1 'reachable' result to mark the whole protocol as reachable
336
+ if (result[protocol].result === 'reachable') {
337
+ protocols[protocol].reachable = true;
338
+ }
339
+ }
340
+ });
341
+ });
342
+
343
+ unreachable = Object.values(protocols).every(
344
+ (protocol) => protocol.tested && !protocol.reachable
345
+ );
346
+ } catch (e) {
347
+ LoggerProxy.logger.error(
348
+ `Roap:request#attachReachabilityData --> Error in parsing reachability data: ${e}`
349
+ );
350
+ }
351
+ }
352
+
353
+ return unreachable;
354
+ }
355
+
299
356
  /**
300
357
  * Get list of all unreachable clusters
301
358
  * @returns {array} Unreachable clusters
@@ -314,6 +371,9 @@ export default class Reachability {
314
371
  if (result.tcp.result === 'unreachable') {
315
372
  unreachableList.push({name: key, protocol: 'tcp'});
316
373
  }
374
+ if (result.xtls.result === 'unreachable') {
375
+ unreachableList.push({name: key, protocol: 'xtls'});
376
+ }
317
377
  });
318
378
 
319
379
  return unreachableList;
@@ -17,11 +17,11 @@ import {
17
17
  RECONNECTION_STATE,
18
18
  } from '../constants';
19
19
  import BEHAVIORAL_METRICS from '../metrics/constants';
20
- import ReconnectInProgress from '../common/errors/reconnection-in-progress';
20
+ import ReconnectionError from '../common/errors/reconnection';
21
+ import ReconnectionNotStartedError from '../common/errors/reconnection-not-started';
21
22
  import Metrics from '../metrics';
22
23
  import Meeting from '../meeting';
23
24
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
24
- import ReconnectionError from '../common/errors/reconnection';
25
25
 
26
26
  /**
27
27
  * Used to indicate that the reconnect logic needs to be retried.
@@ -73,7 +73,6 @@ export default class ReconnectionManager {
73
73
  rejoinAttempts: any;
74
74
  shareStatus: any;
75
75
  status: any;
76
- tryCount: any;
77
76
  webex: any;
78
77
  /**
79
78
  * @param {Meeting} meeting
@@ -102,13 +101,6 @@ export default class ReconnectionManager {
102
101
  * @memberof ReconnectionManager
103
102
  */
104
103
  this.status = RECONNECTION.STATE.DEFAULT_STATUS;
105
- /**
106
- * @instance
107
- * @type {Number}
108
- * @private
109
- * @memberof ReconnectionManager
110
- */
111
- this.tryCount = RECONNECTION.STATE.DEFAULT_TRY_COUNT;
112
104
  /**
113
105
  * @instance
114
106
  * @type {Object}
@@ -131,7 +123,7 @@ export default class ReconnectionManager {
131
123
 
132
124
  // @ts-ignore
133
125
  this.maxRejoinAttempts = meeting.config.reconnection.maxRejoinAttempts;
134
- this.rejoinAttempts = RECONNECTION.STATE.DEFAULT_TRY_COUNT;
126
+ this.rejoinAttempts = 0;
135
127
  // @ts-ignore
136
128
  this.autoRejoinEnabled = meeting.config.reconnection.autoRejoin;
137
129
 
@@ -217,8 +209,7 @@ export default class ReconnectionManager {
217
209
  */
218
210
  public reset() {
219
211
  this.status = RECONNECTION.STATE.DEFAULT_STATUS;
220
- this.tryCount = RECONNECTION.STATE.DEFAULT_TRY_COUNT;
221
- this.rejoinAttempts = RECONNECTION.STATE.DEFAULT_TRY_COUNT;
212
+ this.rejoinAttempts = 0;
222
213
  }
223
214
 
224
215
  /**
@@ -265,43 +256,30 @@ export default class ReconnectionManager {
265
256
  return this.status === RECONNECTION.STATE.IN_PROGRESS;
266
257
  }
267
258
 
268
- /**
269
- * Sets the reconnection status
270
- *
271
- * @public
272
- * @param {RECONNECTION_STATE} status
273
- * @memberof ReconnectionManager
274
- * @returns {undefined}
275
- */
276
- public setStatus(status: RECONNECTION_STATE) {
277
- this.status = status;
278
- }
279
-
280
259
  /**
281
260
  * @returns {Boolean}
282
- * @throws {ReconnectionError}
261
+ * @throws {ReconnectInProgress, ReconnectionDisabled}
283
262
  * @private
284
263
  * @memberof ReconnectionManager
285
264
  */
286
- private validate() {
265
+ private canStartReconnection() {
287
266
  if (this.meeting.config.reconnection.enabled) {
288
- if (
289
- this.status === RECONNECTION.STATE.DEFAULT_STATUS ||
290
- this.status === RECONNECTION.STATE.COMPLETE
291
- ) {
267
+ if (this.status === RECONNECTION.STATE.DEFAULT_STATUS) {
292
268
  return true;
293
269
  }
294
270
 
295
271
  LoggerProxy.logger.info(
296
- 'ReconnectionManager:index#validate --> Reconnection already in progress.'
272
+ 'ReconnectionManager:index#canStartReconnection --> Reconnection already in progress.'
297
273
  );
298
274
 
299
- throw new ReconnectInProgress('Reconnection already in progress.');
275
+ return false;
300
276
  }
301
277
 
302
- LoggerProxy.logger.info('ReconnectionManager:index#validate --> Reconnection is not enabled.');
278
+ LoggerProxy.logger.info(
279
+ 'ReconnectionManager:index#canStartReconnection --> Reconnection is not enabled.'
280
+ );
303
281
 
304
- throw new ReconnectionError('Reconnection is not enabled.');
282
+ return false;
305
283
  }
306
284
 
307
285
  /**
@@ -309,105 +287,152 @@ export default class ReconnectionManager {
309
287
  * @param {Object} reconnectOptions
310
288
  * @param {boolean} [reconnectOptions.networkDisconnect=false] indicates if a network disconnect event happened
311
289
  * @param {boolean} [reconnectOptions.networkRetry=false] indicates if we are retrying the reconnect
290
+ * @param {Function} [completionCallback] callback that gets called when reconnection is started successfully
312
291
  * @returns {Promise}
313
292
  * @public
314
293
  * @memberof ReconnectionManager
315
294
  */
316
- public async reconnect({
317
- networkDisconnect = false,
318
- networkRetry = false,
319
- }: {
320
- networkDisconnect?: boolean;
321
- networkRetry?: boolean;
322
- } = {}) {
295
+ public async reconnect(
296
+ {
297
+ networkDisconnect = false,
298
+ networkRetry = false,
299
+ }: {
300
+ networkDisconnect?: boolean;
301
+ networkRetry?: boolean;
302
+ } = {},
303
+ completionCallback: (() => Promise<void>) | undefined = undefined
304
+ ) {
323
305
  LoggerProxy.logger.info(
324
306
  `ReconnectionManager:index#reconnect --> Reconnection start for meeting ${this.meeting.id}.`
325
307
  );
326
- // First, validate that we can reconnect, if not, it will throw an error
327
- try {
328
- this.validate();
329
- } catch (error) {
330
- LoggerProxy.logger.info(
331
- 'ReconnectionManager:index#reconnect --> Reconnection unable to begin.',
332
- error
333
- );
334
- throw error;
335
- }
336
308
 
337
- if (!networkRetry) {
338
- // Only log START metrics on the initial reconnect
339
- LoggerProxy.logger.info(
340
- 'ReconnectionManager:index#reconnect --> Sending reconnect start metric.'
309
+ const triggerEvent = (event, payload = undefined) =>
310
+ Trigger.trigger(
311
+ this.meeting,
312
+ {
313
+ file: 'reconnection-manager/index',
314
+ function: 'reconnect',
315
+ },
316
+ event,
317
+ payload
341
318
  );
342
319
 
343
- // @ts-ignore
344
- this.webex.internal.newMetrics.submitClientEvent({
345
- name: 'client.media.reconnecting',
346
- options: {
347
- meetingId: this.meeting.id,
348
- },
349
- });
320
+ if (!this.canStartReconnection()) {
321
+ throw new ReconnectionNotStartedError();
350
322
  }
351
323
 
352
324
  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
- }
325
+ this.status = RECONNECTION.STATE.IN_PROGRESS;
360
326
 
361
- try {
362
- const media = await this.executeReconnection({networkDisconnect});
327
+ triggerEvent(EVENT_TRIGGERS.MEETING_RECONNECTION_STARTING);
363
328
 
364
- return media;
365
- } catch (reconnectError) {
366
- if (reconnectError instanceof NeedsRetryError) {
329
+ if (!networkRetry) {
330
+ // Only log START metrics on the initial reconnect
367
331
  LoggerProxy.logger.info(
368
- 'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
332
+ 'ReconnectionManager:index#reconnect --> Sending reconnect start metric.'
369
333
  );
370
- // Reset our reconnect status since we are looping back to the beginning
371
- this.status = RECONNECTION.STATE.DEFAULT_STATUS;
372
334
 
373
- // This is a network retry, so we should not log START metrics again
374
- return this.reconnect({networkDisconnect: true, networkRetry: true});
335
+ // @ts-ignore
336
+ this.webex.internal.newMetrics.submitClientEvent({
337
+ name: 'client.media.reconnecting',
338
+ options: {
339
+ meetingId: this.meeting.id,
340
+ },
341
+ });
375
342
  }
376
343
 
377
- // Reconnect has failed
378
- LoggerProxy.logger.error(
379
- 'ReconnectionManager:index#reconnect --> Reconnection failed.',
380
- reconnectError.message
381
- );
382
- LoggerProxy.logger.info(
383
- 'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
384
- );
344
+ try {
345
+ await this.webex.meetings.startReachability();
346
+ } catch (err) {
347
+ LoggerProxy.logger.info(
348
+ 'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
349
+ err
350
+ );
351
+ }
352
+
353
+ try {
354
+ await this.executeReconnection({networkDisconnect});
355
+ } catch (reconnectError) {
356
+ if (reconnectError instanceof NeedsRetryError) {
357
+ LoggerProxy.logger.info(
358
+ 'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
359
+ );
360
+ // Reset our reconnect status since we are looping back to the beginning
361
+ this.status = RECONNECTION.STATE.DEFAULT_STATUS;
362
+
363
+ // This is a network retry, so we should not log START metrics again
364
+ await this.reconnect({networkDisconnect: true, networkRetry: true}, completionCallback);
365
+
366
+ return;
367
+ }
368
+
369
+ // Reconnect has failed
370
+ LoggerProxy.logger.error(
371
+ 'ReconnectionManager:index#reconnect --> Reconnection failed.',
372
+ reconnectError.message
373
+ );
374
+
375
+ // send call aborted event with category as expected as we are trying to rejoin
376
+ // @ts-ignore
377
+ this.webex.internal.newMetrics.submitClientEvent({
378
+ name: 'client.call.aborted',
379
+ payload: {
380
+ errors: [
381
+ {
382
+ category: 'expected',
383
+ errorCode: 2008,
384
+ fatal: true,
385
+ name: 'media-engine',
386
+ shownToUser: false,
387
+ },
388
+ ],
389
+ },
390
+ options: {
391
+ meetingId: this.meeting.id,
392
+ },
393
+ });
394
+
395
+ if (reconnectError instanceof NeedsRejoinError && this.autoRejoinEnabled) {
396
+ await this.rejoinMeeting(reconnectError.wasSharing);
397
+
398
+ return;
399
+ }
400
+
401
+ throw reconnectError;
402
+ }
403
+
404
+ // finalize the reconnection process by calling the completionCallback
405
+ if (completionCallback) {
406
+ await completionCallback();
407
+ }
408
+
409
+ triggerEvent(EVENT_TRIGGERS.MEETING_RECONNECTION_SUCCESS);
385
410
 
386
- // send call aborted event with catogery as expected as we are trying to rejoin
387
411
  // @ts-ignore
388
412
  this.webex.internal.newMetrics.submitClientEvent({
389
- name: 'client.call.aborted',
413
+ name: 'client.media.recovered',
390
414
  payload: {
391
- errors: [
392
- {
393
- category: 'expected',
394
- errorCode: 2008,
395
- fatal: true,
396
- name: 'media-engine',
397
- shownToUser: false,
398
- },
399
- ],
415
+ recoveredBy: 'new',
400
416
  },
401
417
  options: {
402
418
  meetingId: this.meeting.id,
403
419
  },
404
420
  });
421
+ } catch (error) {
422
+ triggerEvent(EVENT_TRIGGERS.MEETING_RECONNECTION_FAILURE, {
423
+ error: new ReconnectionError('Reconnection failure event', error),
424
+ });
405
425
 
406
- if (reconnectError instanceof NeedsRejoinError && this.autoRejoinEnabled) {
407
- return this.rejoinMeeting(reconnectError.wasSharing);
408
- }
426
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_RECONNECT_FAILURE, {
427
+ correlation_id: this.meeting.correlationId,
428
+ locus_id: this.meeting.locusUrl.split('/').pop(),
429
+ reason: error.message,
430
+ stack: error.stack,
431
+ });
409
432
 
410
- throw reconnectError;
433
+ throw new ReconnectionError('Reconnection failure event', error);
434
+ } finally {
435
+ this.reset();
411
436
  }
412
437
  }
413
438
 
@@ -420,8 +445,6 @@ export default class ReconnectionManager {
420
445
  * @memberof ReconnectionManager
421
446
  */
422
447
  private async executeReconnection({networkDisconnect = false}: {networkDisconnect?: boolean}) {
423
- this.status = RECONNECTION.STATE.IN_PROGRESS;
424
-
425
448
  LoggerProxy.logger.info(
426
449
  'ReconnectionManager:index#executeReconnection --> Attempting to reconnect to meeting.'
427
450
  );
@@ -61,7 +61,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
61
61
  ipVersion?: IP_VERSION;
62
62
  locusMediaRequest?: LocusMediaRequest;
63
63
  }) {
64
- const {roapMessage, locusSelfUrl, mediaId, meetingId, locusMediaRequest, ipVersion} = options;
64
+ const {roapMessage, locusSelfUrl, mediaId, locusMediaRequest, ipVersion} = options;
65
65
 
66
66
  if (!mediaId) {
67
67
  LoggerProxy.logger.info('Roap:request#sendRoap --> sending empty mediaID');
@@ -82,14 +82,6 @@ export default class RoapRequest extends StatelessWebexPlugin {
82
82
  `Roap:request#sendRoap --> ${locusSelfUrl} \n ${roapMessage.messageType} \n seq:${roapMessage.seq}`
83
83
  );
84
84
 
85
- // @ts-ignore
86
- this.webex.internal.newMetrics.submitClientEvent({
87
- name: 'client.locus.media.request',
88
- options: {
89
- meetingId,
90
- },
91
- });
92
-
93
85
  return locusMediaRequest
94
86
  .send({
95
87
  type: 'RoapMessage',
@@ -101,13 +93,6 @@ export default class RoapRequest extends StatelessWebexPlugin {
101
93
  ipVersion,
102
94
  })
103
95
  .then((res) => {
104
- // @ts-ignore
105
- this.webex.internal.newMetrics.submitClientEvent({
106
- name: 'client.locus.media.response',
107
- options: {
108
- meetingId,
109
- },
110
- });
111
96
  // always it will be the first mediaConnection Object
112
97
  const mediaConnections =
113
98
  res.body.mediaConnections &&
@@ -131,14 +116,6 @@ export default class RoapRequest extends StatelessWebexPlugin {
131
116
  };
132
117
  })
133
118
  .catch((err) => {
134
- // @ts-ignore
135
- this.webex.internal.newMetrics.submitClientEvent({
136
- name: 'client.locus.media.response',
137
- options: {
138
- meetingId,
139
- rawError: err,
140
- },
141
- });
142
119
  LoggerProxy.logger.error(`Roap:request#sendRoap --> Error:`, err);
143
120
  LoggerProxy.logger.error(
144
121
  `Roap:request#sendRoapRequest --> roapMessage that caused error:${JSON.stringify(