@webex/plugin-meetings 3.8.0-next.45 → 3.8.0-next.47

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.
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.45",
46
+ "@webex/plugin-meetings": "3.8.0-next.47",
47
47
  "@webex/plugin-rooms": "3.8.0-next.17",
48
48
  "@webex/test-helper-chai": "3.8.0-next.13",
49
49
  "@webex/test-helper-mocha": "3.8.0-next.13",
@@ -71,7 +71,7 @@
71
71
  "@webex/internal-plugin-metrics": "3.8.0-next.13",
72
72
  "@webex/internal-plugin-support": "3.8.0-next.17",
73
73
  "@webex/internal-plugin-user": "3.8.0-next.13",
74
- "@webex/internal-plugin-voicea": "3.8.0-next.45",
74
+ "@webex/internal-plugin-voicea": "3.8.0-next.47",
75
75
  "@webex/media-helpers": "3.8.0-next.14",
76
76
  "@webex/plugin-people": "3.8.0-next.15",
77
77
  "@webex/plugin-rooms": "3.8.0-next.17",
@@ -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.45"
95
+ "version": "3.8.0-next.47"
96
96
  }
@@ -5856,16 +5856,7 @@ export default class Meeting extends StatelessWebexPlugin {
5856
5856
  this
5857
5857
  );
5858
5858
 
5859
- const proxyError = new Proxy(error, {
5860
- // eslint-disable-next-line require-jsdoc
5861
- get(target, prop) {
5862
- if (prop === 'handledBySdk') {
5863
- return true;
5864
- }
5865
-
5866
- return Reflect.get(target, prop);
5867
- },
5868
- });
5859
+ const proxyError = MeetingUtil.markErrorAsHandledBySdk(error);
5869
5860
 
5870
5861
  joinFailed(proxyError);
5871
5862
 
