@webex/plugin-meetings 2.60.0-next.1 → 2.60.0-next.2

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 (55) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +1 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/enums.js +2 -1
  6. package/dist/controls-options-manager/enums.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/meeting/in-meeting-actions.js +2 -0
  10. package/dist/meeting/in-meeting-actions.js.map +1 -1
  11. package/dist/meeting/index.js +155 -103
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/meeting-info/meeting-info-v2.js +3 -0
  14. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  15. package/dist/meeting-info/utilv2.js +14 -29
  16. package/dist/meeting-info/utilv2.js.map +1 -1
  17. package/dist/meetings/collection.js +17 -0
  18. package/dist/meetings/collection.js.map +1 -1
  19. package/dist/meetings/index.js +13 -0
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/metrics/constants.js +1 -0
  22. package/dist/metrics/constants.js.map +1 -1
  23. package/dist/reconnection-manager/index.js +26 -26
  24. package/dist/reconnection-manager/index.js.map +1 -1
  25. package/dist/rtcMetrics/index.js +25 -0
  26. package/dist/rtcMetrics/index.js.map +1 -1
  27. package/dist/statsAnalyzer/index.js +21 -1
  28. package/dist/statsAnalyzer/index.js.map +1 -1
  29. package/dist/statsAnalyzer/mqaUtil.js +16 -16
  30. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  31. package/dist/webinar/index.js +1 -1
  32. package/package.json +21 -22
  33. package/src/constants.ts +10 -4
  34. package/src/controls-options-manager/enums.ts +2 -0
  35. package/src/meeting/in-meeting-actions.ts +4 -0
  36. package/src/meeting/index.ts +140 -92
  37. package/src/meeting-info/meeting-info-v2.ts +4 -0
  38. package/src/meeting-info/utilv2.ts +6 -19
  39. package/src/meetings/collection.ts +13 -0
  40. package/src/meetings/index.ts +11 -0
  41. package/src/metrics/constants.ts +1 -0
  42. package/src/reconnection-manager/index.ts +62 -66
  43. package/src/rtcMetrics/index.ts +24 -0
  44. package/src/statsAnalyzer/index.ts +30 -1
  45. package/src/statsAnalyzer/mqaUtil.ts +17 -14
  46. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  47. package/test/unit/spec/meeting/index.js +1058 -158
  48. package/test/unit/spec/meeting/muteState.js +2 -1
  49. package/test/unit/spec/meeting-info/meetinginfov2.js +28 -0
  50. package/test/unit/spec/meetings/collection.js +12 -0
  51. package/test/unit/spec/meetings/index.js +306 -118
  52. package/test/unit/spec/member/util.js +0 -31
  53. package/test/unit/spec/reconnection-manager/index.js +25 -10
  54. package/test/unit/spec/rtcMetrics/index.ts +20 -0
  55. package/test/unit/spec/stats-analyzer/index.js +12 -2
@@ -9,7 +9,6 @@ import {
9
9
  ClientEvent,
10
10
  ClientEventLeaveReason,
11
11
  CallDiagnosticUtils,
12
- CALL_DIAGNOSTIC_CONFIG,
13
12
  } from '@webex/internal-plugin-metrics';
