From bff6a5fd104da2cbc48dced3ed6a7c95d8a5080f Mon Sep 17 00:00:00 2001 From: "Judy Zhu (judyz)" Date: Wed, 10 Dec 2025 15:58:33 +0800 Subject: [PATCH 1/3] fix(meetings): webex-455994 browser-media-block return error message --- .../call-diagnostic-metrics.util.ts | 12 +++++++++ .../call-diagnostic-metrics.util.ts | 26 +++++++++++++++++++ .../plugin-meetings/src/meeting/index.ts | 21 ++++++++++++++- .../test/unit/spec/meeting/index.js | 21 +++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts b/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts index 074571df275..df4fa9e2a2d 100644 --- a/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts +++ b/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts @@ -179,6 +179,18 @@ export const isSdpOfferCreationError = (rawError: any) => { return false; }; +export const isBrowserMediaError = (rawError) => { + if (isBrowserMediaErrorName(rawError.name)) { + return true; + } + + return false; +}; + +export const getBrowserMediaErrorCode = (rawError) => { + return BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name]; +}; + /** * MDN Media Devices getUserMedia() method returns a name if it errs * Documentation can be found here: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia diff --git a/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts b/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts index 92dca99d89a..b75645d09e7 100644 --- a/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +++ b/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts @@ -685,4 +685,30 @@ describe('internal-plugin-metrics', () => { }); }); }); + + describe('isBrowserMediaError', () => { + it('should return true if error name is in BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP', () => { + const errorName = Object.keys(BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP)[0]; + const error = {name: errorName}; + expect(isBrowserMediaError(error)).toBe(true); + }); + + it('should return false if error name is not in BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP', () => { + const error = {name: 'SomeOtherError'}; + expect(isBrowserMediaError(error)).toBe(false); + }); + }); + + describe('getBrowserMediaErrorCode', () => { + it('should return correct error code for known error name', () => { + const errorName = Object.keys(BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP)[0]; + const error = {name: errorName}; + expect(getBrowserMediaErrorCode(error)).toBe(BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[errorName]); + }); + + it('should return undefined for unknown error name', () => { + const error = {name: 'UnknownError'}; + expect(getBrowserMediaErrorCode(error)).toBeUndefined(); + }); + }); }); diff --git a/packages/@webex/plugin-meetings/src/meeting/index.ts b/packages/@webex/plugin-meetings/src/meeting/index.ts index e54300fb473..c965a2dbf90 100644 --- a/packages/@webex/plugin-meetings/src/meeting/index.ts +++ b/packages/@webex/plugin-meetings/src/meeting/index.ts @@ -1,5 +1,5 @@ import uuid from 'uuid'; -import {cloneDeep, isEqual, isEmpty} from 'lodash'; +import {cloneDeep, isEqual, isEmpty, merge} from 'lodash'; import jwtDecode from 'jwt-decode'; // @ts-ignore - Fix this import {StatelessWebexPlugin} from '@webex/webex-core'; @@ -50,6 +50,11 @@ import { type MeetingTranscriptPayload, } from '@webex/internal-plugin-voicea'; +import { + getBrowserMediaErrorCode, + isBrowserMediaError, + isBrowserMediaErrorName, +} from '@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util'; import {processNewCaptions} from './voicea-meeting'; import { @@ -5440,6 +5445,20 @@ export default class Meeting extends StatelessWebexPlugin { shouldRetry = false; } + if (CallDiagnosticUtils.isBrowserMediaError(error)) { + shouldRetry = false; + // eslint-disable-next-line no-ex-assign + error = merge({ + error: { + body: { + errorCode: CallDiagnosticUtils.getBrowserMediaErrorCode(error), + message: error?.message, + name: error?.name, + }, + }, + }); + } + // we only want to call leave if join was successful and this was a retry or we won't be doing any more retries if (joined && (isRetry || !shouldRetry)) { try { diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js index f15e5aff9fc..fd21960b1ec 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js @@ -1316,6 +1316,27 @@ describe('plugin-meetings', () => { }) ); }); + + it('should not handle non-browser media error', () => { + let shouldRetry = true; + let error = {name: 'OtherError', message: 'other'}; + + if (CallDiagnosticUtils.isBrowserMediaError(error)) { + shouldRetry = false; + error = merge({ + error: { + body: { + errorCode: CallDiagnosticUtils.getBrowserMediaErrorCode(error), + message: error?.message, + name: error?.name, + }, + }, + }); + } + + expect(shouldRetry).toBe(true); + expect(error).toEqual({name: 'OtherError', message: 'other'}); + }); }); describe('#isTranscriptionSupported', () => { it('should return false if the feature is not supported for the meeting', () => { From 836f43a83c2bd3f347fd9083d14e092e43693753 Mon Sep 17 00:00:00 2001 From: "Judy Zhu (judyz)" Date: Wed, 10 Dec 2025 17:03:25 +0800 Subject: [PATCH 2/3] fix(meetings): webex-455994 browser-media-block return error message --- .../call-diagnostic-metrics.util.ts | 15 +++++++++++++++ .../call-diagnostic-metrics.util.ts | 13 +++++++++---- .../test/unit/spec/meeting/index.js | 4 ++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts b/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts index df4fa9e2a2d..475c267ff0f 100644 --- a/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts +++ b/packages/@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util.ts @@ -179,7 +179,15 @@ export const isSdpOfferCreationError = (rawError: any) => { return false; }; +/** + * Checks if the given error is a browser media error by its name. + * Returns true if the error name matches any known browser media error name in the mapping. + * + * @param {Object} rawError - The error object to check. + * @returns {boolean} True if the error is a browser media error, false otherwise. + */ export const isBrowserMediaError = (rawError) => { + // eslint-disable-next-line no-use-before-define if (isBrowserMediaErrorName(rawError.name)) { return true; } @@ -187,6 +195,13 @@ export const isBrowserMediaError = (rawError) => { return false; }; +/** + * Returns the client error code mapped to the given browser media error name. + * If the error name is not found in the mapping, returns undefined. + * + * @param {Object} rawError - The error object containing the error name. + * @returns {string|undefined} The mapped client error code, or undefined if not found. + */ export const getBrowserMediaErrorCode = (rawError) => { return BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name]; }; diff --git a/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts b/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts index b75645d09e7..aa42360859c 100644 --- a/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +++ b/packages/@webex/internal-plugin-metrics/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts @@ -10,6 +10,7 @@ import { ICE_AND_REACHABILITY_FAILED_CLIENT_CODE, ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE, MISSING_ROAP_ANSWER_CLIENT_CODE, + BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP, } from '../../../../src/call-diagnostic/config'; import Logger from '@webex/plugin-logger'; @@ -688,14 +689,15 @@ describe('internal-plugin-metrics', () => { describe('isBrowserMediaError', () => { it('should return true if error name is in BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP', () => { + // Use a known browser media error name from the config map const errorName = Object.keys(BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP)[0]; const error = {name: errorName}; - expect(isBrowserMediaError(error)).toBe(true); + assert.isTrue(CallDiagnosticUtils.isBrowserMediaError(error)); }); it('should return false if error name is not in BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP', () => { const error = {name: 'SomeOtherError'}; - expect(isBrowserMediaError(error)).toBe(false); + assert.isFalse(CallDiagnosticUtils.isBrowserMediaError(error)); }); }); @@ -703,12 +705,15 @@ describe('internal-plugin-metrics', () => { it('should return correct error code for known error name', () => { const errorName = Object.keys(BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP)[0]; const error = {name: errorName}; - expect(getBrowserMediaErrorCode(error)).toBe(BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[errorName]); + assert.strictEqual( + CallDiagnosticUtils.getBrowserMediaErrorCode(error), + BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[errorName] + ); }); it('should return undefined for unknown error name', () => { const error = {name: 'UnknownError'}; - expect(getBrowserMediaErrorCode(error)).toBeUndefined(); + assert.isUndefined(CallDiagnosticUtils.getBrowserMediaErrorCode(error)); }); }); }); diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js index fd21960b1ec..a6fb8f03379 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js @@ -1334,8 +1334,8 @@ describe('plugin-meetings', () => { }); } - expect(shouldRetry).toBe(true); - expect(error).toEqual({name: 'OtherError', message: 'other'}); + assert.equal(shouldRetry, true); + assert.equal(error.name, 'OtherError'); }); }); describe('#isTranscriptionSupported', () => { From 11878a819e29f0b2029422b37977e9837453779a Mon Sep 17 00:00:00 2001 From: "Judy Zhu (judyz)" Date: Thu, 11 Dec 2025 14:01:28 +0800 Subject: [PATCH 3/3] fix(meetings): webex-455994 browser-media-block return error message --- .../test/unit/spec/meeting/index.js | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js index a6fb8f03379..c39cf7a46a6 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js @@ -714,7 +714,7 @@ describe('plugin-meetings', () => { }); }); - describe('#joinWithMedia', () => { + describe.only('#joinWithMedia', () => { it('should have #joinWithMedia', () => { assert.exists(meeting.joinWithMedia); }); @@ -1003,6 +1003,35 @@ describe('plugin-meetings', () => { ); }); + it('should call leave() if addMediaInternal() fails ', async () => { + const addMediaError = new Error('fake addMedia error'); + addMediaError.name = 'TypeError'; + + const rejectError = { + error: { + body: { + errorCode: 2729, + message: 'fake addMedia error', + name: 'TypeError' + } + } + }; + meeting.addMediaInternal.rejects(addMediaError); + sinon.stub(meeting, 'leave').resolves(); + + await assert.isRejected( + meeting.joinWithMedia({ + joinOptions, + mediaOptions, + }), + rejectError + ); + + assert.calledOnce(meeting.join); + assert.calledOnce(meeting.addMediaInternal); + assert.calledOnce(Metrics.sendBehavioralMetric); + }); + it('should not call leave() if addMediaInternal() fails the first time and succeeds the second time and should only call join() once', async () => { const addMediaError = new Error('fake addMedia error'); const leaveStub = sinon.stub(meeting, 'leave'); @@ -1316,27 +1345,6 @@ describe('plugin-meetings', () => { }) ); }); - - it('should not handle non-browser media error', () => { - let shouldRetry = true; - let error = {name: 'OtherError', message: 'other'}; - - if (CallDiagnosticUtils.isBrowserMediaError(error)) { - shouldRetry = false; - error = merge({ - error: { - body: { - errorCode: CallDiagnosticUtils.getBrowserMediaErrorCode(error), - message: error?.message, - name: error?.name, - }, - }, - }); - } - - assert.equal(shouldRetry, true); - assert.equal(error.name, 'OtherError'); - }); }); describe('#isTranscriptionSupported', () => { it('should return false if the feature is not supported for the meeting', () => {