From 88926479baa46008a48b8e4ef53eab3db1f83720 Mon Sep 17 00:00:00 2001 From: Marcin Date: Fri, 2 Aug 2024 10:17:28 +0100 Subject: [PATCH] fix: fail sooner if ice connection goes to failed state (#3736) --- .../src/media/MediaConnectionAwaiter.ts | 20 +++++++++ .../unit/spec/media/MediaConnectionAwaiter.ts | 41 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/packages/@webex/plugin-meetings/src/media/MediaConnectionAwaiter.ts b/packages/@webex/plugin-meetings/src/media/MediaConnectionAwaiter.ts index 67be91508c4..52a40599f85 100644 --- a/packages/@webex/plugin-meetings/src/media/MediaConnectionAwaiter.ts +++ b/packages/@webex/plugin-meetings/src/media/MediaConnectionAwaiter.ts @@ -44,6 +44,15 @@ export default class MediaConnectionAwaiter { return this.webrtcMediaConnection.getConnectionState() === ConnectionState.Connected; } + /** + * Returns true if the connection is in an unrecoverable "failed" state + * + * @returns {boolean} + */ + private isFailed(): boolean { + return this.webrtcMediaConnection.getConnectionState() === ConnectionState.Failed; + } + /** * Returns true if the ICE Gathering is completed, false otherwise. * @@ -83,6 +92,17 @@ export default class MediaConnectionAwaiter { `Media:MediaConnectionAwaiter#connectionStateChange --> connection state: ${this.webrtcMediaConnection.getConnectionState()}` ); + if (this.isFailed()) { + LoggerProxy.logger.warn( + 'Media:MediaConnectionAwaiter#connectionStateChange --> ICE failed, rejecting' + ); + this.clearCallbacks(); + + this.defer.reject({ + iceConnected: this.iceConnected, + }); + } + if (!this.isConnected()) { return; } diff --git a/packages/@webex/plugin-meetings/test/unit/spec/media/MediaConnectionAwaiter.ts b/packages/@webex/plugin-meetings/test/unit/spec/media/MediaConnectionAwaiter.ts index 17c05de01ae..de4d308dfff 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/media/MediaConnectionAwaiter.ts +++ b/packages/@webex/plugin-meetings/test/unit/spec/media/MediaConnectionAwaiter.ts @@ -84,6 +84,47 @@ describe('MediaConnectionAwaiter', () => { assert.calledThrice(mockMC.off); }); + it('rejects immediately if ice state is FAILED', async () => { + mockMC.getConnectionState.returns(ConnectionState.Connecting); + mockMC.getIceGatheringState.returns('gathering'); + + let promiseResolved = false; + let promiseRejected = false; + + mediaConnectionAwaiter + .waitForMediaConnectionConnected() + .then(() => { + promiseResolved = true; + }) + .catch((error) => { + promiseRejected = true; + + const {iceConnected} = error; + assert.equal(iceConnected, false); + }); + + await testUtils.flushPromises(); + assert.equal(promiseResolved, false); + assert.equal(promiseRejected, false); + + // check the right listener was registered + assert.calledThrice(mockMC.on); + assert.equal(mockMC.on.getCall(0).args[0], Event.PEER_CONNECTION_STATE_CHANGED); + assert.equal(mockMC.on.getCall(1).args[0], Event.ICE_CONNECTION_STATE_CHANGED); + assert.equal(mockMC.on.getCall(2).args[0], Event.ICE_GATHERING_STATE_CHANGED); + const iceConnectionListener = mockMC.on.getCall(1).args[1]; + + mockMC.getConnectionState.returns(ConnectionState.Failed); + iceConnectionListener(); + + await testUtils.flushPromises(); + + assert.equal(promiseResolved, false); + assert.equal(promiseRejected, true); + + assert.calledThrice(mockMC.off); + }); + it('rejects after timeout if dtls state is not connected', async () => { mockMC.getConnectionState.returns(ConnectionState.Connecting); mockMC.getIceGatheringState.returns('gathering');