@webex/plugin-meetings 3.1.0 → 3.2.0

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 (205) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/reconnection-not-started.js +46 -0
  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/index.js +80 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/interpretation/index.js +1 -1
  10. package/dist/interpretation/siLanguage.js +1 -1
  11. package/dist/locus-info/controlsUtils.js +7 -1
  12. package/dist/locus-info/controlsUtils.js.map +1 -1
  13. package/dist/locus-info/index.js +10 -0
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/media/properties.js +102 -57
  16. package/dist/media/properties.js.map +1 -1
  17. package/dist/mediaQualityMetrics/config.js +10 -10
  18. package/dist/mediaQualityMetrics/config.js.map +1 -1
  19. package/dist/meeting/in-meeting-actions.js +6 -0
  20. package/dist/meeting/in-meeting-actions.js.map +1 -1
  21. package/dist/meeting/index.js +564 -475
  22. package/dist/meeting/index.js.map +1 -1
  23. package/dist/meeting/locusMediaRequest.js +27 -0
  24. package/dist/meeting/locusMediaRequest.js.map +1 -1
  25. package/dist/meeting/util.js +9 -16
  26. package/dist/meeting/util.js.map +1 -1
  27. package/dist/meeting/voicea-meeting.js +37 -49
  28. package/dist/meeting/voicea-meeting.js.map +1 -1
  29. package/dist/meeting-info/util.js +304 -267
  30. package/dist/meeting-info/util.js.map +1 -1
  31. package/dist/meeting-info/utilv2.js +334 -298
  32. package/dist/meeting-info/utilv2.js.map +1 -1
  33. package/dist/meetings/index.js +12 -28
  34. package/dist/meetings/index.js.map +1 -1
  35. package/dist/reachability/index.js +88 -9
  36. package/dist/reachability/index.js.map +1 -1
  37. package/dist/reconnection-manager/index.js +138 -109
  38. package/dist/reconnection-manager/index.js.map +1 -1
  39. package/dist/roap/request.js +3 -27
  40. package/dist/roap/request.js.map +1 -1
  41. package/dist/statsAnalyzer/index.js +8 -2
  42. package/dist/statsAnalyzer/index.js.map +1 -1
  43. package/dist/statsAnalyzer/mqaUtil.js +17 -0
  44. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  45. package/dist/types/annotation/annotation.types.d.ts +42 -0
  46. package/dist/types/annotation/constants.d.ts +31 -0
  47. package/dist/types/annotation/index.d.ts +117 -0
  48. package/dist/types/breakouts/breakout.d.ts +8 -0
  49. package/dist/types/breakouts/collection.d.ts +5 -0
  50. package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
  51. package/dist/types/breakouts/events.d.ts +8 -0
  52. package/dist/types/breakouts/index.d.ts +5 -0
  53. package/dist/types/breakouts/request.d.ts +22 -0
  54. package/dist/types/breakouts/utils.d.ts +15 -0
  55. package/dist/types/common/browser-detection.d.ts +9 -0
  56. package/dist/types/common/collection.d.ts +48 -0
  57. package/dist/types/common/config.d.ts +2 -0
  58. package/dist/types/common/errors/captcha-error.d.ts +15 -0
  59. package/dist/types/common/errors/intent-to-join.d.ts +16 -0
  60. package/dist/types/common/errors/join-meeting.d.ts +17 -0
  61. package/dist/types/common/errors/media.d.ts +15 -0
  62. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  63. package/dist/types/common/errors/parameter.d.ts +15 -0
  64. package/dist/types/common/errors/password-error.d.ts +15 -0
  65. package/dist/types/common/errors/permission.d.ts +14 -0
  66. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  67. package/dist/types/common/errors/reconnection-not-started.d.ts +13 -0
  68. package/dist/types/common/errors/reconnection.d.ts +15 -0
  69. package/dist/types/common/errors/stats.d.ts +15 -0
  70. package/dist/types/common/errors/webex-errors.d.ts +93 -0
  71. package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
  72. package/dist/types/common/events/events-scope.d.ts +17 -0
  73. package/dist/types/common/events/events.d.ts +12 -0
  74. package/dist/types/common/events/trigger-proxy.d.ts +2 -0
  75. package/dist/types/common/events/util.d.ts +2 -0
  76. package/dist/types/common/logs/logger-config.d.ts +2 -0
  77. package/dist/types/common/logs/logger-proxy.d.ts +2 -0
  78. package/dist/types/common/logs/request.d.ts +36 -0
  79. package/dist/types/common/queue.d.ts +34 -0
  80. package/dist/types/config.d.ts +73 -0
  81. package/dist/types/constants.d.ts +1098 -0
  82. package/dist/types/controls-options-manager/constants.d.ts +4 -0
  83. package/dist/types/controls-options-manager/enums.d.ts +15 -0
  84. package/dist/types/controls-options-manager/index.d.ts +136 -0
  85. package/dist/types/controls-options-manager/types.d.ts +43 -0
  86. package/dist/types/controls-options-manager/util.d.ts +1 -0
  87. package/dist/types/index.d.ts +19 -0
  88. package/dist/types/interceptors/index.d.ts +2 -0
  89. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  90. package/dist/types/interpretation/collection.d.ts +5 -0
  91. package/dist/types/interpretation/index.d.ts +5 -0
  92. package/dist/types/interpretation/siLanguage.d.ts +5 -0
  93. package/dist/types/locus-info/controlsUtils.d.ts +2 -0
  94. package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
  95. package/dist/types/locus-info/fullState.d.ts +2 -0
  96. package/dist/types/locus-info/hostUtils.d.ts +2 -0
  97. package/dist/types/locus-info/index.d.ts +322 -0
  98. package/dist/types/locus-info/infoUtils.d.ts +2 -0
  99. package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
  100. package/dist/types/locus-info/parser.d.ts +272 -0
  101. package/dist/types/locus-info/selfUtils.d.ts +2 -0
  102. package/dist/types/media/MediaConnectionAwaiter.d.ts +61 -0
  103. package/dist/types/media/index.d.ts +34 -0
  104. package/dist/types/media/properties.d.ts +117 -0
  105. package/dist/types/media/util.d.ts +2 -0
  106. package/dist/types/mediaQualityMetrics/config.d.ts +247 -0
  107. package/dist/types/meeting/in-meeting-actions.d.ts +173 -0
  108. package/dist/types/meeting/index.d.ts +1832 -0
  109. package/dist/types/meeting/locusMediaRequest.d.ts +75 -0
  110. package/dist/types/meeting/muteState.d.ts +178 -0
  111. package/dist/types/meeting/request.d.ts +295 -0
  112. package/dist/types/meeting/request.type.d.ts +11 -0
  113. package/dist/types/meeting/state.d.ts +9 -0
  114. package/dist/types/meeting/util.d.ts +122 -0
  115. package/dist/types/meeting/voicea-meeting.d.ts +17 -0
  116. package/dist/types/meeting-info/collection.d.ts +20 -0
  117. package/dist/types/meeting-info/index.d.ts +69 -0
  118. package/dist/types/meeting-info/meeting-info-v2.d.ts +123 -0
  119. package/dist/types/meeting-info/request.d.ts +22 -0
  120. package/dist/types/meeting-info/util.d.ts +49 -0
  121. package/dist/types/meeting-info/utilv2.d.ts +65 -0
  122. package/dist/types/meetings/collection.d.ts +40 -0
  123. package/dist/types/meetings/index.d.ts +383 -0
  124. package/dist/types/meetings/meetings.types.d.ts +4 -0
  125. package/dist/types/meetings/request.d.ts +27 -0
  126. package/dist/types/meetings/util.d.ts +18 -0
  127. package/dist/types/member/index.d.ts +160 -0
  128. package/dist/types/member/types.d.ts +32 -0
  129. package/dist/types/member/util.d.ts +2 -0
  130. package/dist/types/members/collection.d.ts +29 -0
  131. package/dist/types/members/index.d.ts +353 -0
  132. package/dist/types/members/request.d.ts +114 -0
  133. package/dist/types/members/types.d.ts +25 -0
  134. package/dist/types/members/util.d.ts +215 -0
  135. package/dist/types/metrics/constants.d.ts +70 -0
  136. package/dist/types/metrics/index.d.ts +45 -0
  137. package/dist/types/multistream/mediaRequestManager.d.ts +119 -0
  138. package/dist/types/multistream/receiveSlot.d.ts +68 -0
  139. package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
  140. package/dist/types/multistream/remoteMedia.d.ts +72 -0
  141. package/dist/types/multistream/remoteMediaGroup.d.ts +49 -0
  142. package/dist/types/multistream/remoteMediaManager.d.ts +300 -0
  143. package/dist/types/multistream/sendSlotManager.d.ts +69 -0
  144. package/dist/types/networkQualityMonitor/index.d.ts +70 -0
  145. package/dist/types/personal-meeting-room/index.d.ts +47 -0
  146. package/dist/types/personal-meeting-room/request.d.ts +14 -0
  147. package/dist/types/personal-meeting-room/util.d.ts +2 -0
  148. package/dist/types/reachability/clusterReachability.d.ts +110 -0
  149. package/dist/types/reachability/index.d.ts +120 -0
  150. package/dist/types/reachability/request.d.ts +39 -0
  151. package/dist/types/reachability/util.d.ts +15 -0
  152. package/dist/types/reactions/constants.d.ts +3 -0
  153. package/dist/types/reactions/reactions.d.ts +4 -0
  154. package/dist/types/reactions/reactions.type.d.ts +52 -0
  155. package/dist/types/reconnection-manager/index.d.ts +126 -0
  156. package/dist/types/recording-controller/enums.d.ts +7 -0
  157. package/dist/types/recording-controller/index.d.ts +207 -0
  158. package/dist/types/recording-controller/util.d.ts +14 -0
  159. package/dist/types/roap/index.d.ts +86 -0
  160. package/dist/types/roap/request.d.ts +39 -0
  161. package/dist/types/roap/turnDiscovery.d.ts +155 -0
  162. package/dist/types/rtcMetrics/constants.d.ts +4 -0
  163. package/dist/types/rtcMetrics/index.d.ts +61 -0
  164. package/dist/types/statsAnalyzer/global.d.ts +36 -0
  165. package/dist/types/statsAnalyzer/index.d.ts +217 -0
  166. package/dist/types/statsAnalyzer/mqaUtil.d.ts +48 -0
  167. package/dist/types/transcription/index.d.ts +64 -0
  168. package/dist/types/webinar/collection.d.ts +16 -0
  169. package/dist/types/webinar/index.d.ts +5 -0
  170. package/dist/webinar/index.js +1 -1
  171. package/package.json +22 -22
  172. package/src/common/errors/reconnection-not-started.ts +25 -0
  173. package/src/constants.ts +14 -5
  174. package/src/index.ts +30 -0
  175. package/src/locus-info/controlsUtils.ts +11 -0
  176. package/src/locus-info/index.ts +16 -0
  177. package/src/media/properties.ts +67 -15
  178. package/src/mediaQualityMetrics/config.ts +13 -7
  179. package/src/meeting/in-meeting-actions.ts +12 -0
  180. package/src/meeting/index.ts +144 -107
  181. package/src/meeting/locusMediaRequest.ts +31 -0
  182. package/src/meeting/util.ts +9 -16
  183. package/src/meeting/voicea-meeting.ts +44 -46
  184. package/src/meeting-info/util.ts +241 -233
  185. package/src/meeting-info/utilv2.ts +250 -244
  186. package/src/meetings/index.ts +15 -27
  187. package/src/reachability/index.ts +60 -0
  188. package/src/reconnection-manager/index.ts +128 -105
  189. package/src/roap/request.ts +1 -24
  190. package/src/statsAnalyzer/index.ts +10 -3
  191. package/src/statsAnalyzer/mqaUtil.ts +23 -0
  192. package/test/unit/spec/locus-info/controlsUtils.js +20 -0
  193. package/test/unit/spec/locus-info/index.js +21 -0
  194. package/test/unit/spec/media/properties.ts +145 -140
  195. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  196. package/test/unit/spec/meeting/index.js +271 -105
  197. package/test/unit/spec/meeting/locusMediaRequest.ts +49 -0
  198. package/test/unit/spec/meeting/utils.js +3 -10
  199. package/test/unit/spec/meeting/voicea-meeting.ts +5 -14
  200. package/test/unit/spec/meetings/index.js +59 -17
  201. package/test/unit/spec/reachability/index.ts +266 -0
  202. package/test/unit/spec/reconnection-manager/index.js +127 -39
  203. package/test/unit/spec/roap/request.ts +0 -37
  204. package/test/unit/spec/stats-analyzer/index.js +100 -8
  205. package/src/common/errors/reconnection-in-progress.ts +0 -8
