Firebase App named '[DEFAULT]' already exists with different options or config - firebase

I'm building an app with NextJS, NextAuth and Firebase.
While implementing NextAuth, I've encountered this error:
error - FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists with different options or config (app/duplicate-app).
Here's my code:
[...NextAuth].js
import NextAuth from "next-auth/next";
import GoogleProvider from "next-auth/providers/google";
import { FirestoreAdapter } from "#next-auth/firebase-adapter";
import { db } from "#/firebase/config";
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
adapter: FirestoreAdapter(db),
});
My firebase config file
import { initializeApp, getApp, getApps } from "firebase/app";
import "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: ___,
authDomain: ___,
projectId: ___,
storageBucket: ___,
messagingSenderId: ___,
appId: ___,
measurementId: ___,
};
const app =
getApps().length === 0
? initializeApp({ ...firebaseConfig, projectId: firebaseConfig?.projectId })
: getApp();
const db = getFirestore(app);
export { app, db };
As you can see in my config file, I'm testing if an app already exists, but it doesn't seem to work.
I've checked if somebody already had the same problem as me, but I didn't find an answer.
Any idea?
Thanks a lot,
Gabriel

According to the documentation for the Firebase Adapter for NextAuth.js, you should be passing in the configuration object to the adapter directly or an instance of Firestore from the Admin SDK (i.e. using import { getFirestore } from "firebase-admin/firestore").
Initially, you should try removing your "#/firebase/config" import and just use the configuration directly.
import NextAuth from "next-auth/next";
import GoogleProvider from "next-auth/providers/google";
import { FirestoreAdapter } from "#next-auth/firebase-adapter";
const firebaseConfig = {
apiKey: ___,
authDomain: ___,
projectId: ___,
storageBucket: ___,
messagingSenderId: ___,
appId: ___,
measurementId: ___,
};
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
adapter: FirestoreAdapter(firebaseConfig),
});
This initialization behaviour is defined in the main constructor in src/index.ts and in the initialization utility method in src/utils.ts.
The next step to try would be to make sure your Next.js sources make use of the Firebase Admin SDK instead of the client-side Firebase SDK which behaves differently.
If the above doesn't work, you can look for the more general cause. Sift through your codebase and look for statements that initialize Firebase service providers before your code pulls in "#/firebase/config". Any call to getApp() without any arguments will silently initialize the default FirebaseApp instance.
// NOTE: this is pseudo-code, not the actual implementation
// gets the named/default app, throwing an error if not initialized
export function getApp(name: string = DEFAULT_ENTRY_NAME): FirebaseApp {
const app = _apps.get(name);
if (!app && name === DEFAULT_ENTRY_NAME) return initializeApp(); // <-- this initializeApp is your problem
if (!app) throw new Error(name + " not initialized");
return app;
}
This also applies to calls that initialize a service (e.g. getFirestore()) without any app argument as they also will call getApp() internally.
// NOTE: this is pseudo-code, not the actual implementation
export function getFirestore(app?: FirebaseApp) {
app = app || getApp(); // use given app or use default
return app._providers.get('firestore') || initializeFirestore(app, DEFAULT_SETTINGS)
}
Unfortunately, tracking down this particular problem can be a pain as you module bundler/build tool might be "tree-shaking" the code and stripping what it thinks is unnecessary - which may include your getApp() and getFirestore() calls if you don't use app or db in the local code. Using just import "#/firebase/config" in this case should solve that.

Related

How to retrieve auth cookie on serverInit in Pinia store?

