@webex/plugin-meetings 3.8.0-next.54 → 3.8.0-next.56

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.
@@ -85,9 +85,15 @@ export { IceGatheringFailed };
85
85
  * @extends WebexMeetingsError
86
86
  * @property {number} code - 30203
87
87
  * @property {string} message - 'Failed to add media'
88
+ * @property {Error} [cause] - The underlying error that caused the failure
88
89
  */
89
90
  declare class AddMediaFailed extends WebexMeetingsError {
90
91
  static CODE: number;
91
- constructor();
92
+ cause?: Error;
93
+ /**
94
+ * Creates a new AddMediaFailed error
95
+ * @param {Error} [cause] - The underlying error that caused the media addition to fail
96
+ */
97
+ constructor(cause?: Error);
92
98
  }
93
99
  export { AddMediaFailed };
@@ -1976,5 +1976,11 @@ export default class Meeting extends StatelessWebexPlugin {
1976
1976
  * @returns {Promise<void>}
1977
1977
  */
1978
1978
  checkAndRefreshPermissionToken(threshold: number, reason: string): Promise<void>;
1979
+ /**
1980
+ * Gets the media reachability metrics
1981
+ *
1982
+ * @returns {Promise<MediaReachabilityMetrics>}
1983
+ */
1984
+ private getMediaReachabilityMetricFields;
1979
1985
  }
1980
1986
  export {};
@@ -458,7 +458,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
458
458
  }, _callee7);
459
459
  }))();
460
460
  },
461
- version: "3.8.0-next.54"
461
+ version: "3.8.0-next.56"
462
462
  });
463
463
  var _default = exports.default = Webinar;
464
464
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -43,7 +43,7 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.8.0-next.54",
46
+ "@webex/plugin-meetings": "3.8.0-next.56",
47
47
  "@webex/plugin-rooms": "3.8.0-next.21",
48
48
  "@webex/test-helper-chai": "3.8.0-next.17",
49
49
  "@webex/test-helper-mocha": "3.8.0-next.17",
@@ -71,7 +71,7 @@
71
71
  "@webex/internal-plugin-metrics": "3.8.0-next.17",
72
72
  "@webex/internal-plugin-support": "3.8.0-next.21",
73
73
  "@webex/internal-plugin-user": "3.8.0-next.17",
74
- "@webex/internal-plugin-voicea": "3.8.0-next.54",
74
+ "@webex/internal-plugin-voicea": "3.8.0-next.56",
75
75
  "@webex/media-helpers": "3.8.0-next.19",
76
76
  "@webex/plugin-people": "3.8.0-next.19",
77
77
  "@webex/plugin-rooms": "3.8.0-next.21",
@@ -92,5 +92,5 @@
92
92
  "//": [
93
93
  "TODO: upgrade jwt-decode when moving to node 18"
94
94
  ],
95
- "version": "3.8.0-next.54"
95
+ "version": "3.8.0-next.56"
96
96
  }
@@ -152,12 +152,19 @@ WebExMeetingsErrors[IceGatheringFailed.CODE] = IceGatheringFailed;
152
152
  * @extends WebexMeetingsError
153
153
  * @property {number} code - 30203
154
154
  * @property {string} message - 'Failed to add media'
155
+ * @property {Error} [cause] - The underlying error that caused the failure
155
156
  */