@@ -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(
@@ -998,12 +998,13 @@ export class StatsAnalyzer extends EventsScope {
998
998
  result.qualityLimitationReason;
999
999
  this.statsResults[mediaType][sendrecvType].qualityLimitationResolutionChanges =
1000
1000
  result.qualityLimitationResolutionChanges;
1001
- this.statsResults[mediaType][sendrecvType].retransmittedPacketsSent =
1001
+ this.statsResults[mediaType][sendrecvType].totalRtxPacketsSent =
1002
1002
  result.retransmittedPacketsSent;
1003
+ this.statsResults[mediaType][sendrecvType].totalRtxBytesSent = result.retransmittedBytesSent;
1003
1004
  this.statsResults[mediaType][sendrecvType].totalBytesSent = result.bytesSent;
1004
1005
  this.statsResults[mediaType][sendrecvType].headerBytesSent = result.headerBytesSent;
1005
- this.statsResults[mediaType][sendrecvType].retransmittedBytesSent =
1006
- result.retransmittedBytesSent;
1006
+ this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
1007
+ this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
1007
1008
  }
1008
1009
  }
1009
1010
 
@@ -1116,6 +1117,8 @@ export class StatsAnalyzer extends EventsScope {
1116
1117
 
1117
1118
  this.statsResults[mediaType][sendrecvType].lastPacketReceivedTimestamp =
1118
1119
  result.lastPacketReceivedTimestamp;
1120
+ this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
1121
+ this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
1119
1122
 
1120
1123
  // From Thin
1121
1124
  this.statsResults[mediaType][sendrecvType].totalNackCount = result.nackCount;
@@ -1132,6 +1135,10 @@ export class StatsAnalyzer extends EventsScope {
1132
1135
  this.statsResults[mediaType][sendrecvType].fecPacketsReceived = result.fecPacketsReceived;
1133
1136
  this.statsResults[mediaType][sendrecvType].totalBytesReceived = result.bytesReceived;
1134
1137
  this.statsResults[mediaType][sendrecvType].headerBytesReceived = result.headerBytesReceived;
1138
+ this.statsResults[mediaType][sendrecvType].totalRtxPacketsReceived =
1139
+ result.retransmittedPacketsReceived;
1140
+ this.statsResults[mediaType][sendrecvType].totalRtxBytesReceived =
1141
+ result.retransmittedBytesReceived;
1135
1142
 
1136
1143
  this.statsResults[mediaType][sendrecvType].meanRtpJitter.push(result.jitter);
1137
1144
 
@@ -216,6 +216,8 @@ export const getAudioSenderStreamMqa = ({
216
216
  statsResults[mediaType][sendrecvType].totalKeyFramesEncoded - lastFramesEncoded || 0;
217
217
  audioSenderStream.requestedKeyFrames =
218
218
  statsResults[mediaType][sendrecvType].totalFirCount - lastFirCount || 0;
219
+
220
+ audioSenderStream.requestedBitrate = statsResults[mediaType][sendrecvType].requestedBitrate || 0;
219
221
  };
220
222
 
221
223
  export const getVideoReceiverMqa = ({
@@ -235,10 +237,16 @@ export const getVideoReceiverMqa = ({
235
237
  const lastPacketsLost = getLastTotalValue('totalPacketsLost');
236
238
  const lastBytesReceived = getLastTotalValue('totalBytesReceived');
237
239
 
240
+ const lastRtxPacketsReceived = getLastTotalValue('totalRtxPacketsReceived');
241
+ const lastRtxBytesReceived = getLastTotalValue('totalRtxBytesReceived');
242
+
238
243
  const packetsLost = getTotalValue('totalPacketsLost');
239
244
  const totalPacketsReceived = getTotalValue('totalPacketsReceived');
240
245
  const totalBytesReceived = getTotalValue('totalBytesReceived');
241
246
 
247
+ const totalRtxPacketsReceived = getTotalValue('totalRtxPacketsReceived');
248
+ const totalRtxBytesReceived = getTotalValue('totalRtxBytesReceived');
249
+
242
250
  const meanRemoteJitter = Object.keys(statsResults)
243
251
  .filter((mt) => mt.includes(baseMediaType))
244
252
  .reduce((acc, mt) => acc.concat(statsResults[mt][sendrecvType].meanRemoteJitter), []);
@@ -264,10 +272,15 @@ export const getVideoReceiverMqa = ({
264
272
 
265
273
  // Calculate the outgoing bitrate
266
274
  const totalBytesReceivedInaMin = totalBytesReceived - lastBytesReceived;
275
+ const totalRtxBytesReceivedInaMin = totalRtxBytesReceived - lastRtxBytesReceived;
267
276
 
268
277
  videoReceiver.common.rtpBitrate = totalBytesReceivedInaMin
269
278
  ? (totalBytesReceivedInaMin * 8) / 60
270
279
  : 0;
280
+ videoReceiver.common.rtxPackets = totalRtxPacketsReceived - lastRtxPacketsReceived;
281
+ videoReceiver.common.rtxBitrate = totalRtxBytesReceivedInaMin
282
+ ? (totalRtxBytesReceivedInaMin * 8) / 60
283
+ : 0;
271
284
  };
272
285
 
273
286
  export const getVideoReceiverStreamMqa = ({
@@ -347,11 +360,15 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, b
347
360
  const lastPacketsSent = getLastTotalValue('totalPacketsSent');
348
361
  const lastBytesSent = getLastTotalValue('totalBytesSent');
349
362
  const lastPacketsLostTotal = getLastTotalValue('totalPacketsLostOnReceiver');
363
+ const lastRtxPacketsSent = getLastTotalValue('totalRtxPacketsSent');
364
+ const lastRtxBytesSent = getLastTotalValue('totalRtxBytesSent');
350
365
 
351
366
  const totalPacketsLostOnReceiver = getTotalValue('totalPacketsLostOnReceiver');
352
367
  const totalPacketsSent = getTotalValue('totalPacketsSent');
353
368
  const totalBytesSent = getTotalValue('totalBytesSent');
354
369
  const availableOutgoingBitrate = getTotalValue('availableOutgoingBitrate');
370
+ const totalRtxPacketsSent = getTotalValue('totalRtxPacketsSent');
371
+ const totalRtxBytesSent = getTotalValue('totalRtxBytesSent');
355
372
 
356
373
  videoSender.common.common.direction =
357
374
  statsResults[Object.keys(statsResults).find((mediaType) => mediaType.includes(baseMediaType))]
@@ -387,8 +404,11 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, b
387
404
 
388
405
  // Calculate the outgoing bitrate
389
406
  const totalBytesSentInaMin = totalBytesSent - lastBytesSent;
407
+ const totalRtxBytesSentInaMin = totalRtxBytesSent - lastRtxBytesSent;
390
408
 
391
409
  videoSender.common.rtpBitrate = totalBytesSentInaMin ? (totalBytesSentInaMin * 8) / 60 : 0;
410
+ videoSender.common.rtxPackets = totalRtxPacketsSent - lastRtxPacketsSent;
411
+ videoSender.common.rtxBitrate = totalRtxBytesSentInaMin ? (totalRtxBytesSentInaMin * 8) / 60 : 0;
392
412
  };
393
413
 
394
414
  export const getVideoSenderStreamMqa = ({
@@ -437,4 +457,7 @@ export const getVideoSenderStreamMqa = ({
437
457
  videoSenderStream.transmittedWidth = statsResults[mediaType][sendrecvType].width || 0;
438
458
  videoSenderStream.transmittedFrameSize =
439
459
  (videoSenderStream.transmittedHeight * videoSenderStream.transmittedWidth) / 256;
460
+ videoSenderStream.requestedBitrate = statsResults[mediaType][sendrecvType].requestedBitrate || 0;
461
+ videoSenderStream.requestedFrameSize =
462
+ statsResults[mediaType][sendrecvType].requestedFrameSize || 0;
440
463
  };
@@ -269,6 +269,26 @@ describe('plugin-meetings', () => {
269
269
  assert.equal(updates.hasInterpretationChanged, false);
270
270
  });
271
271
 
272
+ it('returns hasManualCaptionChanged = true when it has changed', () => {
273
+ const newControls = {
274
+ manualCaptionControl: {enabled: false},
275
+ };
276
+
277
+ const {updates} = ControlsUtils.getControls({manualCaptionControl: {enabled: true}}, newControls);
278
+
279
+ assert.equal(updates.hasManualCaptionChanged, true);
280
+ });
281
+
282
+ it('returns hasManualCaptionChanged = false when it has not changed', () => {
283
+ const newControls = {
284
+ manualCaptionControl: {enabled: true},
285
+ };
286
+
287
+ const {updates} = ControlsUtils.getControls({manualCaptionControl: {enabled: true}}, newControls);
288
+
289
+ assert.equal(updates.hasManualCaptionChanged, false);
290
+ });
291
+
272
292
  describe('videoEnabled', () => {
273
293
  const testVideoEnabled = (oldControls, newControls, updatedProperty) => {
274
294
  const result = ControlsUtils.getControls(oldControls, newControls);
@@ -467,6 +467,27 @@ describe('plugin-meetings', () => {
467
467
  );
468
468
  });
469
469
 
470
+ it('should update the manual caption state', () => {
471
+ locusInfo.emitScoped = sinon.stub();
472
+ locusInfo.controls = {
473
+ manualCaptionControl: {enabled: false},
474
+ };
475
+
476
+ locusInfo.updateControls({manualCaptionControl: { enabled: true, }});
477
+
478
+ assert.calledWith(
479
+ locusInfo.emitScoped,
480
+ {
481
+ file: 'locus-info',
482
+ function: 'updateControls',
483
+ },
484
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_MANUAL_CAPTION_UPDATED,
485
+ {
486
+ enabled: true,
487
+ }
488
+ );
489
+ });
490
+
470
491
  it('should update the meetingContainerURL from null', () => {
471
492
  locusInfo.controls = {
472
493
  meetingContainer: {meetingContainerUrl: null},