I have recently started learning Nuxt 3 by trying to implement basic user auth logic with firebase and now I would need to retrieve the cookie from the req to initialise the user auth state inside Pinia store but it seems like nuxtServerInit is not supported by Pinia. After realising this I proceeded to a different solution where I'm using server middleware to pass the req object to an action called nuxtServerInit() but then I ran into another problem which is that I cannot call the auth().verifyIdToken(token) from there because the firebase is initialised inside a plugin which runs afterwards.
After this I also tried to initialise firebase inside server middleware and pass auth object to nuxtServerInit() action and set it to the state but then I could not call an action from there I guess because Pinia has not initialised yet?
How could I overcome the original problem? What could be a better approach?
Heres the firebase init plugin:
import { initializeApp } from 'firebase/app'
import { getAuth } from "firebase/auth"
import { getFirestore } from 'firebase/firestore'
import { useStore } from '~/store/index.js'
export default defineNuxtPlugin(nuxtApp => {
const firebaseConfig = {
apiKey: "API_KEY",
authDomain: "AUTH_DOMAIN",
projectId: "PROJECT_ID",
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "MESSAGING_SENDER_ID",
appId: "APP_ID"
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app)
const firestore = getFirestore(app)
nuxtApp.vueApp.provide('auth', auth)
nuxtApp.provide('auth', auth)
nuxtApp.vueApp.provide('firestore', firestore)
nuxtApp.provide('firestore', firestore)
const store = useStore()
console.log('AUTH', auth)
auth.onAuthStateChanged((user) => {
store.setAuth(user)
})
})

"Firebase Error : Firestore has already been started and its settings can no longer be changed." connecting Firebase v9 with Firestore Emulator