14
13
  import {
15
14
  ConnectionState,
@@ -105,6 +104,7 @@ import {
105
104
  MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC,
106
105
  MEETING_PERMISSION_TOKEN_REFRESH_REASON,
107
106
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
107
+ RECONNECTION,
108
108
  } from '../constants';
109
109
  import BEHAVIORAL_METRICS from '../metrics/constants';
110
110
  import ParameterError from '../common/errors/parameter';
@@ -528,7 +528,7 @@ export default class Meeting extends StatelessWebexPlugin {
528
528
  meetingInfoFailureCode?: number;
529
529
  meetingInfoExtraParams?: Record<string, any>;
530
530
  networkQualityMonitor: NetworkQualityMonitor;
531
- networkStatus: string;
531
+ networkStatus?: NETWORK_STATUS;
532
532
  passwordStatus: string;
533
533
  queuedMediaUpdates: any[];
534
534
  recording: any;
@@ -577,6 +577,7 @@ export default class Meeting extends StatelessWebexPlugin {
577
577
  private sendSlotManager: SendSlotManager = new SendSlotManager(LoggerProxy);
578
578
  private deferSDPAnswer?: Defer; // used for waiting for a response
579
579
  private sdpResponseTimer?: ReturnType<typeof setTimeout>;
580
+ private hasMediaConnectionConnectedAtLeastOnce: boolean;
580
581
 
581
582
  /**
582
583
  * @param {Object} attrs
@@ -1096,13 +1097,14 @@ export default class Meeting extends StatelessWebexPlugin {
1096
1097
  */
1097
1098
  this.networkQualityMonitor = null;
1098
1099
  /**
1100
+ * Indicates network status of the webrtc media connection
1099
1101
  * @instance
1100
1102
  * @type {String}
1101
1103
  * @readonly
1102
1104
  * @public
1103
1105
  * @memberof Meeting
1104
1106
  */
1105
- this.networkStatus = null;
1107
+ this.networkStatus = undefined;
1106
1108
  /**
1107
1109
  * Passing only info as we send basic info for meeting added event
1108
1110
  * @instance
@@ -1318,6 +1320,15 @@ export default class Meeting extends StatelessWebexPlugin {
1318
1320
  * @memberof Meeting
1319
1321
  */
1320
1322
  this.retriedWithTurnServer = false;
1323
+
1324
+ /**
1325
+ * Whether or not the media connection has ever successfully connected.
1326
+ * @instance
1327
+ * @type {boolean}
1328
+ * @private
1329
+ * @memberof Meeting
1330
+ */
1331
+ this.hasMediaConnectionConnectedAtLeastOnce = false;
1321
1332
  }
1322
1333
 
1323
1334
  /**
@@ -1928,12 +1939,12 @@ export default class Meeting extends StatelessWebexPlugin {
1928
1939
 
1929
1940
  /**
1930
1941
  * sets the network status on meeting object
1931
- * @param {String} networkStatus
1942
+ * @param {NETWORK_STATUS} networkStatus
1932
1943
  * @private
1933
1944
  * @returns {undefined}
1934
1945
  * @memberof Meeting
1935
1946
  */
1936
- private setNetworkStatus(networkStatus: string) {
1947
+ private setNetworkStatus(networkStatus?: NETWORK_STATUS) {
1937
1948
  if (networkStatus === NETWORK_STATUS.DISCONNECTED) {
1938
1949
  Trigger.trigger(
1939
1950
  this,
@@ -3428,6 +3439,10 @@ export default class Meeting extends StatelessWebexPlugin {
3428
3439
  requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
3429
3440
  policies: this.selfUserPolicies,
3430
3441
  }),
3442
+ canChat: ControlsOptionsUtil.hasPolicies({
3443
+ requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
3444
+ policies: this.selfUserPolicies,
3445
+ }),
3431
3446
  canShareApplication:
3432
3447
  (ControlsOptionsUtil.hasHints({
3433
3448
  requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
@@ -3916,6 +3931,7 @@ export default class Meeting extends StatelessWebexPlugin {
3916
3931
  this.receiveSlotManager.reset();
3917
3932
  this.mediaProperties.webrtcMediaConnection.close();
3918
3933
  this.sendSlotManager.reset();
3934
+ this.setNetworkStatus(undefined);
3919
3935
  }
3920
3936
 
3921
3937
  this.audio = null;
@@ -4268,6 +4284,8 @@ export default class Meeting extends StatelessWebexPlugin {
4268
4284
 
4269
4285
  return this.reconnectionManager
4270
4286
  .reconnect(options)
4287
+ .then(() => this.waitForRemoteSDPAnswer())
4288
+ .then(() => this.waitForMediaConnectionConnected())
4271
4289
  .then(() => {
4272
4290
  Trigger.trigger(
4273
4291
  this,
@@ -4278,6 +4296,18 @@ export default class Meeting extends StatelessWebexPlugin {
4278
4296
  EVENT_TRIGGERS.MEETING_RECONNECTION_SUCCESS
4279
4297
  );
4280
4298
  LoggerProxy.logger.log('Meeting:index#reconnect --> Meeting reconnect success');
4299
+
4300
+ // @ts-ignore
4301
+ this.webex.internal.newMetrics.submitClientEvent({
4302
+ name: 'client.media.recovered',
4303
+ payload: {
4304
+ recoveredBy: 'new',
4305
+ },
4306
+ options: {
4307
+ meetingId: this.id,
4308
+ },
4309
+ });
4310
+ this.reconnectionManager.setStatus(RECONNECTION.STATE.COMPLETE);
4281
4311
  })
4282
4312
  .catch((error) => {
4283
4313
  Trigger.trigger(
@@ -4587,7 +4617,11 @@ export default class Meeting extends StatelessWebexPlugin {
4587
4617
  // @ts-ignore
4588
4618
  this.webex.internal.newMetrics.submitClientEvent({
4589
4619
  name: 'client.call.initiated',
4590
- payload: {trigger: 'user-interaction', isRoapCallEnabled: true},
4620
+ payload: {
4621
+ trigger: 'user-interaction',
4622
+ isRoapCallEnabled: true,
4623
+ pstnAudioType: options?.pstnAudioType,
4624
+ },
4591
4625
  options: {meetingId: this.id},
4592
4626
  });
4593
4627
 
@@ -4753,7 +4787,7 @@ export default class Meeting extends StatelessWebexPlugin {
4753
4787
  .then((join) => {
4754
4788
  if (isBrowser) {
4755
4789
  // @ts-ignore - config coming from registerPlugin
4756
- if (this.config.receiveTranscription || this.receiveTranscription) {
4790
+ if (this.config.receiveTranscription || options.receiveTranscription) {
4757
4791
  if (this.isTranscriptionSupported()) {
4758
4792
  LoggerProxy.logger.info(
4759
4793
  'Meeting:index#join --> Attempting to enabled to receive transcription!'
@@ -5398,40 +5432,25 @@ export default class Meeting extends StatelessWebexPlugin {
5398
5432
 
5399
5433
  this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
5400
5434
  const connectionFailed = () => {
5401
- // we know the media connection failed and browser will not attempt to recover it any more
5402
- // so reset the timer as it's not needed anymore, we want to reconnect immediately
5403
- this.reconnectionManager.resetReconnectionTimer();
5404
-
5405
- this.reconnect({networkDisconnect: true});
5406
- // @ts-ignore
5407
- this.webex.internal.newMetrics.submitClientEvent({
5408
- name: 'client.ice.end',
5409
- payload: {
5410
- canProceed: false,
5411
- icePhase: 'IN_MEETING',
5412
- errors: [
5413
- // @ts-ignore
5414
- this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
5415
- {
5416
- clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
5417
- }
5418
- ),
5419
- ],
5420
- },
5421
- options: {
5422
- meetingId: this.id,
5423
- },
5424
- });
5425
-
5426
- this.uploadLogs({
5427
- file: 'peer-connection-manager/index',
5428
- function: 'connectionFailed',
5429
- });
5430
-
5431
5435
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_FAILURE, {
5432
5436
  correlation_id: this.correlationId,
5433
5437
  locus_id: this.locusId,
5438
+ networkStatus: this.networkStatus,
5439
+ hasMediaConnectionConnectedAtLeastOnce: this.hasMediaConnectionConnectedAtLeastOnce,
5434
5440
  });
5441
+
5442
+ if (this.hasMediaConnectionConnectedAtLeastOnce) {
5443
+ // we know the media connection failed and browser will not attempt to recover it any more
5444
+ // so reset the timer as it's not needed anymore, we want to reconnect immediately
5445
+ this.reconnectionManager.resetReconnectionTimer();
5446
+
5447
+ this.reconnect({networkDisconnect: true});
5448
+
5449
+ this.uploadLogs({
5450
+ file: 'peer-connection-manager/index',
5451
+ function: 'connectionFailed',
5452
+ });
5453
+ }
5435
5454
  };
5436
5455
 
5437
5456
  LoggerProxy.logger.info(
@@ -5443,22 +5462,32 @@ export default class Meeting extends StatelessWebexPlugin {
5443
5462
 
5444
5463
  switch (event.state) {
5445
5464
  case ConnectionState.Connecting:
5446
- // @ts-ignore
5447
- this.webex.internal.newMetrics.submitClientEvent({
5448
- name: 'client.ice.start',
5449
- options: {
5450
- meetingId: this.id,
5451
- },
5452
- });
5465
+ if (!this.hasMediaConnectionConnectedAtLeastOnce) {
5466
+ // Only send CA event for join flow if we haven't successfully connected media yet
5467
+ // @ts-ignore
5468
+ this.webex.internal.newMetrics.submitClientEvent({
5469
+ name: 'client.ice.start',
5470
+ options: {
5471
+ meetingId: this.id,
5472
+ },
5473
+ });
5474
+ }
5453
5475
  break;
5454
5476
  case ConnectionState.Connected:
5455
- // @ts-ignore
5456
- this.webex.internal.newMetrics.submitClientEvent({
5457
- name: 'client.ice.end',
5458
- options: {
5459
- meetingId: this.id,
5460
- },
5461
- });
5477
+ if (!this.hasMediaConnectionConnectedAtLeastOnce) {
5478
+ // Only send CA event for join flow if we haven't successfully connected media yet
5479
+ // @ts-ignore
5480
+ this.webex.internal.newMetrics.submitClientEvent({
5481
+ name: 'client.ice.end',
5482
+ payload: {
5483
+ canProceed: true,
5484
+ icePhase: 'JOIN_MEETING_FINAL',
5485
+ },
5486
+ options: {
5487
+ meetingId: this.id,
5488
+ },
5489
+ });
5490
+ }
5462
5491
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
5463
5492
  correlation_id: this.correlationId,
5464
5493
  locus_id: this.locusId,
@@ -5467,6 +5496,7 @@ export default class Meeting extends StatelessWebexPlugin {
5467
5496
  this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
5468
5497
  this.reconnectionManager.iceReconnected();
5469
5498
  this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
5499
+ this.hasMediaConnectionConnectedAtLeastOnce = true;
5470
5500
  break;
5471
5501
  case ConnectionState.Disconnected:
5472
5502
  this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
@@ -5771,36 +5801,42 @@ export default class Meeting extends StatelessWebexPlugin {
5771
5801
  try {
5772
5802
  await this.mediaProperties.waitForMediaConnectionConnected();
5773
5803
  } catch (error) {
5774
- // @ts-ignore
5775
- this.webex.internal.newMetrics.submitClientEvent({
5776
- name: 'client.ice.end',
5777
- payload: {
5778
- canProceed: !this.turnServerUsed, // If we haven't done turn tls retry yet we will proceed with join attempt
5779
- icePhase: this.turnServerUsed ? 'JOIN_MEETING_FINAL' : 'JOIN_MEETING_RETRY',
5780
- errors: [
5781
- // @ts-ignore
5782
- this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode({
5783
- clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
5784
- signalingState:
5785
- this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
5786
- ?.signalingState ||
5787
- this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
5788
- 'unknown',
5789
- iceConnectionState:
5790
- this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
5791
- ?.iceConnectionState ||
5792
- this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
5793
- ?.iceConnectionState ||
5794
- 'unknown',
5795
- turnServerUsed: this.turnServerUsed,
5796
- }),
5797
- }),
5798
- ],
5799
- },
5800
- options: {
5801
- meetingId: this.id,
5802
- },
5803
- });
5804
+ if (!this.hasMediaConnectionConnectedAtLeastOnce) {
5805
+ // Only send CA event for join flow if we haven't successfully connected media yet
5806
+ // @ts-ignore
5807
+ this.webex.internal.newMetrics.submitClientEvent({
5808
+ name: 'client.ice.end',
5809
+ payload: {
5810
+ canProceed: !this.turnServerUsed, // If we haven't done turn tls retry yet we will proceed with join attempt
5811
+ icePhase: this.turnServerUsed ? 'JOIN_MEETING_FINAL' : 'JOIN_MEETING_RETRY',
5812
+ errors: [
5813
+ // @ts-ignore
5814
+ this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
5815
+ {
5816
+ clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
5817
+ signalingState:
5818
+ this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
5819
+ ?.signalingState ||
5820
+ this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
5821
+ ?.signalingState ||
5822
+ 'unknown',
5823
+ iceConnectionState:
5824
+ this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
5825
+ ?.iceConnectionState ||
5826
+ this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
5827
+ ?.iceConnectionState ||
5828
+ 'unknown',
5829
+ turnServerUsed: this.turnServerUsed,
5830
+ }),
5831
+ }
5832
+ ),
5833
+ ],
5834
+ },
5835
+ options: {
5836
+ meetingId: this.id,
5837
+ },
5838
+ });
5839
+ }
5804
5840
  throw new Error(
5805
5841
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
5806
5842
  );
@@ -5924,9 +5960,24 @@ export default class Meeting extends StatelessWebexPlugin {
5924
5960
  bundlePolicy?: BundlePolicy
5925
5961
  ): Promise<void> {
5926
5962
  this.retriedWithTurnServer = true;
5963
+ const LOG_HEADER = 'Meeting:index#addMedia():retryWithForcedTurnDiscovery -->';
5927
5964
 
5928
5965
  await this.cleanUpBeforeRetryWithTurnServer();
5929
5966
 
5967
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_RETRY, {
5968
+ correlation_id: this.correlationId,
5969
+ state: this.state,
5970
+ meetingState: this.meetingState,
5971
+ reason: 'forcingTurnTls',
5972
+ });
5973
+
5974
+ if (this.state === MEETING_STATE.STATES.LEFT) {
5975
+ LoggerProxy.logger.info(
5976
+ `${LOG_HEADER} meeting state was LEFT after first attempt to establish media connection. Attempting to rejoin. `
5977
+ );
5978
+ await this.join({rejoin: true});
5979
+ }
5980
+
5930
5981
  await this.retryEstablishMediaConnectionWithForcedTurnDiscovery(
5931
5982
  remoteMediaManagerConfig,
5932
5983
  bundlePolicy
@@ -6130,6 +6181,7 @@ export default class Meeting extends StatelessWebexPlugin {
6130
6181
  */
6131
6182
  async addMedia(options: AddMediaOptions = {}): Promise<void> {
6132
6183
  this.retriedWithTurnServer = false;
6184
+ this.hasMediaConnectionConnectedAtLeastOnce = false;
6133
6185
  const LOG_HEADER = 'Meeting:index#addMedia -->';
6134
6186
  LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
6135
6187
 
@@ -6807,17 +6859,13 @@ export default class Meeting extends StatelessWebexPlugin {
6807
6859
  .catch((error) => {
6808
6860
  LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
6809
6861
 
6810
- Metrics.sendBehavioralMetric(
6811
- // @ts-ignore - check if STOP_WHITEBOARD_SHARE_FAILURE exists
6812
- BEHAVIORAL_METRICS.STOP_WHITEBOARD_SHARE_FAILURE,
6813
- {
6814
- correlation_id: this.correlationId,
6815
- locus_id: this.locusUrl.split('/').pop(),
6816
- reason: error.message,
6817
- stack: error.stack,
6818
- board: {channelUrl},
6819
- }
6820
- );
6862
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_STOP_WHITEBOARD_SHARE_FAILURE, {
6863
+ correlation_id: this.correlationId,
6864
+ locus_id: this.locusUrl.split('/').pop(),
6865
+ reason: error.message,
6866
+ stack: error.stack,
6867
+ board: {channelUrl},
6868
+ });
6821
6869
 
6822
6870
  return Promise.reject(error);
6823
6871
  })
@@ -216,6 +216,10 @@ export default class MeetingInfoV2 {
216
216
  installedOrgID,
217
217
  };
218
218
 
219
+ if (installedOrgID) {
220
+ body.installedOrgID = installedOrgID;
221
+ }
222
+
219
223
  const uri = this.webex.meetings.preferredWebexSite
220
224
  ? `https://${this.webex.meetings.preferredWebexSite}/wbxappapi/v2/meetings/spaceInstant`
221
225
  : '';
@@ -203,25 +203,12 @@ MeetingInfoUtil.getDestinationType = async (from) => {
203
203
  return Promise.resolve(options);
204
204
  }
205
205
  );
206
- } else if (hydraId && hydraId.room) {
207
- options.type = _CONVERSATION_URL_;
208
- try {
209
- await webex.internal.services.waitForCatalog('postauth');
210
-
211
- const serviceUrl = webex.internal.services.getServiceUrlFromClusterId(
212
- {
213
- cluster: hydraId.cluster,
214
- },
215
- webex
216
- );
217
-
218
- options.destination = hydraId.destination
219
- ? `${serviceUrl}/conversations/${hydraId.destination}`
220
- : serviceUrl;
221
- } catch (e) {
222
- LoggerProxy.logger.error(`Meeting-info:util#getDestinationType --> ${e}`);
223
- throw e;
224
- }
206
+ } else if (hydraId.room) {
207
+ LoggerProxy.logger.error(
208
+ `Meeting-info:util#getDestinationType --> Using the space ID as a destination is no longer supported. Please refer to the [migration guide](https://github.com/webex/webex-js-sdk/wiki/Migration-to-Unified-Space-Meetings) to migrate to use the meeting ID or SIP address.`
209
+ );
210
+ // Error code 30105 added as Space ID deprecated as of beta, Please refer migration guide.
211
+ throw new SpaceIDDeprecatedError();
225
212
  } else {
226
213
  LoggerProxy.logger.warn(`Meeting-info:util#getDestinationType --> ${meetingInfoError}`);
227
214
  throw new ParameterError(`${meetingInfoError}`);
@@ -60,4 +60,17 @@ export default class MeetingCollection extends Collection {
60
60
 
61
61
  return null;
62
62
  }
63
+
64
+ /**
65
+ * Gets the meeting that has a webrtc media connection
66
+ * NOTE: this function assumes there is no more than 1 such meeting
67
+ *
68
+ * @returns {Meeting} first meeting found, else undefined
69
+ * @public
70
+ * @memberof MeetingCollection
71
+ */
72
+ public getActiveWebrtcMeeting() {
73
+ // @ts-ignore
74
+ return find(this.meetings, (meeting) => meeting.mediaProperties.webrtcMediaConnection);
75
+ }
63
76
  }
@@ -1467,4 +1467,15 @@ export default class Meetings extends WebexPlugin {
1467
1467
  getLogger() {
1468
1468
  return LoggerProxy.get();
1469
1469
  }
1470
+
1471
+ /**
1472
+ * Returns the first meeting it finds that has the webrtc media connection created.
1473
+ * Useful for debugging in the console.
1474
+ *
1475
+ * @private
1476
+ * @returns {Meeting} Meeting object that has a webrtc media connection, else undefined
1477
+ */
1478
+ getActiveWebrtcMeeting() {
1479
+ return this.meetingCollection.getActiveWebrtcMeeting();
1480
+ }
1470
1481
  }
@@ -9,6 +9,7 @@ const BEHAVIORAL_METRICS = {
9
9
  JOIN_FAILURE: 'js_sdk_join_failures',
10
10
  ADD_MEDIA_SUCCESS: 'js_sdk_add_media_success',
11
11
  ADD_MEDIA_FAILURE: 'js_sdk_add_media_failures',
12
+ ADD_MEDIA_RETRY: 'js_sdk_add_media_retry',
12
13
  ROAP_MERCURY_EVENT_RECEIVED: 'js_sdk_roap_mercury_received',
13
14
  CONNECTION_SUCCESS: 'js_sdk_connection_success',
14
15
  CONNECTION_FAILURE: 'js_sdk_connection_failures',
@@ -14,6 +14,7 @@ import {
14
14
  _CALL_,
15
15
  _LEFT_,
16
16
  _ID_,
17
+ RECONNECTION_STATE,
17
18
  } from '../constants';
18
19
  import BEHAVIORAL_METRICS from '../metrics/constants';
19
20
  import ReconnectionError from '../common/errors/reconnection';
@@ -96,7 +97,7 @@ export default class ReconnectionManager {
96
97
 
97
98
  /**
98
99
  * @instance
99
- * @type {String}
100
+ * @type {RECONNECTION_STATE}
100
101
  * @private
101
102
  * @memberof ReconnectionManager
102
103
  */
@@ -265,6 +266,18 @@ export default class ReconnectionManager {
265
266
  return this.status === RECONNECTION.STATE.IN_PROGRESS;
266
267
  }
267
268
 
269
+ /**
270
+ * Sets the reconnection status
271
+ *
272
+ * @public
273
+ * @param {RECONNECTION_STATE} status
274
+ * @memberof ReconnectionManager
275
+ * @returns {undefined}
276
+ */
277
+ public setStatus(status: RECONNECTION_STATE) {
278
+ this.status = status;
279
+ }
280
+
268
281
  /**
269
282
  * @returns {Boolean}
270
283
  * @throws {ReconnectionError}
@@ -337,73 +350,55 @@ export default class ReconnectionManager {
337
350
  });
338
351
  }
339
352
 
340
- return this.executeReconnection({networkDisconnect})
341
- .then(() => {
342
- LoggerProxy.logger.info('ReconnectionManager:index#reconnect --> Reconnection successful.');
353
+ return this.executeReconnection({networkDisconnect}).catch((reconnectError) => {
354
+ if (reconnectError instanceof NeedsRetryError) {
343
355
  LoggerProxy.logger.info(
344
- 'ReconnectionManager:index#reconnect --> Sending reconnect success metric.'
356
+ 'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
345
357
  );
358
+ // Reset our reconnect status since we are looping back to the beginning
359
+ this.status = RECONNECTION.STATE.DEFAULT_STATUS;
346
360
 
347
- // @ts-ignore
348
- this.webex.internal.newMetrics.submitClientEvent({
349
- name: 'client.media.recovered',
350
- payload: {
351
- recoveredBy: 'new',
352
- },
353
- options: {
354
- meetingId: this.meeting.id,
355
- },
356
- });
357
- })
358
- .catch((reconnectError) => {
359
- if (reconnectError instanceof NeedsRetryError) {
360
- LoggerProxy.logger.info(
361
- 'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
362
- );
363
- // Reset our reconnect status since we are looping back to the beginning
364
- this.status = RECONNECTION.STATE.DEFAULT_STATUS;
365
-
366
- // This is a network retry, so we should not log START metrics again
367
- return this.reconnect({networkDisconnect: true, networkRetry: true});
368
- }
361
+ // This is a network retry, so we should not log START metrics again
362
+ return this.reconnect({networkDisconnect: true, networkRetry: true});
363
+ }
369
364
 
370
- // Reconnect has failed
371
- LoggerProxy.logger.error(
372
- 'ReconnectionManager:index#reconnect --> Reconnection failed.',
373
- reconnectError.message
374
- );
375
- LoggerProxy.logger.info(
376
- 'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
377
- );
365
+ // Reconnect has failed
366
+ LoggerProxy.logger.error(
367
+ 'ReconnectionManager:index#reconnect --> Reconnection failed.',
368
+ reconnectError.message
369
+ );
370
+ LoggerProxy.logger.info(
371
+ 'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
372
+ );
378
373
 
379
- // @ts-ignore
380
- this.webex.internal.newMetrics.submitClientEvent({
381
- name: 'client.call.aborted',
382
- payload: {
383
- errors: [
384
- {
385
- category: 'expected',
386
- errorCode: 2008,
387
- fatal: true,
388
- name: 'media-engine',
389
- shownToUser: false,
390
- },
391
- ],
392
- },
393
- options: {
394
- meetingId: this.meeting.id,
395
- },
396
- });
397
- if (reconnectError instanceof NeedsRejoinError) {
398
- // send call aborded event with catogery as expected as we are trying to rejoin
374
+ // @ts-ignore
375
+ this.webex.internal.newMetrics.submitClientEvent({
376
+ name: 'client.call.aborted',
377
+ payload: {
378
+ errors: [
379
+ {
380
+ category: 'expected',
381
+ errorCode: 2008,
382
+ fatal: true,
383
+ name: 'media-engine',
384
+ shownToUser: false,
385
+ },
386
+ ],
387
+ },
388
+ options: {
389
+ meetingId: this.meeting.id,
390
+ },
391
+ });
392
+ if (reconnectError instanceof NeedsRejoinError) {
393
+ // send call aborded event with catogery as expected as we are trying to rejoin
399
394
 
400
- if (this.autoRejoinEnabled) {
401
- return this.rejoinMeeting(reconnectError.wasSharing);
402
- }
395
+ if (this.autoRejoinEnabled) {
396
+ return this.rejoinMeeting(reconnectError.wasSharing);
403
397
  }
398
+ }
404
399
 
405
- throw reconnectError;
406
- });
400
+ throw reconnectError;
401
+ });
407
402
  }
408
403
 
409
404
  /**
@@ -485,14 +480,13 @@ export default class ReconnectionManager {
485
480
  const media = await this.reconnectMedia();
486
481
 
487
482
  LoggerProxy.logger.log(
488
- 'ReconnectionManager:index#executeReconnection --> Media reestablished'
483
+ 'ReconnectionManager:index#executeReconnection --> webRTC media connection renewed and local sdp offer sent'
489
484
  );
490
- this.status = RECONNECTION.STATE.COMPLETE;
491
485
 
492
486
  return media;
493
487
  } catch (error) {
494
488
  LoggerProxy.logger.error(
495
- 'ReconnectionManager:index#executeReconnection --> Media reestablishment failed'
489
+ 'ReconnectionManager:index#executeReconnection --> failed to renew webRTC media connection or initiate offer'
496
490
  );
497
491
  this.status = RECONNECTION.STATE.FAILURE;
498
492
 
@@ -559,9 +553,7 @@ export default class ReconnectionManager {
559
553
  * @memberof ReconnectionManager
560
554
  */
561
555
  async reconnectMedia() {
562
- LoggerProxy.logger.log(
563
- 'ReconnectionManager:index#reconnectMedia --> Begin reestablishment of media'
564
- );
556
+ LoggerProxy.logger.log('ReconnectionManager:index#reconnectMedia --> do turn discovery');
565
557
 
566
558
  // do the TURN server discovery again and ignore reachability results since the TURN server might change
567
559
  const turnServerResult = await this.meeting.roap.doTurnDiscovery(this.meeting, true, true);
@@ -576,6 +568,10 @@ export default class ReconnectionManager {
576
568
  });
577
569
  }
578
570
 
571
+ LoggerProxy.logger.log(
572
+ 'ReconnectionManager:index#reconnectMedia --> renew webRTC media connection and send local sdp offer'
573
+ );
574
+
579
575
  await this.meeting.mediaProperties.webrtcMediaConnection.reconnect(iceServers);
580
576
 
581
577
  // resend media requests