Skip to content

feat: initializeApp idempotency #2947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Conversation

DellaBitta
Copy link

@DellaBitta DellaBitta commented Jul 2, 2025

Discussion

Update initializeApp to return an existing app if one exists with the same name and the same AppOptions. However, due to their inabilty to be compared, initializeApp will throw if an existing app has a configured Credential or httpAgent.

Additionally adds reference docs to getApp and getApps.

Testing

Updated unit tests.

API Changes

Internal: go/fb-admin-init-idempotency

Fixes: #2111

@DellaBitta DellaBitta marked this pull request as ready for review July 10, 2025 15:07
@DellaBitta DellaBitta requested a review from lahirumaramba July 10, 2025 15:07
@DellaBitta DellaBitta changed the title initializeApp idempotency (WIP candidate) initializeApp idempotency Jul 10, 2025
@lahirumaramba lahirumaramba self-assigned this Jul 14, 2025
@lahirumaramba lahirumaramba added release-note release:stage Stage a release candidate labels Jul 14, 2025
@lahirumaramba lahirumaramba changed the title initializeApp idempotency feat: initializeApp idempotency Jul 15, 2025
Copy link
Member

@lahirumaramba lahirumaramba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM! Added a few comments. Thank you!

* or `options.credential` are defined. This is due to the function's inability to
* determine if the existing {@link App}'s `options` compare to the `options` parameter
* of this function. It's recommended to use {@link getApp} or {@link getApps} if your
* implementation uses either of these two fields in {@link AppOptions}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we extend the JSDoc with an example?

/**...
 * For example, to safely initialize an app that may already exist:
 *
 * ```javascript
 * let app;
 * try {
 * app = getApp("myApp");
 * } catch (error) {
 * app = initializeApp({ credential: myCredential }, "myApp");
 * }
 * ```
 */

} else if (autoInit) {
// Auto-initialization is triggered when no options were passed to
// initializeApp. With no options to compare, simply return the App.
return currentApp;
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simplify this by using early exits/guard clauses (and removing the nested if else statements)?
If it doesn't work in this case feel free to ignore. For example:

if (this.appStore.has(appName)) {
  const currentApp = this.appStore.get(appName)!;

  if (currentApp.autoInit() !== autoInit) {
    throw new FirebaseAppError(
      AppErrorCodes.INVALID_APP_OPTIONS,
      `Firebase app named "${appName}" attempted mismatch between custom AppOptions` +
      ' and an App created via Auto Init.'
    )
  }

  if (autoInit) {
    return currentApp;
  }

if (typeof options.httpAgent !== 'undefined') {
.....


// `httpAgent` objects cannot be compared with deepEquals impls.
// Idempotency cannot be supported if one exists.
if (typeof options.httpAgent !== 'undefined') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Would it make sense to move this (and below) validation to a separate function (out of initializeApp) to clean it up a bit?

* @internal
*/
function validateAppNameFormat(appName: string): void {
if (typeof appName !== 'string' || appName === '') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use validator.isNonEmptyString() here?

export function isNonEmptyString(value: any): value is string {

export function initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App {
return defaultAppStore.initializeApp(options, appName);
}

/**
* Returns an existing App instance for the provided name. If no name is provided
* the the default app name is queried.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queried or used?

* A (read-only) array of all initialized apps.
*
* @returns An array containing all initialized apps.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get @egilmorez's review on the new doc changes, please. Thanks!

'name.',
);
AppErrorCodes.INVALID_APP_OPTIONS,
`Firebase app named "${appName}" attempted mismatch between custom AppOptions` +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simplify this error message? I am not sure Auto Init is a well known term in the Admin SDK world

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release:stage Stage a release candidate release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Firebase.initializeApp throwing default Firebase app already exists errors in Next.js apps.
3 participants