From 177cde1665eb69041b12d4d4e0ef0f878e0293f3 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 3 Feb 2025 11:09:58 -0500 Subject: [PATCH 1/7] connectAuthEmulator idempotency --- packages/auth/src/core/auth/emulator.ts | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/auth/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts index f0ccb048f1f..7ea74d5b0e7 100644 --- a/packages/auth/src/core/auth/emulator.ts +++ b/packages/auth/src/core/auth/emulator.ts @@ -44,15 +44,9 @@ import { _castAuth } from './auth_impl'; export function connectAuthEmulator( auth: Auth, url: string, - options?: { disableWarnings: boolean } + options: { disableWarnings: boolean } = { disableWarnings: false } ): void { const authInternal = _castAuth(auth); - _assert( - authInternal._canInitEmulator, - authInternal, - AuthErrorCode.EMULATOR_CONFIG_FAILED - ); - _assert( /^https?:\/\//.test(url), authInternal, @@ -66,15 +60,29 @@ export function connectAuthEmulator( const portStr = port === null ? '' : `:${port}`; // Always replace path with "/" (even if input url had no path at all, or had a different one). - authInternal.config.emulator = { url: `${protocol}//${host}${portStr}/` }; - authInternal.settings.appVerificationDisabledForTesting = true; - authInternal.emulatorConfig = Object.freeze({ + const emulator = { url: `${protocol}//${host}${portStr}/` }; + const emulatorConfig = Object.freeze({ host, port, protocol: protocol.replace(':', ''), options: Object.freeze({ disableWarnings }) }); + if (!authInternal._canInitEmulator) { + _assert( + JSON.stringify(emulator) === + JSON.stringify(authInternal.config.emulator) && + JSON.stringify(emulatorConfig) === + JSON.stringify(authInternal.emulatorConfig), + authInternal, + AuthErrorCode.EMULATOR_CONFIG_FAILED + ); + } + + authInternal.config.emulator = emulator; + authInternal.emulatorConfig = emulatorConfig; + authInternal.settings.appVerificationDisabledForTesting = true; + if (!disableWarnings) { emitEmulatorWarning(); } From e0ba6df98293aad07588db2c234982451b079e64 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 3 Feb 2025 14:17:05 -0500 Subject: [PATCH 2/7] Added tests. --- packages/auth/src/core/auth/emulator.test.ts | 35 ++++++++++++++++++++ packages/auth/src/core/auth/emulator.ts | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/auth/src/core/auth/emulator.test.ts b/packages/auth/src/core/auth/emulator.test.ts index 71a30883218..3994ad9b42c 100644 --- a/packages/auth/src/core/auth/emulator.test.ts +++ b/packages/auth/src/core/auth/emulator.test.ts @@ -76,6 +76,41 @@ describe('core/auth/emulator', () => { ); }); + it('passes with same config if a network request has already been made', async () => { + expect(() => connectAuthEmulator(auth, 'http://127.0.0.1:2020')).to.not + .throw; + await user.delete(); + expect(() => connectAuthEmulator(auth, 'http://127.0.0.1:2020')).to.not + .throw; + }); + + it('fails with differeing config if a network request has already been made', async () => { + expect(() => connectAuthEmulator(auth, 'http://127.0.0.1:2020')).to.not + .throw; + await user.delete(); + expect(() => connectAuthEmulator(auth, 'http://127.0.0.1:2021')).to.throw( + FirebaseError, + 'auth/emulator-config-failed' + ); + }); + + it('subsequent calls update the endpoint appropriately', async () => { + connectAuthEmulator(auth, 'http://127.0.0.2:2020'); + expect(auth.emulatorConfig).to.eql({ + protocol: 'http', + host: '127.0.0.2', + port: 2020, + options: { disableWarnings: false } + }); + connectAuthEmulator(auth, 'http://127.0.0.1:2020'); + expect(auth.emulatorConfig).to.eql({ + protocol: 'http', + host: '127.0.0.1', + port: 2020, + options: { disableWarnings: false } + }); + }); + it('updates the endpoint appropriately', async () => { connectAuthEmulator(auth, 'http://127.0.0.1:2020'); await user.delete(); diff --git a/packages/auth/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts index 7ea74d5b0e7..cbb7714c5f0 100644 --- a/packages/auth/src/core/auth/emulator.ts +++ b/packages/auth/src/core/auth/emulator.ts @@ -44,7 +44,7 @@ import { _castAuth } from './auth_impl'; export function connectAuthEmulator( auth: Auth, url: string, - options: { disableWarnings: boolean } = { disableWarnings: false } + options?: { disableWarnings: boolean } ): void { const authInternal = _castAuth(auth); _assert( From ce6341acc780d3a7bff7aa951ddfaf76090b01a5 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Mon, 3 Feb 2025 14:20:22 -0500 Subject: [PATCH 3/7] Return if already attached. --- packages/auth/src/core/auth/emulator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/auth/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts index cbb7714c5f0..d57c822612f 100644 --- a/packages/auth/src/core/auth/emulator.ts +++ b/packages/auth/src/core/auth/emulator.ts @@ -77,6 +77,7 @@ export function connectAuthEmulator( authInternal, AuthErrorCode.EMULATOR_CONFIG_FAILED ); + return; } authInternal.config.emulator = emulator; From bc79c7cf9349b8285639266460adc528bbf2a9ba Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 4 Feb 2025 09:48:52 -0500 Subject: [PATCH 4/7] Added changset and test comment updates. --- .changeset/lemon-candles-vanish.md | 8 ++++++++ packages/auth/src/core/auth/emulator.test.ts | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .changeset/lemon-candles-vanish.md diff --git a/.changeset/lemon-candles-vanish.md b/.changeset/lemon-candles-vanish.md new file mode 100644 index 00000000000..5072132134f --- /dev/null +++ b/.changeset/lemon-candles-vanish.md @@ -0,0 +1,8 @@ +--- +'@firebase/auth': patch +'firebase': patch +--- + +Fixed: invoking `connectAuthEmulator` mulitiple times with the same parameters will no longer cause +an error. Fixes [GitHub Issue #6824](https://github.com/firebase/firebase-js-sdk/issues/6824). + diff --git a/packages/auth/src/core/auth/emulator.test.ts b/packages/auth/src/core/auth/emulator.test.ts index 3994ad9b42c..47c5d927c44 100644 --- a/packages/auth/src/core/auth/emulator.test.ts +++ b/packages/auth/src/core/auth/emulator.test.ts @@ -84,7 +84,7 @@ describe('core/auth/emulator', () => { .throw; }); - it('fails with differeing config if a network request has already been made', async () => { + it('fails with alternate config if a network request has already been made', async () => { expect(() => connectAuthEmulator(auth, 'http://127.0.0.1:2020')).to.not .throw; await user.delete(); @@ -95,11 +95,11 @@ describe('core/auth/emulator', () => { }); it('subsequent calls update the endpoint appropriately', async () => { - connectAuthEmulator(auth, 'http://127.0.0.2:2020'); + connectAuthEmulator(auth, 'http://127.0.0.1:2021'); expect(auth.emulatorConfig).to.eql({ protocol: 'http', - host: '127.0.0.2', - port: 2020, + host: '127.0.0.1', + port: 2021, options: { disableWarnings: false } }); connectAuthEmulator(auth, 'http://127.0.0.1:2020'); From d9fa2041e0ecc1810245c3873064f196f33ffd1a Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Tue, 4 Feb 2025 12:47:52 -0500 Subject: [PATCH 5/7] Use deepEqual instead comparing JSON. --- packages/auth/src/core/auth/emulator.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/auth/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts index d57c822612f..acdca17b072 100644 --- a/packages/auth/src/core/auth/emulator.ts +++ b/packages/auth/src/core/auth/emulator.ts @@ -18,6 +18,7 @@ import { Auth } from '../../model/public_types'; import { AuthErrorCode } from '../errors'; import { _assert } from '../util/assert'; import { _castAuth } from './auth_impl'; +import { deepEqual } from '@firebase/util'; /** * Changes the {@link Auth} instance to communicate with the Firebase Auth Emulator, instead of production @@ -70,10 +71,8 @@ export function connectAuthEmulator( if (!authInternal._canInitEmulator) { _assert( - JSON.stringify(emulator) === - JSON.stringify(authInternal.config.emulator) && - JSON.stringify(emulatorConfig) === - JSON.stringify(authInternal.emulatorConfig), + deepEqual(emulator, authInternal.config.emulator || {}) && + deepEqual(emulatorConfig, authInternal.emulatorConfig || {}), authInternal, AuthErrorCode.EMULATOR_CONFIG_FAILED ); From 71ee5cbda1bb7cdf67fafc1f54f5770814d887a1 Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Wed, 5 Feb 2025 15:34:28 -0500 Subject: [PATCH 6/7] Added more descriptive state checking for asserts --- packages/auth/src/core/auth/emulator.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/auth/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts index acdca17b072..60cc9403d3d 100644 --- a/packages/auth/src/core/auth/emulator.ts +++ b/packages/auth/src/core/auth/emulator.ts @@ -69,13 +69,27 @@ export function connectAuthEmulator( options: Object.freeze({ disableWarnings }) }); + // There are a few scenarios to guard against if the Auth instance has already started: if (!authInternal._canInitEmulator) { + // Applications may not initialize the emulator for the first time if Auth has already started + // to make network requests. _assert( - deepEqual(emulator, authInternal.config.emulator || {}) && - deepEqual(emulatorConfig, authInternal.emulatorConfig || {}), + authInternal.config.emulator && authInternal.emulatorConfig, authInternal, AuthErrorCode.EMULATOR_CONFIG_FAILED ); + + // Applications may not alter the configuration of the emulator (aka pass a different config) + // once Auth has started to make network requests. + _assert( + deepEqual(emulator, authInternal.config.emulator) && + deepEqual(emulatorConfig, authInternal.emulatorConfig), + authInternal, + AuthErrorCode.EMULATOR_CONFIG_FAILED + ); + + // It's valid, however, to invoke connectAuthEmulator() after Auth has started making + // connections, so long as the config matches the existing config. This results in a no-op. return; } From c829d9958f1702aa80daa23340269d73d7ef880b Mon Sep 17 00:00:00 2001 From: DellaBitta Date: Fri, 7 Feb 2025 09:03:50 -0500 Subject: [PATCH 7/7] Update lemon-candles-vanish.md Fixed typo in `multiple` --- .changeset/lemon-candles-vanish.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/lemon-candles-vanish.md b/.changeset/lemon-candles-vanish.md index 5072132134f..715db44d0d7 100644 --- a/.changeset/lemon-candles-vanish.md +++ b/.changeset/lemon-candles-vanish.md @@ -3,6 +3,6 @@ 'firebase': patch --- -Fixed: invoking `connectAuthEmulator` mulitiple times with the same parameters will no longer cause +Fixed: invoking `connectAuthEmulator` multiple times with the same parameters will no longer cause an error. Fixes [GitHub Issue #6824](https://github.com/firebase/firebase-js-sdk/issues/6824).