Skip to content

Commit daa9eb6

Browse files
committed
Changes based on review feedback.
1 parent c3ef699 commit daa9eb6

File tree

1 file changed

+102
-75
lines changed

1 file changed

+102
-75
lines changed

src/app/lifecycle.ts

Lines changed: 102 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
182198
function 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

Comments
 (0)