@@ -6254,10 +6245,10 @@ export default class Meeting extends StatelessWebexPlugin {
6254
6245
  /**
6255
6246
  * Handles ROAP_FAILURE event from the webrtc media connection
6256
6247
  *
6257
- * @param {Error} error
6248
+ * @param {Error} roapError
6258
6249
  * @returns {void}
6259
6250
  */
6260
- handleRoapFailure = (error) => {
6251
+ handleRoapFailure = (roapError) => {
6261
6252
  // eslint-disable-next-line @typescript-eslint/no-shadow
6262
6253
  const sendBehavioralMetric = (metricName, error, correlationId) => {
6263
6254
  const data = {
@@ -6273,6 +6264,8 @@ export default class Meeting extends StatelessWebexPlugin {
6273
6264
  Metrics.sendBehavioralMetric(metricName, data, metadata);
6274
6265
  };
6275
6266
 
6267
+ const error = MeetingUtil.markErrorAsHandledBySdk(roapError);
6268
+
6276
6269
  if (error instanceof Errors.SdpOfferCreationError) {
6277
6270
  sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.correlationId);
6278
6271
 
@@ -6311,7 +6304,7 @@ export default class Meeting extends StatelessWebexPlugin {
6311
6304
  clearTimeout(this.sdpResponseTimer);
6312
6305
  this.sdpResponseTimer = undefined;
6313
6306
 
6314
- this.deferSDPAnswer.reject();
6307
+ this.deferSDPAnswer.reject(error);
6315
6308
  }
6316
6309
  } else if (error instanceof Errors.SdpError) {
6317
6310
  // this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
@@ -6466,7 +6459,9 @@ export default class Meeting extends StatelessWebexPlugin {
6466
6459
  {
6467
6460
  logText: `${LOG_HEADER} Roap Offer`,
6468
6461
  }
6469
- ).catch((error) => {
6462
+ ).catch((originalError) => {
6463
+ const error = MeetingUtil.markErrorAsHandledBySdk(originalError);
6464
+
6470
6465
  const multistreamNotSupported = error instanceof MultistreamNotSupportedError;
6471
6466
 
6472
6467
  // @ts-ignore
@@ -7114,7 +7109,28 @@ export default class Meeting extends StatelessWebexPlugin {
7114
7109
  } catch (error) {
7115
7110
  const {iceConnected} = error;
7116
7111
 
7112
+ let handledBySdk = false;
7113
+
7117
7114
  if (!this.hasMediaConnectionConnectedAtLeastOnce) {
7115
+ const caError =
7116
+ // @ts-ignore
7117
+ this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode({
7118
+ clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
7119
+ signalingState:
7120
+ this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
7121
+ ?.signalingState ||
7122
+ this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
7123
+ 'unknown',
7124
+ iceConnected,
7125
+ turnServerUsed: this.turnServerUsed,
7126
+ unreachable:
7127
+ // @ts-ignore
7128
+ await this.webex.meetings.reachability
7129
+ .isWebexMediaBackendUnreachable()
7130
+ .catch(() => false),
7131
+ }),
7132
+ });
7133
+
7118
7134
  // Only send CA event for join flow if we haven't successfully connected media yet
7119
7135
  // @ts-ignore
7120
7136
  this.webex.internal.newMetrics.submitClientEvent({
@@ -7122,37 +7138,25 @@ export default class Meeting extends StatelessWebexPlugin {
7122
7138
  payload: {
7123
7139
  canProceed: !this.turnServerUsed, // If we haven't done turn tls retry yet we will proceed with join attempt
7124
7140
  icePhase: this.addMediaData.icePhaseCallback(),
7125
- errors: [
7126
- // @ts-ignore
7127
- this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
7128
- {
7129
- clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
7130
- signalingState:
7131
- this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
7132
- ?.signalingState ||
7133
- this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
7134
- ?.signalingState ||
7135
- 'unknown',
7136
- iceConnected,
7137
- turnServerUsed: this.turnServerUsed,
7138
- unreachable:
7139
- // @ts-ignore
7140
- await this.webex.meetings.reachability
7141
- .isWebexMediaBackendUnreachable()
7142
- .catch(() => false),
7143
- }),
7144
- }
7145
- ),
7146
- ],
7141
+ errors: [caError],
7147
7142
  },
7148
7143
  options: {
7149
7144
  meetingId: this.id,
7150
7145
  },
7151
7146
  });
7147
+
7148
+ handledBySdk = true;
7152
7149
  }
7153
- throw new Error(
7150
+
7151
+ let timedOutError = new Error(
7154
7152
  `Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
7155
7153
  );
7154
+
7155
+ if (handledBySdk) {
7156
+ timedOutError = MeetingUtil.markErrorAsHandledBySdk(timedOutError);
7157
+ }
7158
+
7159
+ throw timedOutError;
7156
7160
  }
7157
7161
  }
7158
7162
 
@@ -7213,6 +7217,11 @@ export default class Meeting extends StatelessWebexPlugin {
7213
7217
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
7214
7218
  } seconds`
7215
7219
  );
7220
+
7221
+ const timeoutError = new Error('Timeout waiting for SDP answer');
7222
+
7223
+ const timeoutErrorProxy = MeetingUtil.markErrorAsHandledBySdk(timeoutError);
7224
+
7216
7225
  // @ts-ignore
7217
7226
  this.webex.internal.newMetrics.submitClientEvent({
7218
7227
  name: 'client.media-engine.remote-sdp-received',
@@ -7225,7 +7234,7 @@ export default class Meeting extends StatelessWebexPlugin {
7225
7234
  }),
7226
7235
  ],
7227
7236
  },
7228
- options: {meetingId: this.id, rawError: new Error('Timeout waiting for SDP answer')},
7237
+ options: {meetingId: this.id, rawError: timeoutErrorProxy},
7229
7238
  });
7230
7239
 
7231
7240
  deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
@@ -7333,7 +7342,14 @@ export default class Meeting extends StatelessWebexPlugin {
7333
7342
  error
7334
7343
  );
7335
7344
 
7336
- throw new AddMediaFailed();
7345
+ let addMediaFailedError = new AddMediaFailed();
7346
+
7347
+ // @ts-ignore - handledBySdk is added by a proxy
7348
+ if (error.handledBySdk) {
7349
+ addMediaFailedError = MeetingUtil.markErrorAsHandledBySdk(addMediaFailedError);
7350
+ }
7351
+
7352
+ throw addMediaFailedError;
7337
7353
  }
7338
7354
  }
7339
7355
 
@@ -6,6 +6,7 @@ import {WebexPlugin} from '@webex/webex-core';
6
6
  import {MEDIA, HTTP_VERBS, ROAP} from '../constants';
7
7
  import LoggerProxy from '../common/logs/logger-proxy';
8
8
  import {ClientMediaPreferences} from '../reachability/reachability.types';
9
+ import MeetingUtil from './util';
9
10
 
10
11
  export type MediaRequestType = 'RoapMessage' | 'LocalMute';
11
12
  export type RequestResult = any;
@@ -263,7 +264,9 @@ export class LocusMediaRequest extends WebexPlugin {
263
264
 
264
265
  return result;
265
266
  })
