Reputation: 413
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 :
What I've already read/tried :
Upvotes: 11
Views: 7468
Reputation: 31
Try to check if the enableIndexedDbPersistence is running in a browser environment like this:
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
if (typeof window !== "undefined") {
enableIndexedDbPersistence(db)
.catch((err) => {
if (err.code === "failed-precondition") {
// Multiple tabs open, persistence can only be enabled
// in one tab at a a time.
// ...
} else if (err.code === "unimplemented") {
// The current browser does not support all of the
// features required to enable persistence
// ...
}
});
}
Upvotes: 1
Reputation: 1109
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 thesettings
property, check how it is in your case.
Upvotes: 3
Reputation: 121
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));
}
}
}
Upvotes: 11
Reputation: 1207
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.
Upvotes: 0