I have updated to Firebase v9 a few weeks ago and I have an issue when trying to connect my Firebase App to Firestore Emulator.
firebase.js (my VueJS plugin, where I setup Firebase) :
import { initializeApp, getApps } from "firebase/app"
import { getAuth, connectAuthEmulator, onAuthStateChanged } from "firebase/auth";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore"
import { getStorage, connectStorageEmulator } from "firebase/storage";
import { getFunctions, connectFunctionsEmulator } from 'firebase/functions';
import { isSupported, getAnalytics } from "firebase/analytics";
export default async ({ app }, inject) => {
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SERVICE_ID,
appId: process.env.FIREBASE_APP_ID,
measurementId: process.env.FIREBASE_MEASUREMENT_ID,
}
// I've checked, the values of firebaseConfig are all set here.
// This IF statement is here to avoid initializing the app several times
const apps = getApps();
let firebaseApp = null;
if (!apps.length) {
firebaseApp = initializeApp(firebaseConfig);
}
else {
firebaseApp = apps[0];
}
// INIT AUTH
const auth = getAuth();
auth.languageCode = 'fr';
onAuthStateChanged(auth, async authUser => {
const claims = authUser ? (await authUser.getIdTokenResult(true)).claims : null;
await app.store.dispatch('onAuthStateChanged', { authUser, claims });
},
(error) => {
console.error("Firebase Auth onAuthStateChanged ERROR", error)
});
// Get other services
const firestore = getFirestore(firebaseApp);
const storage = getStorage(firebaseApp);
const functions = getFunctions(firebaseApp, process.env.FIREBASE_REGION);
// Setup analytics if supported
let analytics = null;
const analyticsSupported = await isSupported()
if (analyticsSupported) {
analytics = getAnalytics();
analytics.automaticDataCollectionEnabled = false;
}
// Connecting to emulators
if (process.client && process.env.APP_ENV === 'local') {
console.log("LOCAL ENVIRONMENT, CONNECTING TO EMULATORS...");
connectAuthEmulator(auth, "http://localhost:9099");
connectFirestoreEmulator(firestore, 'localhost', 8080);
connectStorageEmulator(storage, "localhost", 9199);
connectFunctionsEmulator(functions, "localhost", 5001);
}
Inject firebase objects into my VueJS app
const fire = { auth, firestore, storage, functions, analytics }
inject('fire', fire);
}
Here is the error I get, caused by this line : connectFirestoreEmulator(firestore, 'localhost', 8080);
FirebaseError Firestore has already been started and its settings can
no longer be changed. You can only modify settings before calling any
other methods on a Firestore object.
I am not trying to modify Firestore object's settings property myself, so it has to be the method connectFirestoreEmulator.
The problem can be narrowed down to the following code :
import { initializeApp } from "firebase/app"
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore"
export default async ({ app }, inject) => {
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SERVICE_ID,
appId: process.env.FIREBASE_APP_ID,
measurementId: process.env.FIREBASE_MEASUREMENT_ID,
}
firebaseApp = initializeApp(firebaseConfig);
const firestore = getFirestore(firebaseApp);
if (process.env.APP_ENV === 'local') {
connectFirestoreEmulator(firestore, 'localhost', 8080);
}
const fire = { auth, firestore, storage, functions, analytics };
inject('fire', fire);
}
I've managed to avoid triggering the error by adding process.client so it doesn't connect to emulators on server-side (SSR) :
if (process.client && process.env.APP_ENV === 'local') {
However when I add that, the emulators are not connected when code is executed server-side (SSR) on the first page load, and initial Firestore data is being read from the real Firebase App instead of the emulators.
Any idea what can be done to manage proper connection to Firestore emulator on SSR ?
Is this a Firebase bug ?
Versions I use :
In my App : Firebase JS SDK v9.6.9
Emulators : firebase-tools v10.4.0 for the emulators
What I've already read/tried :
https://firebase.google.com/docs/reference/js/app.md#initializeapp
Firebase Firestore emulator error `Host has been set in both settings() and useEmulator(), emulator host will be used`
FirebaseFirestore has already been started and its settings can no longer be changed
It's been a while, but I ran into a similar issue, and after a lot of hairpulling, I ended up with a solution (though it feels a little hacky).
Before running the connectFirestoreEmulator line, check if firestor._settingsFrozen is false. So you only run that line basically if Firestore hasn't already been initialized. You can check that firestore is getting initialized with the emulator settings by logging out the firestore variable before the connectFirestoreEmulator line and seeing what the settings say there--if it says port is 8080 and host is localhost, then you're good.
Here's my code for comparison (slightly different setup from yours but I believe we were running into the same issue):
import { initializeApp } from 'firebase/app';
import { connectAuthEmulator, getAuth } from 'firebase/auth';
import { connectFirestoreEmulator, getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "XXXXXXXXX",
authDomain: "XXXXXXXXX",
projectId: "XXXXXXXXX",
storageBucket: "XXXXXXXXX",
messagingSenderId: "XXXXXXXXX",
appId: "XXXXXXXXX",
measurementId: "XXXXXXXXX",
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
export default (context) => {
if (context.env.appEnv === 'dev') {
connectAuthEmulator(auth, `http://127.0.0.1:${context.env.authPort}`);
if (!db._settingsFrozen) {
connectFirestoreEmulator(db, '127.0.0.1', parseInt(context.env.firestorePort));
}
}
}
Reviewing Firebase JS SDK issues related, it seems that the issue is because the Firestore instance (which is initialized like this: firestore = getFirestore(firebaseApp)) is called after the emulator (connectFirestoreEmulator) has been started.
After calling the "connectFirestoreEmulator" method, "firestore" variable is being used in the constant variable "fire = { auth, firestore, storage, functions, analytics }"
If you use "const fire" before connecting to the emulator, the problem may be solved.
Here is a code example that might help you:
firebaseApp = initializeApp(firebaseConfig);
const fire = { auth, firestore, storage, functions, analytics };
const firestore = getFirestore(firebaseApp);
if (process.env.APP_ENV === 'local') {
connectFirestoreEmulator(firestore, 'localhost', 8080);
}
As a reference, I used this github repository.
You can try checking the setting.host value of your firebase object in order to check if it is already 'localhost', so you can skip calling the connectFirestoreEmulator() function.
This did happen to me in an Angular application using Hot Module Replacement. I tried to use a global constant, but did not work.
In my case, I'm using AngularFire (https://github.com/angular/angularfire), so I had to do something like this:
// ...
const firestore = getFirestore();
const host = (firestore.toJSON() as { settings?: { host?: string } }).settings?.host ?? '';
// console.log({ host });
if (process.env.APP_ENV === 'local' && !host.startsWith('localhost')) {
connectFirestoreEmulator(firestore, 'localhost', 8080);
}
// ...
In my case I had to use firestore.toJSON() in order to access the settings property, check how it is in your case.

undefined is not an object (evaluating '_app.default.apps')

this is my code. I am new to react native and trying to make a firebase authanticator using firebase in expo. please hell me with this error. I am facing similar issue for other things related to firebase also.
import firebase from "firebase/app";
import "firebase/auth";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "removed",
authDomain: "removed",
databaseURL: "removed",
projectId: "removed",
storageBucket: "removed",
messagingSenderId: "removed",
appId: "removed"
};
// Initialize Firebase
let app;
if(firebase.apps.length === 0){
app = firebase.initializeApp(firebaseConfig);
}else{
app = firebase.app()
}
const auth = firebase.auth
export { auth };```
see How to check if a Firebase App is already initialized on Android
and switch to s/t like
import { initializeApp, getApps, getApp } from "firebase/app";
getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();

Nuxt + Firebase - Correct way to handle environment variables

I have been trying to develop a fully functional, reusable firebase authentication app however after looking online at multiple different solutions, I have developed the below which works perfectly but from my understanding there is no way to protect the API keys/sensitive data? Is there anyway to use environment variables on the plugins/firebase.js file?
The nuxt/firebase docs suggests declaring them within the nuxt.config.js file? But when following the docs and trying to install firebase & #nuxtjs/firebase I keep running into errors. NPM error when I am trying to install #nuxtjs/firebase
Is there any definitive/working best practise to follow when working with Nuxt & Firebase?
~plugins/firebase.js
import firebase from 'firebase/app';
import 'firebase/auth'
import 'firebase/firestore'
import 'firebase/storage'
const firebaseConfig = {
apiKey: "xxxx",
authDomain: "xxxx",
projectId: "xxxx",
storageBucket: "xxxx",
messagingSenderId: "xxxx",
appId: "xxxx"
};
firebase.apps.length ? firebase.initializeApp(firebaseConfig) : ''
export const auth = firebase.auth()
export const google = new firebase.auth.GoogleAuthProvider()
export const storage = firebase.storage()
export default firebase
~/plugins/fireauth.js
import { auth } from '~/plugins/firebase.js'
export default (context) => {
const { store } = context
return new Promise((resolve, reject) => {
auth.onAuthStateChanged(user => {
console.log(user);
store.dispatch('setUser', user)
resolve(user)
}, err => {
reject(err)
})
})
}
Update to #Kissu
The environment variables are now working as per #kissu's comments below - however the app is now crashing because the initializeApp() is not being run.
Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app-compat/no-app).
~/plugins/firebase.js
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth'
import 'firebase/compat/firestore'
import 'firebase/compat/storage'
export default ({ $config }) => {
const firebaseConfig = {
apiKey: $config.firebaseConfig.apiKey,
authDomain: $config.firebaseConfig.authDomain,
projectId: $config.firebaseConfig.projectId,
storageBucket: $config.firebaseConfig.storageBucket,
messagingSenderId: $config.firebaseConfig.messagingSenderId,
appId: $config.firebaseConfig.appId
}
!firebase.apps.length ? firebase.initializeApp(firebaseConfig) : ''
}
export const auth = firebase.auth()
export const google = new firebase.auth.GoogleAuthProvider()
export const storage = firebase.storage()
Resources:
https://firebase.nuxtjs.org/
https://dev.to/drewclem/building-user-accounts-with-nuxt-vuex-and-firebase-2o6l
In your Firebase Config, you can use local environment variables (from your .env file) using process.env.VUE_APP_:
// ~/plugins/firebase.js
const firebaseConfig = {
apiKey: process.env.VUE_APP_FIREBASE_apiKey,
authDomain: process.env.VUE_APP_FIREBASE_authDomain,
projectId: process.env.VUE_APP_FIREBASE_projectId,
storageBucket: process.env.VUE_APP_FIREBASE_storageBucket,
messagingSenderId: process.env.VUE_APP_FIREBASE_messagingSenderId,
appId: process.env.VUE_APP_FIREBASE_appId,
measurementId: process.env.VUE_APP_FIREBASE_measurementd,
};
But be sure to set your Variables in your .env file like this:
// ~/.env
// NOTE: THIS FILE IS NOT COMMITTED TO CODE REPOSITORY / GITHUB AND SHOULD BE IGNORED BY DEFAULT IN YOUR `.gitignore` FILE
// NOTE: For Vue.js environment variables, you must prefix them with `VUE_APP_`
VUE_APP_FIREBASE_apiKey=
VUE_APP_FIREBASE_authDomain=
VUE_APP_FIREBASE_projectId=
VUE_APP_FIREBASE_storageBucket=
VUE_APP_FIREBASE_messagingSenderId=
VUE_APP_FIREBASE_appId=
VUE_APP_FIREBASE_measurementId=
Where after the = you put your secret keys and ID's. NOTE: Be sure to leave NO SPACE between the = and your key. For example:
VUE_APP_FIREBASE_apiKey=YOUR.KEY.HERE
...
You can read more documentation on VUE_APP Environment Variables here: https://cli.vuejs.org/guide/mode-and-env.html#environment-variables
This .env file should not be committed to Github, or your code repository. Instead, set these variables on your production environment. For example, if you are using Netlify or Heroku, you will want to set Environment Variables with the EXACT same names like VUE_APP_FIREBASE_apiKey and set its value to be equal to your Key.
If using Netlify, set your Environment Variables in your Build & Deploy Site Settings:
https://app.netlify.com/sites/{{YOUR_SITE_HERE}}/settings/deploys#environment

Using with Firebase / Expo query to Cloud Firestore

So I am trying to keep my code clean and build different files for querying... So I may be taking this harder than it needs to be.
I building a react-native app using Expo CLI.
I have created 3 files, one is my firebase config file,
one is a query file
then the actual file that is using that query.
it looks ok to me... but I get this error.
TypeError: undefined is not an object(evaluating'_firebase.firebase.firestore')
Here is my config file
import * as firebase from "firebase";
import 'firebase/firestore';
const firebaseConfig = {
apiKey: "api-key",
authDomain: "project-id.firebaseapp.com",
databaseURL: "https://project-id.firebaseio.com",
projectId: "project-id",
storageBucket: "project-id.appspot.com",
messagingSenderId: "sender-id",
appId: "app-id",
measurementId: "G-measurement-id"
};
firebase.initializeApp(firebaseConfig);
Then I have a a query file, basically acting as the API layer
import { firebase } from "./firebase";
const db = firebase.firestore();
const getListings = () => {
db.collection("listings").get();
};
export default {
getListings,
};
Then I am trying to view the queried data.
import listingApi from "../api/listings";
function ListingsScreen({ navigation }) {
const [listings, setListings] = useState([]);
useEffect(() => {
loadListings();
}, []);
const loadListings = async () => {
const response = await listingApi.getListings();
setListings(response.data);
};
This is my first time ever using Firebase or cloud firestore... so im really confused.
The error message is telling you that '_firebase.firebase.firestore' data is returning as undefined, this means the document you requested doesn't exist.
At the officiald documentation of Expo, is recommended to put the firebase config information in the same file with your code in order to use firebase, for example:
import * as firebase from 'firebase'
import 'firebase/firestore';
const firebaseConfig = { ... } // apiKey, authDomain, etc. (see above)
firebase.initializeApp(firebaseConfig);
const dbh = firebase.firestore();
dbh.collection("characters").doc("mario").set({
employment: "plumber",
outfitColor: "red",
specialAttack: "fireball"
})

Resources