266
- .catch((e) => {
267
+ .catch((error) => {
268
+ let e = error;
269
+
267
270
  if (
268
271
  isRequestAffectingConfluenceState(request) &&
269
272
  this.confluenceState === 'creation in progress'
@@ -272,6 +275,8 @@ export class LocusMediaRequest extends WebexPlugin {
272
275
  }
273
276
 
274
277
  if (request.type === 'RoapMessage') {
278
+ e = MeetingUtil.markErrorAsHandledBySdk(e);
279
+
275
280
  // @ts-ignore
276
281
  this.webex.internal.newMetrics.submitClientEvent({
277
282
  name: 'client.locus.media.response',
@@ -812,6 +812,24 @@ const MeetingUtil = {
812
812
  },
813
813
  ];
814
814
  },
815
+
816
+ /**
817
+ * Creates a proxy object to mark an error as handled by the SDK.
818
+ * @param {Error} error original error
819
+ * @returns {Proxy} proxy object with handledBySdk property
820
+ */
821
+ markErrorAsHandledBySdk: (error) => {
822
+ return new Proxy(error, {
823
+ // eslint-disable-next-line require-jsdoc
824
+ get(target, prop) {
825
+ if (prop === 'handledBySdk') {
826
+ return true;
827
+ }
828
+
829
+ return Reflect.get(target, prop);
830
+ },
831
+ });
832
+ },
815
833
  };
816
834
 
817
835
  export default MeetingUtil;
@@ -935,6 +935,21 @@ export default class Meetings extends WebexPlugin {
935
935
  .disconnect()
936
936
  // @ts-ignore
937
937
  .then(() => this.webex.internal.device.unregister())
938
+ .catch((error) => {
939
+ // If error status code is 404, continue the chain
940
+ if (error.statusCode === 404) {
941
+ LoggerProxy.logger.info(
942
+ 'Meetings:index#unregister --> 404 error during device unregister, proceeding normally'
943
+ );
944
+
945
+ return; // returning undefined allows the chain to continue
946
+ }
947
+ // For any other status code, break the chain by rethrowing
948
+ LoggerProxy.logger.error(
949
+ `Meetings:index#unregister --> Failed to unregister device: ${error.message}`
950
+ );
951
+ throw error; // rethrow to break the promise chain
952
+ })
938
953
  .then(() => {
939
954
  Trigger.trigger(
940
955
  this,
@@ -1011,13 +1011,19 @@ describe('plugin-meetings', () => {
1011
1011
  .stub()
1012
1012
  .returns(fakeClientError);
1013
1013
 
1014
- // call joinWithMedia() - it should fail
1015
- await assert.isRejected(
1016
- meeting.joinWithMedia({
1014
+ const promise = meeting.joinWithMedia({
1017
1015
  joinOptions,
1018
1016
  mediaOptions,
1019
1017
  })
1020
- );
1018
+
1019
+ // call joinWithMedia() - it should fail
1020
+ await assert.isRejected(promise);
1021
+
1022
+ const rejectedError = await promise.catch((error) => error);
1023
+
1024
+ // Since the SDK has sent the CA events, we need to mark this error as handled
1025
+ // so the client doesn't try and send CA events again
1026
+ assert.isTrue(rejectedError.handledBySdk);
1021
1027
 
1022
1028
  // check the right CA events have been sent:
1023
1029
  // calls at index 0 and 2 to submitClientEvent are for "client.media.capabilities" which we don't care about in this test
@@ -8810,14 +8816,21 @@ describe('plugin-meetings', () => {
8810
8816
  clock.restore();
8811
8817
  });
8812
8818
 
8813
- const checkMetricSent = (event, error) => {
8819
+ const checkMetricSent = (event, error, expectedErrorCode) => {
8814
8820
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
8815
- assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
8821
+ assert.deepEqual(webex.internal.newMetrics.submitClientEvent.getCall(0).args[0], {
8816
8822
  name: event,
8817
8823
  payload: {
8818
8824
  canProceed: false,
8819
8825
  },
8820
- options: {rawError: error, meetingId: meeting.id},
8826
+ options: {
8827
+ rawError: {
8828
+ ...(error.cause ? {cause: {name: error.cause.name}} : {cause: undefined}),
8829
+ code: expectedErrorCode,
8830
+ name: error.name,
8831
+ },
8832
+ meetingId: meeting.id,
8833
+ },
8821
8834
  });
8822
8835
  };
8823
8836
 
@@ -8851,7 +8864,7 @@ describe('plugin-meetings', () => {
8851
8864
 
8852
8865
  eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
8853
8866
 
8854
- checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
8867
+ checkMetricSent('client.media-engine.local-sdp-generated', fakeError, 30005);
8855
8868
  checkBehavioralMetricSent(
8856
8869
  BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
8857
8870
  Errors.ErrorCode.SdpOfferCreationError,
@@ -8868,7 +8881,7 @@ describe('plugin-meetings', () => {
8868
8881
 
8869
8882
  eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
8870
8883
 
8871
- checkMetricSent('client.media-engine.remote-sdp-received', fakeError);
8884
+ checkMetricSent('client.media-engine.remote-sdp-received', fakeError, 30006);
8872
8885
  checkBehavioralMetricSent(
8873
8886
  BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
8874
8887
  Errors.ErrorCode.SdpOfferHandlingError,
@@ -8892,7 +8905,7 @@ describe('plugin-meetings', () => {
8892
8905
 
8893
8906
  eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
8894
8907
 
8895
- checkMetricSent('client.media-engine.remote-sdp-received', fakeError);
8908
+ checkMetricSent('client.media-engine.remote-sdp-received', fakeError, 30004);
8896
8909
  checkBehavioralMetricSent(
8897
8910
  BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
8898
8911
  Errors.ErrorCode.SdpAnswerHandlingError,
@@ -8900,6 +8913,7 @@ describe('plugin-meetings', () => {
8900
8913
  fakeRootCauseName
8901
8914
  );
8902
8915
  assert.calledOnce(meeting.deferSDPAnswer.reject);
8916
+ assert.isTrue(meeting.deferSDPAnswer.reject.getCall(0).args[0].handledBySdk);
8903
8917
  assert.calledOnce(clearTimeoutSpy);
8904
8918
  });
8905
8919
 
@@ -8909,7 +8923,7 @@ describe('plugin-meetings', () => {
8909
8923
 
8910
8924
  eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
8911
8925
 
8912
- checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
8926
+ checkMetricSent('client.media-engine.local-sdp-generated', fakeError, 30002);
8913
8927
  // expectedMetadataType is the error name in this case
8914
8928
  checkBehavioralMetricSent(
8915
8929
  BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
@@ -8927,7 +8941,7 @@ describe('plugin-meetings', () => {
8927
8941
 
8928
8942
  eventListeners[MediaConnectionEventNames.ROAP_FAILURE](fakeError);
8929
8943
 
8930
- checkMetricSent('client.media-engine.local-sdp-generated', fakeError);
8944
+ checkMetricSent('client.media-engine.local-sdp-generated', fakeError, 30003);
8931
8945
  // expectedMetadataType is the error name in this case
8932
8946
  checkBehavioralMetricSent(
8933
8947
  BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
@@ -9121,7 +9135,7 @@ describe('plugin-meetings', () => {
9121
9135
  assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
9122
9136
  clientErrorCode: expectedErrorCode,
9123
9137
  });
9124
- assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
9138
+ assert.deepEqual(webex.internal.newMetrics.submitClientEvent.getCall(0).args[0], {
9125
9139
  name: 'client.media-engine.remote-sdp-received',
9126
9140
  payload: {
9127
9141
  canProceed,
@@ -9129,9 +9143,18 @@ describe('plugin-meetings', () => {
9129
9143
  },
9130
9144
  options: {
9131
9145
  meetingId: meeting.id,
9132
- rawError: fakeError,
9146
+ rawError: fakeError instanceof MultistreamNotSupportedError ? {
9147
+ code: fakeError.code,
9148
+ name: fakeError.name,
9149
+ sdkMessage: fakeError.sdkMessage,
9150
+ error: fakeError.error,
9151
+ } : {},
9133
9152
  },
9134
9153
  });
9154
+ const actualError = webex.internal.newMetrics.submitClientEvent.getCall(0).args[0].options.rawError;
9155
+
9156
+ assert.isTrue(actualError.handledBySdk);
9157
+ assert.equal(actualError.message, fakeError.message);
9135
9158
  };
9136
9159
 
9137
9160
  it('handles OFFER message correctly when request fails', async () => {
@@ -185,7 +185,12 @@ describe('LocusMediaRequest.send()', () => {
185
185
 
186
186
  it('sends correct metric event when roap message fails', async () => {
187
187
  webexRequestStub.rejects({code: 300, message: 'fake error'});
188
- await assert.isRejected(sendRoapMessage('OFFER'));
188
+
189
+ const promise = sendRoapMessage('OFFER');
190
+ await assert.isRejected(promise);
191
+
192
+ const error = await promise.catch((err) => err);
193
+ assert.isTrue(error.handledBySdk);
189
194
 
190
195
  assert.calledWith(mockWebex.internal.newMetrics.submitClientEvent, {
191
196
  name: 'client.locus.media.response',
@@ -1185,6 +1185,14 @@ describe('plugin-meetings', () => {
1185
1185
  });
1186
1186
  });
1187
1187
 
1188
+ describe('markErrorAsHandledBySdk', () => {
1189
+ it('should set the error as handled', () => {
1190
+ const error = MeetingUtil.markErrorAsHandledBySdk(new Error('Test error'));
1191
+
1192
+ assert.isTrue(error.handledBySdk);
1193
+ })
1194
+ });
1195
+
1188
1196
  describe('getChangeMeetingFloorErrorPayload', () => {
1189
1197
  [
1190
1198
  {
@@ -583,6 +583,24 @@ describe('plugin-meetings', () => {
583
583
  await assert.isRejected(webex.meetings.unregister());
584
584
  });
585
585
 
586
+ it('does not reject when device.unregister fails with statusCode 404', (done) => {
587
+ webex.meetings.registered = true;
588
+ webex.internal.device.unregister = sinon.stub().rejects({statusCode: 404});
589
+ webex.meetings.unregister().then(() => {
590
+ assert.calledWith(
591
+ TriggerProxy.trigger,
592
+ sinon.match.instanceOf(Meetings),
593
+ {
594
+ file: 'meetings',
595
+ function: 'unregister',
596
+ },
597
+ 'meetings:unregistered'
598
+ );
599
+ assert.isFalse(webex.meetings.registered);
600
+ done();
601
+ });
602
+ });
603
+
586
604
  it('rejects when mercury.disconnect fails', async () => {
587
605
  webex.meetings.registered = true;
588
606
  webex.internal.mercury.disconnect = sinon.stub().returns(Promise.reject());