156
157
  class AddMediaFailed extends WebexMeetingsError {
157
158
  static CODE = 30203;
159
+ cause?: Error;
158
160
 
159
- constructor() {
161
+ /**
162
+ * Creates a new AddMediaFailed error
163
+ * @param {Error} [cause] - The underlying error that caused the media addition to fail
164
+ */
165
+ constructor(cause?: Error) {
160
166
  super(AddMediaFailed.CODE, 'Failed to add media');
167
+ this.cause = cause;
161
168
  }
162
169
  }
163
170
  export {AddMediaFailed};
@@ -164,6 +164,7 @@ import Member from '../member';
164
164
  import {BrbState, createBrbState} from './brbState';
165
165
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
166
166
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
167
+ import {ReachabilityMetrics} from '../reachability/reachability.types';
167
168
 
168
169
  // default callback so we don't call an undefined function, but in practice it should never be used
169
170
  const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
@@ -262,6 +263,8 @@ type FetchMeetingInfoParams = {
262
263
  sendCAevents?: boolean;
263
264
  };
264
265
 
266
+ type MediaReachabilityMetrics = ReachabilityMetrics & {isSubnetReachable: boolean};
267
+
265
268
  /**
266
269
  * MediaDirection
267
270
  * @typedef {Object} MediaDirection
@@ -7159,12 +7162,18 @@ export default class Meeting extends StatelessWebexPlugin {
7159
7162
  },
7160
7163
  options: {
7161
7164
  meetingId: this.id,
7165
+ rawError: error,
7162
7166
  },
7163
7167
  });
7164
7168
  }
7165
- throw new Error(
7169
+
7170
+ const timedOutError = new Error(
7166
7171
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
7167
7172
  );
7173
+
7174
+ timedOutError.cause = error;
7175
+
7176
+ throw timedOutError;
7168
7177
  }
7169
7178
  }
7170
7179
 
@@ -7225,6 +7234,9 @@ export default class Meeting extends StatelessWebexPlugin {
7225
7234
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7226
7235
  } seconds`
7227
7236
  );
7237
+
7238
+ const error = new Error('Timed out waiting for REMOTE SDP ANSWER');
7239
+
7228
7240
  // @ts-ignore
7229
7241
  this.webex.internal.newMetrics.submitClientEvent({
7230
7242
  name: 'client.media-engine.remote-sdp-received',
@@ -7237,10 +7249,10 @@ export default class Meeting extends StatelessWebexPlugin {
7237
7249
  }),
7238
7250
  ],
7239
7251
  },
7240
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7252
+ options: {meetingId: this.id, rawError: error},
7241
7253
  });
7242
7254
 
7243
- deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
7255
+ deferSDPAnswer.reject(error);
7244
7256
  }, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
7245
7257
 
7246
7258
  LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
@@ -7345,7 +7357,7 @@ export default class Meeting extends StatelessWebexPlugin {
7345
7357
  error
7346
7358
  );
7347
7359
 
7348
- throw new AddMediaFailed();
7360
+ throw new AddMediaFailed(error);
7349
7361
  }
7350
7362
  }
7351
7363
 
@@ -7760,14 +7772,10 @@ export default class Meeting extends StatelessWebexPlugin {
7760
7772
 
7761
7773
  const {connectionType, selectedCandidatePairChanges, numTransports} =
7762
7774
  await this.mediaProperties.getCurrentConnectionInfo();
7763
- // @ts-ignore
7764
- const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
7775
+
7765
7776
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7766
7777
 
7767
- // @ts-ignore
7768
- const isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(
7769
- this.mediaServerIp
7770
- );
7778
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7771
7779
 
7772
7780
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
7773
7781
  correlation_id: this.correlationId,
@@ -7778,8 +7786,7 @@ export default class Meeting extends StatelessWebexPlugin {
7778
7786
  isMultistream: this.isMultistream,
7779
7787
  retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
7780
7788
  isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
7781
- isSubnetReachable,
7782
- ...reachabilityStats,
7789
+ ...reachabilityMetrics,
7783
7790
  ...iceCandidateErrors,
7784
7791
  iceCandidatesCount: this.iceCandidatesCount,
7785
7792
  });
@@ -7801,18 +7808,13 @@ export default class Meeting extends StatelessWebexPlugin {
7801
7808
  LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
7802
7809
 
7803
7810
  // @ts-ignore
7804
- const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
7811
+ const reachabilityMetrics = await this.getMediaReachabilityMetricFields();
7805
7812
 
7806
7813
  const {selectedCandidatePairChanges, numTransports} =
7807
7814
  await this.mediaProperties.getCurrentConnectionInfo();
7808
7815
 
7809
7816
  const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
7810
7817
 
7811
- // @ts-ignore
7812
- const isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(
7813
- this.mediaServerIp
7814
- );
7815
-
7816
7818
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
7817
7819
  correlation_id: this.correlationId,
7818
7820
  locus_id: this.locusUrl.split('/').pop(),
@@ -7842,7 +7844,6 @@ export default class Meeting extends StatelessWebexPlugin {
7842
7844
  this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
7843
7845
  'unknown',
7844
7846
  ...reachabilityMetrics,
7845
- isSubnetReachable,
7846
7847
  ...iceCandidateErrors,
7847
7848
  iceCandidatesCount: this.iceCandidatesCount,
7848
7849
  });
@@ -9613,4 +9614,44 @@ export default class Meeting extends StatelessWebexPlugin {
9613
9614
 
9614
9615
  return Promise.resolve();
9615
9616
  }
9617
+
9618
+ /**
9619
+ * Gets the media reachability metrics
9620
+ *
9621
+ * @returns {Promise<MediaReachabilityMetrics>}
9622
+ */
9623
+ private async getMediaReachabilityMetricFields(): Promise<MediaReachabilityMetrics> {
9624
+ const reachabilityMetrics: ReachabilityMetrics =
9625
+ // @ts-ignore
9626
+ await this.webex.meetings.reachability.getReachabilityMetrics();
9627
+
9628
+ const successKeys: Array<keyof ReachabilityMetrics> = [
9629
+ 'reachability_public_udp_success',
9630
+ 'reachability_public_tcp_success',
9631
+ 'reachability_public_xtls_success',
9632
+ 'reachability_vmn_udp_success',
9633
+ 'reachability_vmn_tcp_success',
9634
+ 'reachability_vmn_xtls_success',
9635
+ ];
9636
+
9637
+ const totalSuccessCases = successKeys.reduce((total, key) => {
9638
+ const value = reachabilityMetrics[key];
9639
+ if (typeof value === 'number') {
9640
+ return total + value;
9641
+ }
9642
+
9643
+ return total;
9644
+ }, 0);
9645
+
9646
+ let isSubnetReachable = null;
9647
+ if (totalSuccessCases > 0) {
9648
+ // @ts-ignore
9649
+ isSubnetReachable = this.webex.meetings.reachability.isSubnetReachable(this.mediaServerIp);
9650
+ }
9651
+
9652
+ return {
9653
+ ...reachabilityMetrics,
9654
+ isSubnetReachable,
9655
+ };
9656
+ }
9616
9657
  }
