Skip to content
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

Database tries to call connectDatabaseEmulator when already initialized, leading to FIREBASE FATAL ERROR #6853

Closed
Janque opened this issue Dec 5, 2022 · 13 comments · Fixed by #6883
Assignees

Comments

@Janque
Copy link

Janque commented Dec 5, 2022

[REQUIRED] Describe your environment

  • Operating System version: MacOS Monterrey 12.6
  • Browser version: Google Chrome 108.0.5359.94
  • Firebase SDK version: 9.14.0
  • Firebase Product: database

[REQUIRED] Describe the problem

Steps to reproduce:

I'm using the nextjs integration with firebase database and when there is a hot reload (ex. saving a file), the following error appears: Error: FIREBASE FATAL ERROR: Cannot call useEmulator() after instance has already been initialized.
From the line:

 export const database = getDatabase(firebaseApp);

When there has been no hot reload, everything works fine.

After more investigation I found that the code that connects the emulator differs from the one that connects the firebase emulator

//firebase-js-sdk/packages/firestore/src/api/database.ts
export function getFirestore(
  appOrDatabaseId?: FirebaseApp | string,
  optionalDatabaseId?: string
): Firestore {
  const app: FirebaseApp =
    typeof appOrDatabaseId === 'object' ? appOrDatabaseId : getApp();
  const databaseId =
    typeof appOrDatabaseId === 'string'
      ? appOrDatabaseId
      : optionalDatabaseId || DEFAULT_DATABASE_NAME;
  const db = _getProvider(app, 'firestore').getImmediate({
    identifier: databaseId
  }) as Firestore;
  if (!db._initialized) {
    const emulator = getDefaultEmulatorHostnameAndPort('firestore');
    if (emulator) {
      connectFirestoreEmulator(db, ...emulator);
    }
  }
  return db;
}
//firebase-js-sdk/packages/database/src/api/Database.ts
export function getDatabase(
  app: FirebaseApp = getApp(),
  url?: string
): Database {
  const db = _getProvider(app, 'database').getImmediate({
    identifier: url
  }) as Database;
  //In line 323 should be added if(!db._instanceStarted){
    const emulator = getDefaultEmulatorHostnameAndPort('database');
    if (emulator) {
      connectDatabaseEmulator(db, ...emulator);
    }
  //}
  return db;
}
//...
export function connectDatabaseEmulator(
  db: Database,
  host: string,
  port: number,
  options: {
    mockUserToken?: EmulatorMockTokenOptions | string;
  } = {}
): void {
  db = getModularInstance(db);
  db._checkNotDeleted('useEmulator');
  if (db._instanceStarted) {  //This check becomes redundant
    fatal(
      'Cannot call useEmulator() after instance has already been initialized.'
    );
  }
  //...
}

I believe that those changes would fix the issue.

My package.json dependencies

"dependencies": {
    "@fortawesome/fontawesome-svg-core": "^6.2.0",
    "@fortawesome/free-brands-svg-icons": "^6.2.0",
    "@fortawesome/free-solid-svg-icons": "^6.2.0",
    "@fortawesome/react-fontawesome": "^0.2.0",
    "bootstrap": "4.6.2",
    "firebase": "^9.14.0",
    "firebaseui": "^6.0.1",
    "jquery": "^3.6.1",
    "next": "^13.0.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "sass": "^1.55.0"
}
@maneesht
Copy link
Contributor

maneesht commented Dec 5, 2022

@Janque - can you provide an example of how you're using RTDB with Next.js?

@MarkDuckworth MarkDuckworth self-assigned this Dec 6, 2022
@MarkDuckworth
Copy link
Contributor

If I understand this correctly, the recommendation from @Janque is to modify the getDatabase(...) function in RTDB to only attempt to connect to to the emulator if the RTDB instance has not started? That is, make RTDB behave the same as Firestore in initialization.

For general housekeeping, I'm going to remove the firestore tag from this issue.

@MarkDuckworth MarkDuckworth removed their assignment Dec 6, 2022
@Janque
Copy link
Author

Janque commented Dec 6, 2022

@Janque - can you provide an example of how you're using RTDB with Next.js?

Well, as I said it's not really an issue with a specific use, but rather the initialization. Here is an simplified bit that also triggered the error:

// pages/index.js
import { incrementCount } from '../firebase/database';
export default function Home(props) {
   //...
}
export async function getServerSideProps() {
  await incrementCount();
  return {props:{
    //...
  }}
}

// firebase/firebase.js
//...
export const firebaseApp = initializeApp(firebaseConfig);
export const database = getDatabase(firebaseApp);
//...

