@@ -42,81 +42,44 @@ export class AppStore {
4242
4343 // Check if an App already exists and, if so, ensure its AppOptions match
4444 // those of this `initializeApp` request.
45- if ( this . appStore . has ( appName ) ) {
46- const currentApp = this . appStore . get ( appName ) ! ;
47- // Ensure the autoInit state matches the existing app's. If not, throw.
48- if ( currentApp . autoInit ( ) !== autoInit ) {
49- throw new FirebaseAppError (
50- AppErrorCodes . INVALID_APP_OPTIONS ,
51- `Firebase app named "${ appName } " attempted mismatch between custom AppOptions` +
52- ' and an App created via Auto Init.'
53- )
54- } else if ( autoInit ) {
55- // Auto-initialization is triggered when no options were passed to
56- // initializeApp. With no options to compare, simply return the App.
57- return currentApp ;
58- } else {
59- // Auto-initialization was not used. Ensure the options don't contain
60- // incomparable fields, and that options matches the existing app's.
61-
62- // `httpAgent` objects cannot be compared with deepEquals impls.
63- // Idempotency cannot be supported if one exists.
64- if ( typeof options . httpAgent !== 'undefined' ) {
65- throw new FirebaseAppError (
66- AppErrorCodes . INVALID_APP_OPTIONS ,
67- `Firebase app named "${ appName } " already exists and initializeApp was` +
68- ' invoked with an optional http.Agent. The SDK cannot confirm the equality' +
69- ' of http.Agent objects with the existing app. Please use getApp or getApps to reuse' +
70- ' the existing app instead.'
71- ) ;
72- } else if ( typeof currentApp . options . httpAgent !== 'undefined' ) {
73- throw new FirebaseAppError (
74- AppErrorCodes . INVALID_APP_OPTIONS ,
75- `An existing app named "${ appName } " already exists with a different` +
76- ' options configuration: httpAgent.'
77- ) ;
78- }
79-
80- // `Credential` objects cannot be compared with deepEquals impls.
81- // Idempotency cannot be supported if one exists.
82- if ( typeof options . credential !== 'undefined' ) {
83- throw new FirebaseAppError (
84- AppErrorCodes . INVALID_APP_OPTIONS ,
85- `Firebase app named "${ appName } " already exists and initializeApp was` +
86- ' invoked with an optional Credential. The SDK cannot confirm the equality' +
87- ' of Credential objects with the existing app. Please use getApp or getApps' +
88- ' to reuse the existing app instead.'
89- ) ;
90- } else if ( currentApp . customCredential ( ) ) {
91- throw new FirebaseAppError (
92- AppErrorCodes . INVALID_APP_OPTIONS ,
93- `An existing app named "${ appName } " already exists with a different` +
94- ' options configuration: Credential.'
95- ) ;
96- }
97-
98- // The AppOptions object has no known incomparable fields.
99-
100- // FirebaseApp() aguments app.options with a synthesized Credential
101- // upon App construction (below). Run a comparison w/o Credential to
102- // see if the base configurations match. Return the existing app if so.
103- const currentAppOptions = { ...currentApp . options } ;
104- delete currentAppOptions . credential ;
105- if ( deepEqual ( options , currentAppOptions ) ) {
106- return currentApp ;
107- } else {
108- throw new FirebaseAppError (
109- AppErrorCodes . DUPLICATE_APP ,
110- `A Firebase app named "${ appName } " already exists with a different options` +
111- ' configuration.'
112- ) ;
113- }
114- }
45+ if ( ! this . appStore . has ( appName ) ) {
46+ const app = new FirebaseApp ( options , appName , autoInit , this ) ;
47+ this . appStore . set ( app . name , app ) ;
48+ return app ;
49+ }
50+
51+ const currentApp = this . appStore . get ( appName ) ! ;
52+ // Ensure the autoInit state matches the existing app's. If not, throw.
53+ if ( currentApp . autoInit ( ) !== autoInit ) {
54+ throw new FirebaseAppError (
55+ AppErrorCodes . INVALID_APP_OPTIONS ,
56+ `A Firebase app named "${ appName } " already exists with a different configuration.`
57+ )
58+ }
59+
60+ if ( autoInit ) {
61+ // Auto-initialization is triggered when no options were passed to
62+ // initializeApp. With no options to compare, simply return the App.
63+ return currentApp ;
11564 }
11665
117- const app = new FirebaseApp ( options , appName , autoInit , this ) ;
118- this . appStore . set ( app . name , app ) ;
119- return app ;
66+ // Ensure the options objects don't break deep equal comparisons.
67+ validateAppOptionsSupportDeepEquals ( options , currentApp ) ;
68+
69+ // FirebaseApp() adds a synthesized Credential to app.options upon App
70+ // construction. Run a comparison w/o Credential to see if the base config
71+ // matches. Return the existing app if so.
72+ const currentAppOptions = { ...currentApp . options } ;
73+ delete currentAppOptions . credential ;
74+ if ( ! deepEqual ( options , currentAppOptions ) ) {
75+ throw new FirebaseAppError (
76+ AppErrorCodes . DUPLICATE_APP ,
77+ `A Firebase app named "${ appName } " already exists with a different configuration.`
78+ ) ;
79+
80+ }
81+
82+ return currentApp ;
12083 }
12184
12285 public getApp ( appName : string = DEFAULT_APP_NAME ) : App {
@@ -169,6 +132,59 @@ export class AppStore {
169132 }
170133}
171134
135+ /**
136+ * Validates that the `requestedOptions` and the `existingApp` options objects
137+ * do not have fields that would break deep equals comparisons.
138+ *
139+ * @param requestedOptions The incoming AppOptions of a new initailizeApp
140+ * request.
141+ * @param existingApp An existing app with internal options to compare against.
142+ *
143+ * @throws FirebaseAppError if the objects cannot be deeply compared.
144+ *
145+ * @internal
146+ */
147+ function validateAppOptionsSupportDeepEquals (
148+ requestedOptions : AppOptions ,
149+ existingApp : FirebaseApp ) : void {
150+
151+ // http.Agent checks.
152+ if ( typeof requestedOptions . httpAgent !== 'undefined' ) {
153+ throw new FirebaseAppError (
154+ AppErrorCodes . INVALID_APP_OPTIONS ,
155+ `Firebase app named "${ existingApp . name } " already exists and initializeApp was` +
156+ ' invoked with an optional http.Agent. The SDK cannot confirm the equality' +
157+ ' of http.Agent objects with the existing app. Please use getApp or getApps to reuse' +
158+ ' the existing app instead.'
159+ ) ;
160+ } else if ( typeof existingApp . options . httpAgent !== 'undefined' ) {
161+ throw new FirebaseAppError (
162+ AppErrorCodes . INVALID_APP_OPTIONS ,
163+ `An existing app named "${ existingApp . name } " already exists with a different` +
164+ ' options configuration: httpAgent.'
165+ ) ;
166+ }
167+
168+ // Credential checks.
169+ if ( typeof requestedOptions . credential !== 'undefined' ) {
170+ throw new FirebaseAppError (
171+ AppErrorCodes . INVALID_APP_OPTIONS ,
172+ `Firebase app named "${ existingApp . name } " already exists and initializeApp was` +
173+ ' invoked with an optional Credential. The SDK cannot confirm the equality' +
174+ ' of Credential objects with the existing app. Please use getApp or getApps' +
175+ ' to reuse the existing app instead.'
176+ ) ;
177+ }
178+
179+ if ( existingApp . customCredential ( ) ) {
180+ throw new FirebaseAppError (
181+ AppErrorCodes . INVALID_APP_OPTIONS ,
182+ `An existing app named "${ existingApp . name } " already exists with a different` +
183+ ' options configuration: Credential.'
184+ ) ;
185+ }
186+ }
187+
172188/**
173189 * Checks to see if the provided appName is a non-empty string and throws if it
174190 * is not.
@@ -180,7 +196,7 @@ export class AppStore {
180196 * @internal
181197 */
182198function validateAppNameFormat ( appName : string ) : void {
183- if ( typeof appName !== 'string' || appName === '' ) {
199+ if ( ! validator . isNonEmptyString ( appName ) ) {
184200 throw new FirebaseAppError (
185201 AppErrorCodes . INVALID_APP_NAME ,
186202 `Invalid Firebase app name "${ appName } " provided. App name must be a non-empty string.` ,
@@ -201,6 +217,17 @@ export const defaultAppStore = new AppStore();
201217 * `options.credential` are defined. When either is defined, subsequent invocations will
202218 * throw instead of returning an {@link App} object.
203219 *
220+ * For example, to safely initialize an app that may already exist:
221+ *
222+ * ```javascript
223+ * let app;
224+ * try {
225+ * app = getApp("myApp");
226+ * } catch (error) {
227+ * app = initializeApp({ credential: myCredential }, "myApp");
228+ * }
229+ * ```
230+ *
204231 * @param options - Optional A set of {@link AppOptions} for the {@link App} instance.
205232 * If not present, `initializeApp` will try to initialize with the options from the
206233 * `FIREBASE_CONFIG` environment variable. If the environment variable contains a string
@@ -225,7 +252,7 @@ export function initializeApp(options?: AppOptions, appName: string = DEFAULT_AP
225252
226253/**
227254 * Returns an existing App instance for the provided name. If no name is provided
228- * the the default app name is queried .
255+ * the the default app name is used .
229256 *
230257 * @param appName - Optional name of the FirebaseApp instance.
231258 *
0 commit comments