@@ -2166,7 +2166,7 @@ describe('plugin-meetings', () => {
2166
2166
  someReachabilityMetric1: 'some value1',
2167
2167
  someReachabilityMetric2: 'some value2',
2168
2168
  selectedCandidatePairChanges: 2,
2169
- isSubnetReachable: false,
2169
+ isSubnetReachable: null,
2170
2170
  numTransports: 1,
2171
2171
  iceCandidatesCount: 0,
2172
2172
  }
@@ -2213,7 +2213,7 @@ describe('plugin-meetings', () => {
2213
2213
  signalingState: 'unknown',
2214
2214
  connectionState: 'unknown',
2215
2215
  iceConnectionState: 'unknown',
2216
- isSubnetReachable: true,
2216
+ isSubnetReachable: null,
2217
2217
  })
2218
2218
  );
2219
2219
 
@@ -2279,7 +2279,7 @@ describe('plugin-meetings', () => {
2279
2279
  selectedCandidatePairChanges: 2,
2280
2280
  numTransports: 1,
2281
2281
  iceCandidatesCount: 0,
2282
- isSubnetReachable: true,
2282
+ isSubnetReachable: null,
2283
2283
  }
2284
2284
  );
2285
2285
  });
@@ -2337,7 +2337,7 @@ describe('plugin-meetings', () => {
2337
2337
  signalingState: 'have-local-offer',
2338
2338
  connectionState: 'connecting',
2339
2339
  iceConnectionState: 'checking',
2340
- isSubnetReachable: true,
2340
+ isSubnetReachable: null,
2341
2341
  })
2342
2342
  );
2343
2343
 
@@ -2395,7 +2395,7 @@ describe('plugin-meetings', () => {
2395
2395
  signalingState: 'have-local-offer',
2396
2396
  connectionState: 'connecting',
2397
2397
  iceConnectionState: 'checking',
2398
- isSubnetReachable: true,
2398
+ isSubnetReachable: null,
2399
2399
  })
2400
2400
  );
2401
2401
 
@@ -2731,7 +2731,7 @@ describe('plugin-meetings', () => {
2731
2731
  sinon.stub().returns(FAKE_ERROR));
2732
2732
  webex.meetings.reachability = {
2733
2733
  isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
2734
- getReachabilityMetrics: sinon.stub().resolves(),
2734
+ getReachabilityMetrics: sinon.stub().resolves({}),
2735
2735
  stopReachability: sinon.stub(),
2736
2736
  isSubnetReachable: sinon.stub().returns(true),
2737
2737
  };
@@ -2762,7 +2762,8 @@ describe('plugin-meetings', () => {
2762
2762
  turnDiscoverySkippedReason: undefined,
2763
2763
  });
2764
2764
  meeting.meetingState = 'ACTIVE';
2765
- meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
2765
+ const error = {iceConnected: false}
2766
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects(error);
2766
2767
 
2767
2768
  const forceRtcMetricsSend = sinon.stub().resolves();
2768
2769
  const closeMediaConnectionStub = sinon.stub();
@@ -2780,6 +2781,7 @@ describe('plugin-meetings', () => {
2780
2781
  })
2781
2782
  .catch((err) => {
2782
2783
  errorThrown = err;
2784
+ assert.instanceOf(err.cause, Error);
2783
2785
  assert.instanceOf(err, AddMediaFailed);
2784
2786
  });
2785
2787
 
@@ -2836,6 +2838,7 @@ describe('plugin-meetings', () => {
2836
2838
  },
2837
2839
  options: {
2838
2840
  meetingId: meeting.id,
2841
+ rawError: error,
2839
2842
  },
2840
2843
  });