// firebase/database.js
import { database } from './firebase';
import { ref, set, increment } from "firebase/database";
export async function incrementCount() {
    await set(ref(database, "count"), increment(1));
    return;
}

After I run emulators:start it works fine even after reloading the page, but if I edit (or just save without editing) any file it does a hot reload and then throws the error.

@Janque
Copy link
Author

Janque commented Dec 6, 2022

If I understand this correctly, the recommendation from @Janque is to modify the getDatabase(...) function in RTDB to only attempt to connect to to the emulator if the RTDB instance has not started? That is, make RTDB behave the same as Firestore in initialization.

For general housekeeping, I'm going to remove the firestore tag from this issue.

Yes

@maneesht maneesht self-assigned this Dec 6, 2022
@maneesht
Copy link
Contributor

maneesht commented Dec 7, 2022

@Janque - thanks for the example. I just wanted to make sure there wasn't a useEffect or something similar that was calling getDatabase multiple times as that's something I see from time-to-time.
While I think the change can be made, I'm unable to reproduce this issue. getServerSideProps only seems to get triggered on full reload for me. What version of nextjs are you on?

@Janque
Copy link
Author

Janque commented Dec 7, 2022

@maneesht it's 13.0.2, but I just updated to 13.0.6 and it still happens. Start the emulators, all fine; edit and save a file like index.js; if the browser didn't reload by it self, I refresh the page and then the error.

Server Error
Error: FIREBASE FATAL ERROR: Cannot call useEmulator() after instance has already been initialized. 

This error happened while generating the page. Any console logs will be displayed in the terminal window.

Source
  fatal
    file:///.../node_modules/@firebase/database/dist/node-esm/index.node.esm.js (311:11)
  connectDatabaseEmulator
    file:///.../node_modules/@firebase/database/dist/node-esm/index.node.esm.js (13732:9)
  getDatabase
    file:///.../node_modules/@firebase/database/dist/node-esm/index.node.esm.js (13713:9)

@Janque
Copy link
Author

Janque commented Dec 9, 2022

I just did the change that I suggested in my local package files and the problem is temporarily solved

// node_modules/@firebase/database/dist/node-esm/index.node.esm.js
function getDatabase(app = getApp(), url) {
    const db = _getProvider(app, 'database').getImmediate({
        identifier: url
    });
    if (!db._instanceStarted) {
        const emulator = getDefaultEmulatorHostnameAndPort('database');
        if (emulator) {
            connectDatabaseEmulator(db, ...emulator);
        }
    }
    return db;
}

@maneesht
Copy link
Contributor

maneesht commented Dec 13, 2022

@Janque I was able to reproduce the issue. Though the problem is a little more complicated than I originally had thought. The error comes from the connectDatabaseEmulator that the user calls to connect to the emulator, not the one called by Database.ts. If I add the extra check that you have, it still causes an issue for me. I'm checking with others on this.

@Janque
Copy link
Author

Janque commented Dec 13, 2022

@maneesht I never call connectDatabaseEmulator directly in my code.
But when I did the change in my packages file the problem disappeared I guess because connectDatabaseEmulator wasn’t being called more than once anymore.
After that change I haven’t had any issues with it as long as I don’t update.

@maneesht
Copy link
Contributor

That's very odd, do you have environment variables set up for your emulator port?

@Janque
Copy link
Author

Janque commented Dec 13, 2022

Not sure what you mean by "for the emulator port", but I don't have any .env files

@maneesht
Copy link
Contributor

Basically, getDefaultEmulatorHostnameAndPort will look at your process.env.__FIREBASE_DEFAULTS__ to see if you have any emulator ports available, and if and only if you have that environment set up will it call connectDatabaseEmulator. Do you have that env var defined? And is emulator defined for you?

I appreciate your patience. The more information I know about your use-case, the better the subsequent tests will be.

@Janque
Copy link
Author

Janque commented Dec 13, 2022

Well I don't have any .env setup. So I guess no.

I only have the emulators config in the firebase.json

"emulators": {
  "functions": {
    "port": 5001
  },
  "firestore": {
    "port": 8080
  },
  "database": {
    "port": 9000
  },
  "hosting": {
    "port": 5002
  },
  "pubsub": {
    "port": 8085
  },
  "ui": {
    "enabled": true
  }
}

I'd maybe mention that the default hosting port was 5000 and I had to change it because some AirPlay feature (I think) uses it.

Also when I run the emulators I use firebase emulators:start --import emulators/emulator-fsdb which imports a copy of my firestore db, but I don't think that has anything to do with the issue.

@firebase firebase locked and limited conversation to collaborators Jan 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants