From 807f06aa26438a91aaea08fd38efb6c706bb8a5d Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 12 Oct 2022 12:43:56 -0700 Subject: [PATCH] Catch getDefaults() errors and do not throw (#6686) --- .changeset/grumpy-suits-pump.md | 5 +++ packages/util/src/defaults.ts | 31 ++++++++++--- packages/util/test/defaults.test.ts | 69 ++++++++++++++++++++++++----- 3 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 .changeset/grumpy-suits-pump.md diff --git a/.changeset/grumpy-suits-pump.md b/.changeset/grumpy-suits-pump.md new file mode 100644 index 00000000000..c9a1788570f --- /dev/null +++ b/.changeset/grumpy-suits-pump.md @@ -0,0 +1,5 @@ +--- +'@firebase/util': patch +--- + +Catch errors when the SDK checks for `__FIREBASE_DEFAULTS__` and do not block other app functionality. diff --git a/packages/util/src/defaults.ts b/packages/util/src/defaults.ts index 9f4aaf87f4f..0bf32b1968a 100644 --- a/packages/util/src/defaults.ts +++ b/packages/util/src/defaults.ts @@ -70,7 +70,14 @@ const getDefaultsFromCookie = (): FirebaseDefaults | undefined => { if (typeof document === 'undefined') { return; } - const match = document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/); + let match; + try { + match = document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/); + } catch (e) { + // Some environments such as Angular Universal SSR have a + // `document` object but error on accessing `document.cookie`. + return; + } const decoded = match && base64Decode(match[1]); return decoded && JSON.parse(decoded); }; @@ -81,10 +88,24 @@ const getDefaultsFromCookie = (): FirebaseDefaults | undefined => { * (2) if such an object was provided on a shell environment variable * (3) if such an object exists in a cookie */ -const getDefaults = (): FirebaseDefaults | undefined => - getDefaultsFromGlobal() || - getDefaultsFromEnvVariable() || - getDefaultsFromCookie(); +const getDefaults = (): FirebaseDefaults | undefined => { + try { + return ( + getDefaultsFromGlobal() || + getDefaultsFromEnvVariable() || + getDefaultsFromCookie() + ); + } catch (e) { + /** + * Catch-all for being unable to get __FIREBASE_DEFAULTS__ due + * to any environment case we have not accounted for. Log to + * info instead of swallowing so we can find these unknown cases + * and add paths for them if needed. + */ + console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${e}`); + return; + } +}; /** * Returns emulator host stored in the __FIREBASE_DEFAULTS__ object diff --git a/packages/util/test/defaults.test.ts b/packages/util/test/defaults.test.ts index a1360685d7c..05fcf883482 100644 --- a/packages/util/test/defaults.test.ts +++ b/packages/util/test/defaults.test.ts @@ -14,16 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { expect } from 'chai'; +import { expect, use } from 'chai'; +import { match, restore, SinonStub, stub } from 'sinon'; +import sinonChai from 'sinon-chai'; import { getDefaultEmulatorHost, getDefaultEmulatorHostnameAndPort } from '../src/defaults'; -import { getGlobal } from '../src/environment'; +import * as environment from '../src/environment'; + +use(sinonChai); describe('getDefaultEmulatorHost', () => { after(() => { - delete getGlobal().__FIREBASE_DEFAULTS__; + delete environment.getGlobal().__FIREBASE_DEFAULTS__; }); context('with no config', () => { @@ -32,9 +36,54 @@ describe('getDefaultEmulatorHost', () => { }); }); + context('with no config and process.env undefined', () => { + before(() => { + stub(process, 'env').value(undefined); + }); + after(() => { + restore(); + }); + it('returns undefined and does not throw', () => { + expect(getDefaultEmulatorHost('firestore')).to.be.undefined; + expect(getDefaultEmulatorHost('firestore')).to.not.throw; + }); + }); + + context('with no config and no document or document.cookie throws', () => { + before(() => { + // In Node tests document will not exist + if (typeof document !== 'undefined') { + stub(document, 'cookie').get(() => new Error('aaaah')); + } + }); + after(() => { + restore(); + }); + it('returns undefined and does not throw', () => { + expect(getDefaultEmulatorHost('firestore')).to.be.undefined; + expect(getDefaultEmulatorHost('firestore')).to.not.throw; + }); + }); + + context('with no config and something unexpected throws', () => { + let consoleInfoStub: SinonStub; + before(() => { + stub(environment, 'getGlobal').throws(new Error('getGlobal threw!')); + consoleInfoStub = stub(console, 'info'); + }); + after(() => { + delete process.env.__FIREBASE_DEFAULTS__; + restore(); + }); + it('returns undefined and calls console.info with the error', () => { + expect(getDefaultEmulatorHost('firestore')).to.be.undefined; + expect(consoleInfoStub).to.be.calledWith(match('getGlobal threw!')); + }); + }); + context('with global config not listing the emulator', () => { before(() => { - getGlobal().__FIREBASE_DEFAULTS__ = { + environment.getGlobal().__FIREBASE_DEFAULTS__ = { emulatorHosts: { /* no firestore */ database: '127.0.0.1:8080' @@ -49,7 +98,7 @@ describe('getDefaultEmulatorHost', () => { context('with IPv4 hostname in global config', () => { before(() => { - getGlobal().__FIREBASE_DEFAULTS__ = { + environment.getGlobal().__FIREBASE_DEFAULTS__ = { emulatorHosts: { firestore: '127.0.0.1:8080' } @@ -63,7 +112,7 @@ describe('getDefaultEmulatorHost', () => { context('with quoted IPv6 hostname in global config', () => { before(() => { - getGlobal().__FIREBASE_DEFAULTS__ = { + environment.getGlobal().__FIREBASE_DEFAULTS__ = { emulatorHosts: { firestore: '[::1]:8080' } @@ -78,7 +127,7 @@ describe('getDefaultEmulatorHost', () => { describe('getDefaultEmulatorHostnameAndPort', () => { after(() => { - delete getGlobal().__FIREBASE_DEFAULTS__; + delete environment.getGlobal().__FIREBASE_DEFAULTS__; }); context('with no config', () => { @@ -89,7 +138,7 @@ describe('getDefaultEmulatorHostnameAndPort', () => { context('with global config not listing the emulator', () => { before(() => { - getGlobal().__FIREBASE_DEFAULTS__ = { + environment.getGlobal().__FIREBASE_DEFAULTS__ = { emulatorHosts: { /* no firestore */ database: '127.0.0.1:8080' @@ -104,7 +153,7 @@ describe('getDefaultEmulatorHostnameAndPort', () => { context('with IPv4 hostname in global config', () => { before(() => { - getGlobal().__FIREBASE_DEFAULTS__ = { + environment.getGlobal().__FIREBASE_DEFAULTS__ = { emulatorHosts: { firestore: '127.0.0.1:8080' } @@ -121,7 +170,7 @@ describe('getDefaultEmulatorHostnameAndPort', () => { context('with quoted IPv6 hostname in global config', () => { before(() => { - getGlobal().__FIREBASE_DEFAULTS__ = { + environment.getGlobal().__FIREBASE_DEFAULTS__ = { emulatorHosts: { firestore: '[::1]:8080' }