2841
2844
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
@@ -2847,6 +2850,7 @@ describe('plugin-meetings', () => {
2847
2850
  },
2848
2851
  options: {
2849
2852
  meetingId: meeting.id,
2853
+ rawError: error,
2850
2854
  },
2851
2855
  });
2852
2856
 
@@ -2913,7 +2917,7 @@ describe('plugin-meetings', () => {
2913
2917
  selectedCandidatePairChanges: 2,
2914
2918
  numTransports: 1,
2915
2919
  iceCandidatesCount: 0,
2916
- isSubnetReachable: true,
2920
+ isSubnetReachable: null,
2917
2921
  },
2918
2922
  ]);
2919
2923
 
@@ -2975,10 +2979,13 @@ describe('plugin-meetings', () => {
2975
2979
  },
2976
2980
  turnDiscoverySkippedReason: undefined,
2977
2981
  });
2982
+
2983
+ const mediaConnectionError = new Error('fake error');
2984
+
2978
2985
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon
2979
2986
  .stub()
2980
2987
  .onFirstCall()
2981
- .rejects()
2988
+ .rejects(mediaConnectionError)
2982
2989
  .onSecondCall()
2983
2990
  .resolves();
2984
2991
 
@@ -3047,6 +3054,7 @@ describe('plugin-meetings', () => {
3047
3054
  },
3048
3055
  options: {
3049
3056
  meetingId: meeting.id,
3057
+ rawError: mediaConnectionError,
3050
3058
  },
3051
3059
  });
3052
3060
  assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
@@ -3112,7 +3120,7 @@ describe('plugin-meetings', () => {
3112
3120
  retriedWithTurnServer: true,
3113
3121
  isJoinWithMediaRetry: false,
3114
3122
  iceCandidatesCount: 0,
3115
- isSubnetReachable: true,
3123
+ isSubnetReachable: null,
3116
3124
  },
3117
3125
  ]);
3118
3126
  meeting.roap.doTurnDiscovery;
@@ -3269,7 +3277,7 @@ describe('plugin-meetings', () => {
3269
3277
  iceCandidatesCount: 3,
3270
3278
  '701_error': 3,
3271
3279
  '701_turn_host_lookup_received_error': 1,
3272
- isSubnetReachable: true,
3280
+ isSubnetReachable: null,
3273
3281
  }
3274
3282
  );
3275
3283
 
@@ -3332,7 +3340,7 @@ describe('plugin-meetings', () => {
3332
3340
  iceConnectionState: 'unknown',
3333
3341
  selectedCandidatePairChanges: 2,
3334
3342
  numTransports: 1,
3335
- isSubnetReachable: true,
3343
+ isSubnetReachable: null,
3336
3344
  iceCandidatesCount: 0,
3337
3345
  }
3338
3346
  );
@@ -3394,6 +3402,120 @@ describe('plugin-meetings', () => {
3394
3402
  numTransports: 1,
3395
3403
  '701_error': 2,
3396
3404
  '701_turn_host_lookup_received_error': 1,
3405
+ isSubnetReachable: null,
3406
+ iceCandidatesCount: 0,
3407
+ }
3408
+ );
3409
+
3410
+ assert.isOk(errorThrown);
3411
+ });
3412
+
3413
+ it('should send valid isSubnetReachability if media connection success', async () => {
3414
+ meeting.roap.doTurnDiscovery = sinon.stub().returns({
3415
+ turnServerInfo: undefined,
3416
+ turnDiscoverySkippedReason: undefined,
3417
+ });
3418
+ meeting.meetingState = 'ACTIVE';
3419
+ meeting.mediaProperties.waitForMediaConnectionConnected.resolves();
3420
+ meeting.webex.meetings.reachability = {
3421
+ getReachabilityMetrics: sinon.stub().resolves({
3422
+ reachability_public_udp_success: 5,
3423
+ }),
3424
+ stopReachability: sinon.stub(),
3425
+ isSubnetReachable: sinon.stub().returns(false),
3426
+ };
3427
+
3428
+ const forceRtcMetricsSend = sinon.stub().resolves();
3429
+ const closeMediaConnectionStub = sinon.stub();
3430
+ Media.createMediaConnection = sinon.stub().returns({
3431
+ close: closeMediaConnectionStub,
3432
+ forceRtcMetricsSend,
3433
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
3434
+ initiateOffer: sinon.stub().resolves({}),
3435
+ on: sinon.stub(),
3436
+ });
3437
+
3438
+ await meeting
3439
+ .addMedia({
3440
+ mediaSettings: {},
3441
+ });
3442
+
3443
+ assert.calledWith(
3444
+ Metrics.sendBehavioralMetric,
3445
+ BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS,
3446
+ {
3447
+ correlation_id: meeting.correlationId,
3448
+ locus_id: meeting.locusUrl.split('/').pop(),
3449
+ connectionType: 'udp',
3450
+ selectedCandidatePairChanges: 2,
3451
+ numTransports: 1,
3452
+ isMultistream: false,
3453
+ retriedWithTurnServer: false,
3454
+ isJoinWithMediaRetry: false,
3455
+ iceCandidatesCount: 0,
3456
+ reachability_public_udp_success: 5,
3457
+ isSubnetReachable: false,
3458
+ }
3459
+ );
3460
+ });
3461
+
3462
+ it('should send valid isSubnetReachability if media connection fails', async () => {
3463
+ let errorThrown = undefined;
3464
+
3465
+ meeting.roap.doTurnDiscovery = sinon.stub().returns({
3466
+ turnServerInfo: undefined,
3467
+ turnDiscoverySkippedReason: undefined,
3468
+ });
3469
+ meeting.meetingState = 'ACTIVE';
3470
+ meeting.mediaProperties.waitForMediaConnectionConnected.rejects({iceConnected: false});
3471
+ meeting.webex.meetings.reachability = {
3472
+ getReachabilityMetrics: sinon.stub().resolves({
3473
+ reachability_public_udp_success: 5,
3474
+ }),
3475
+ stopReachability: sinon.stub(),
3476
+ isSubnetReachable: sinon.stub().returns(true),
3477
+ };
3478
+
3479
+ const forceRtcMetricsSend = sinon.stub().resolves();
3480
+ const closeMediaConnectionStub = sinon.stub();
3481
+ Media.createMediaConnection = sinon.stub().returns({
3482
+ close: closeMediaConnectionStub,
3483
+ forceRtcMetricsSend,
3484
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
3485
+ initiateOffer: sinon.stub().resolves({}),
3486
+ on: sinon.stub(),
3487
+ });
3488
+
3489
+ await meeting
3490
+ .addMedia({
3491
+ mediaSettings: {},
3492
+ })
3493
+ .catch((err) => {
3494
+ errorThrown = err;
3495
+ assert.instanceOf(err, AddMediaFailed);
3496
+ });
3497
+
3498
+ // Check that the only metric sent is ADD_MEDIA_FAILURE
3499
+ assert.calledOnceWithExactly(
3500
+ Metrics.sendBehavioralMetric,
3501
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
3502
+ {
3503
+ correlation_id: meeting.correlationId,
3504
+ locus_id: meeting.locusUrl.split('/').pop(),
3505
+ reason: errorThrown.message,
3506
+ stack: errorThrown.stack,
3507
+ code: errorThrown.code,
3508
+ turnDiscoverySkippedReason: undefined,
3509
+ turnServerUsed: true,
3510
+ retriedWithTurnServer: false,
3511
+ isMultistream: false,
3512
+ isJoinWithMediaRetry: false,
3513
+ signalingState: 'unknown',
3514
+ connectionState: 'unknown',
3515
+ iceConnectionState: 'unknown',
3516
+ selectedCandidatePairChanges: 2,
3517
+ numTransports: 1,
3518
+ reachability_public_udp_success: 5,
3397
3519
  isSubnetReachable: true,
3398
3520
  iceCandidatesCount: 0,
3399
3521
  }
@@ -3957,6 +4079,9 @@ describe('plugin-meetings', () => {
3957
4079
  },
3958
4080
  options: {
3959
4081
  meetingId: meeting.id,
4082
+ rawError: {
4083
+ iceConnected: false,
4084
+ }
3960
4085
  },
3961
4086
  },
3962
4087
  ]);